SSV NodeInfo Handshake Protocol Specification
This document specifies the SSV NodeInfo Handshake Protocol. The protocol is used by SSV-based nodes to exchange basic node metadata and validate each other's identity when establishing a connection over Libp2p under a dedicated protocol ID.
Table of Contents
- 1. Introduction
- 2. Definitions
- 3. Protocol Constants
- 4. Data Structures
- 5. Serialization and Signing
- 6. Handshake Protocol Flows
- 7. Security Considerations
- 8. Rationale and Notes
- 9. Examples
1. Introduction
The SSV NodeInfo Handshake Protocol defines how two SSV nodes exchange, sign, and verify each other's NodeInfo, which includes a network_id (such as "holesky", "prater", etc.) and optional metadata about node software versions or subnets. The protocol uses a request-response style handshake over Libp2p under a dedicated protocol ID.
The high-level handshake steps are:
- Requester sends an Envelope (containing its NodeInfo) to the peer.
- Responder verifies this Envelope, checks the
network_id, and replies with its own Envelope. - Requester verifies the responder's Envelope.
- Both sides proceed if verification succeeds; otherwise, the handshake is considered failed.
2. Definitions
2.1 Terminology
| Term | Definition |
|---|---|
| Envelope | A Protobuf-encoded message containing a public_key, payload_type, payload, and signature (covering a domain-separated concatenation of fields). |
| NodeInfo | A JSON-based structure holding key node attributes like network_id plus optional metadata. |
| Handshake | The request-response exchange of Envelopes between two nodes at connection time. |
2.2 Domain Separation
- Domain:
"ssv".
Used to separate signatures for different contexts or protocols.
3. Protocol Constants
| Name | Value | Description |
|---|---|---|
DOMAIN | ssv | Fixed ASCII text used during signature generation. |
PAYLOAD_TYPE | ssv/nodeinfo | Identifies the payload as an SSV NodeInfo structure. |
PROTOCOL_ID | /ssv/info/0.0.1 | Libp2p protocol ID used for the handshake. |
4. Data Structures
4.1 Envelope
The Envelope is a Protobuf message:
message Envelope {
bytes public_key = 1;
bytes payload_type = 2;
bytes payload = 3;
bytes signature = 5;
}4.2 NodeInfo
NodeInfo:
- network_id: String
- metadata: NodeMetadata (optional)4.3 NodeMetadata
NodeMetadata:
- node_version: String
- execution_node: String
- consensus_node: String
- subnets: String5. Serialization and Signing
5.1 Envelope Fields
-
public_key
- Sender’s public key in serialized form (e.g., compressed Secp256k1 or raw Ed25519 bytes).
- The public key is encoded and decoded using Protobuf.
- For reference, Libp2p has a Peer Ids and Keys, which may be consulted for consistent handling across implementations.
-
payload_type
- MUST be
"ssv/nodeinfo"in this protocol. - Used to identify how to interpret
payload.
- MUST be
-
payload
- Contains
NodeInfodata in JSON (described below).
- Contains
-
signature
- A cryptographic signature covering
DOMAIN || payload_type || payload.
- A cryptographic signature covering
5.2 NodeInfo JSON Layout
Internally, the protocol uses a “legacy” layout for NodeInfo serialization, with a top-level JSON structure:
{
"Entries": [
"", // (Index 0) Old forkVersion, not used
"<network_id>", // (Index 1) The NodeInfo.network_id
"<json-encoded metadata>" // (Index 2) if NodeMetadata is present
]
}- If the array has fewer than 2 entries, the payload is invalid.
- If the array has 3 entries, the 3rd entry is a JSON object for metadata, for example:
{
"NodeVersion": "...",
"ExecutionNode": "...",
"ConsensusNode": "...",
"Subnets": "..."
}5.3 Signature Preparation
To sign an Envelope, implementations:
-
Construct the unsigned message:
unsigned_message = DOMAIN || payload_type || payload -
Sign
unsigned_messageusing the node’s private key. -
Write the resulting signature to
signature.
To verify an Envelope:
- Recompute the
unsigned_message. - Verify using
public_keyagainstsignature.
If verification fails, the handshake MUST abort.
6. Handshake Protocol Flows
6.1 Protocol ID
Both peers must speak the protocol identified by:
/ssv/info/0.0.16.2 Request Phase
-
Build Envelope
- The initiating node (Requester) serializes its
NodeInfointo JSON (thepayload). - Sets
payload_type = "ssv/nodeinfo". - Prepends
DOMAIN = "ssv"when computing the signature. - Places the resulting
public_keyandsignatureinto the Envelope.
- The initiating node (Requester) serializes its
-
Send Request
- The requester sends this Envelope as the request.
-
Wait for Response
- The requester awaits the single response from the Responder.
6.3 Response Phase
-
Receive & Verify
- The responder verifies the incoming Envelope:
- Check signature correctness.
- Extract
NodeInfo. - Validate
network_idif necessary (see 6.4).
- The responder verifies the incoming Envelope:
-
Build Response
- If valid, the responder builds and signs its own Envelope containing its
NodeInfo.
- If valid, the responder builds and signs its own Envelope containing its
-
Send Response
- The responder sends the Envelope back to the requester.
-
Requester Verifies
- The requester verifies the signature, parses
NodeInfo, and checksnetwork_id.
- The requester verifies the signature, parses
6.4 Network Mismatch Checks
- Implementations MUST check whether the received
NodeInfo’snetwork_idmatches their localnetwork_id. - If they mismatch, the implementation SHOULD reject the connection.
7. Security Considerations
- Signature Validation is mandatory. Any failure to verify the Envelope’s signature indicates an invalid handshake.
- Public Key Authenticity: The Envelope’s
public_keyis not implicitly trusted. It must match the verified signature. - Network Mismatch: Avoid bridging distinct SSV or Ethereum networks. Peers claiming the wrong
network_idshould be rejected. - Payload Size: Although
NodeInfois generally small, implementations SHOULD impose a maximum bound for payload. Any request or response exceeding this size limit SHOULD be rejected.
8. Rationale and Notes
- Using a Protobuf-based Envelope simplifies cross-language interoperability.
- The domain separation string (
"ssv") prevents signature reuse in other contexts. - The “legacy”
Entrieslayout ensures backward-compatibility with older SSV implementations.
9. Examples
9.1 Example Envelope in Hex
An example Envelope could be hex-encoded as:
0a250802122102ba6a707dcec6c60ba2793d52123d34b22556964fc798d4aa88ffc41a00e42407120c7373762f6e6f6465696e666f1aa5017b22456e7472696573223a5b22222c22686f6c65736b79222c227b5c224e6f646556657273696f6e5c223a5c22676574682f785c222c5c22457865637574696f6e4e6f64655c223a5c22676574682f785c222c5c22436f6e73656e7375734e6f64655c223a5c22707279736d2f785c222c5c225375626e6574735c223a5c2230303030303030303030303030303030303030303030303030303030303030303030305c227d225d7d2a473045022100b8a2a668113330369e74b86ec818a87009e2a351f7ee4c0e431e1f659dd1bc3f02202b1ebf418efa7fb0541f77703bea8563234a1b70b8391d43daa40b6e7c3fcc84Decoding reveals (high-level view):
Envelope {
public_key = <raw bytes>,
payload_type = "ssv/nodeinfo",
payload = {
"Entries": [
"",
"holesky",
"{\"NodeVersion\":\"geth/x\",\"ExecutionNode\":\"geth/x\",\"ConsensusNode\":\"prysm/x\",\"Subnets\":\"00000000000000000000000000000000\"}"
]
},
signature = <signature bytes>
}9.2 Verifying the Envelope
-
Recompute:
domain = "ssv"unsigned_message = "ssv" || "ssv/nodeinfo" || payload_bytes -
Verify signature with
public_key. -
Parse payload JSON => parse
NodeInfo=> checknetwork_id.
