Skip to content

Commit

Permalink
add handshake spec to the book
Browse files Browse the repository at this point in the history
  • Loading branch information
diegomrsantos committed Feb 14, 2025
1 parent dcf9727 commit 0270621
Show file tree
Hide file tree
Showing 2 changed files with 276 additions and 0 deletions.
4 changes: 4 additions & 0 deletions book/src/developers.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ _Documentation for protocol developers._

This section lists Anchor-specific decisions that are not strictly spec'd and may be useful for
other protocol developers wishing to interact with Anchor.

## 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. The spec is define here in the [SSV NodeInfo Handshake Protocol](./handshake.md).
272 changes: 272 additions & 0 deletions book/src/handshake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
# 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](#1-introduction)
- [2. Definitions](#2-definitions)
- [2.1 Terminology](#21-terminology)
- [2.2 Domain Separation](#22-domain-separation)
- [3. Protocol Constants](#3-protocol-constants)
- [4. Data Structures](#4-data-structures)
- [4.1 Envelope](#41-envelope)
- [4.2 NodeInfo](#42-nodeinfo)
- [4.3 NodeMetadata](#43-nodemetadata)
- [5. Serialization and Signing](#5-serialization-and-signing)
- [5.1 Envelope Fields](#51-envelope-fields)
- [5.2 NodeInfo JSON Layout](#52-nodeinfo-json-layout)
- [5.3 Signature Preparation](#53-signature-preparation)
- [6. Handshake Protocol Flows](#6-handshake-protocol-flows)
- [6.1 Protocol ID](#61-protocol-id)
- [6.2 Request Phase](#62-request-phase)
- [6.3 Response Phase](#63-response-phase)
- [6.4 Network Mismatch Checks](#64-network-mismatch-checks)
- [7. Security Considerations](#7-security-considerations)
- [8. Rationale and Notes](#8-rationale-and-notes)
- [9. Examples](#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:

1. **Requester** sends an Envelope (containing its NodeInfo) to the peer.
2. **Responder** verifies this Envelope, checks the `network_id`, and replies with its own Envelope.
3. **Requester** verifies the responder's Envelope.
4. 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:

```protobuf
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: String
```

---

## 5. Serialization and Signing

### 5.1 Envelope Fields

1. **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](https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md), which may be consulted for consistent handling across implementations.

2. **payload_type**
- MUST be `"ssv/nodeinfo"` in this protocol.
- Used to identify how to interpret `payload`.

3. **payload**
- Contains `NodeInfo` data in JSON (described below).

4. **signature**
- A cryptographic signature covering `DOMAIN || payload_type || payload`.

### 5.2 NodeInfo JSON Layout

Internally, the protocol uses a “legacy” layout for `NodeInfo` serialization, with a top-level JSON structure:

```json
{
"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:

```json
{
"NodeVersion": "...",
"ExecutionNode": "...",
"ConsensusNode": "...",
"Subnets": "..."
}
```

### 5.3 Signature Preparation

To **sign** an Envelope, implementations:

1. Construct the unsigned message:
```
unsigned_message = DOMAIN || payload_type || payload
```
2. Sign `unsigned_message` using the node’s private key.
3. Write the resulting signature to `signature`.

To **verify** an Envelope:

1. Recompute the `unsigned_message`.
2. Verify using `public_key` against `signature`.

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.1
```

### 6.2 Request Phase

1. **Build Envelope**
- The initiating node (Requester) serializes its `NodeInfo` into JSON (the `payload`).
- Sets `payload_type = "ssv/nodeinfo"`.
- Prepends `DOMAIN = "ssv"` when computing the signature.
- Places the resulting `public_key` and `signature` into the Envelope.

2. **Send Request**
- The requester sends this Envelope as the request.

3. **Wait for Response**
- The requester awaits the single response from the Responder.

### 6.3 Response Phase

1. **Receive & Verify**
- The responder verifies the incoming Envelope:
- Check signature correctness.
- Extract `NodeInfo`.
- Validate `network_id` if necessary (see [6.4](#64-network-mismatch-checks)).

2. **Build Response**
- If valid, the responder builds and signs its own Envelope containing its `NodeInfo`.

3. **Send Response**
- The responder sends the Envelope back to the requester.

4. **Requester Verifies**
- The requester verifies the signature, parses `NodeInfo`, and checks `network_id`.

### 6.4 Network Mismatch Checks

- Implementations **MUST** check whether the received `NodeInfo`’s `network_id` matches their local `network_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_key` is not implicitly trusted. It must match the verified signature.
- **Network Mismatch**: Avoid bridging distinct SSV or Ethereum networks. Peers claiming the wrong `network_id` should be rejected.
- **Payload Size**: Although `NodeInfo` is 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” `Entries` layout ensures backward-compatibility with older SSV implementations.

---

## 9. Examples

### 9.1 Example Envelope in Hex

An example Envelope could be hex-encoded as:

```
0a250802122102ba6a707dcec6c60ba2793d52123d34b22556964fc798d4aa88ffc41a00e42407120c7373762f6e6f6465696e666f1aa5017b22456e7472696573223a5b22222c22686f6c65736b79222c227b5c224e6f646556657273696f6e5c223a5c22676574682f785c222c5c22457865637574696f6e4e6f64655c223a5c22676574682f785c222c5c22436f6e73656e7375734e6f64655c223a5c22707279736d2f785c222c5c225375626e6574735c223a5c2230303030303030303030303030303030303030303030303030303030303030303030305c227d225d7d2a473045022100b8a2a668113330369e74b86ec818a87009e2a351f7ee4c0e431e1f659dd1bc3f02202b1ebf418efa7fb0541f77703bea8563234a1b70b8391d43daa40b6e7c3fcc84
```

Decoding 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

1. Recompute: `domain = "ssv"`
```
unsigned_message = "ssv" || "ssv/nodeinfo" || payload_bytes
```
2. Verify signature with `public_key`.
3. Parse payload JSON => parse `NodeInfo` => check `network_id`.

0 comments on commit 0270621

Please sign in to comment.