From f01ebb82081e13bfb2f849af9f1af37ca848b9b6 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Wed, 21 Jan 2026 11:30:11 +0000 Subject: [PATCH 1/3] Add SWIP: Fork Digest for Network Upgrade Coordination Introduce a fork digest mechanism that enables nodes to identify compatible peers during network upgrades via the handshake and Hive peer gossip. Key features: - 4-byte fork digest derived from genesis hash and fork version - Handshake integration for connection-time compatibility checks - Hive protocol integration for fork-aware peer gossip filtering - Updated BzzAddress signature scheme (v1) removing redundant prefix - Two-release migration path for backwards compatibility --- SWIPs/swip-fork-digest.md | 185 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 SWIPs/swip-fork-digest.md diff --git a/SWIPs/swip-fork-digest.md b/SWIPs/swip-fork-digest.md new file mode 100644 index 0000000..0ed44b3 --- /dev/null +++ b/SWIPs/swip-fork-digest.md @@ -0,0 +1,185 @@ +--- +swip: +title: Fork Digest for Network Upgrade Coordination +author: mfw78 (@mfw78) +status: Draft +type: Standards Track +category: Networking +created: 2026-01-21 +--- + +## Simple Summary + +Introduce a fork digest mechanism that enables nodes to identify compatible peers during network upgrades via the handshake and Hive peer gossip. + +## Abstract + +A fork digest is a 4-byte identifier derived from the network configuration and current fork version. By including the fork digest in both the handshake protocol and Hive peer advertisements, nodes can efficiently filter incompatible peers before connection and avoid gossiping peers that recipients cannot use. + +## Motivation + +Swarm lacks a standardised mechanism for: + +1. **Peer compatibility detection.** Nodes cannot determine protocol compatibility before establishing connections. +2. **Efficient peer gossip.** Hive broadcasts all known peers regardless of fork compatibility, wasting bandwidth. +3. **Graceful upgrades.** During network upgrades, incompatible nodes waste resources attempting failed connections. + +## Specification + +### Fork Digest Calculation + +``` +fork_digest = keccak256(genesis_hash || current_fork_version)[0:4] +``` + +Where: + +- `genesis_hash`: 32-byte hash uniquely identifying the network +- `current_fork_version`: 4-byte version for the active fork + +#### Genesis Hash + +The genesis hash must uniquely identify a Swarm network. An illustrative example: + +``` +genesis_hash = keccak256(network_id || genesis_timestamp || ...) +``` + +The exact composition of the genesis hash requires further discussion. Candidates for inclusion: + +- `network_id` (required) +- `genesis_timestamp` +- Contract addresses (postage stamp, staking, redistribution) +- Other network-specific parameters + +Feedback is solicited on what should constitute the genesis hash for mainnet and testnet. + +### Fork Versions + +| Fork | Version | Activation | +|------|---------|------------| +| Homestead | `0x00000000` | Genesis | + +### Fork Condition + +Forks use timestamp-based activation: + +``` +fork_active = current_timestamp >= activation_timestamp +``` + +### Handshake Integration + +Add `fork_digest` to the handshake message: + +```protobuf +message Handshake { + bytes overlay_address = 1; + bytes underlay_address = 2; + bytes signature = 3; + bytes nonce = 4; + uint64 network_id = 5; + bool full_node = 6; + bytes fork_digest = 7; // 4 bytes +} +``` + +Nodes must reject connections where `peer.fork_digest != local.fork_digest`. + +### Hive Protocol Integration + +Include the fork digest in peer advertisements: + +```protobuf +message BzzAddress { + bytes underlay = 1; + bytes signature = 2; + bytes overlay = 3; + bytes fork_digest = 4; // 4 bytes +} +``` + +Gossip filtering rules: + +- When receiving peers, ignore those with an incompatible fork digest. +- When sending peers, only advertise those matching the recipient's fork digest. + +This prevents nodes from filling their address books with unreachable peers and reduces unnecessary connection attempts across the network. + +### BzzAddress Signature + +The `BzzAddress.signature` field authenticates the address fields using EIP-191 personal sign. + +**Legacy (v0):** + +``` +data = "bee-handshake-" || underlay || overlay || network_id +signature = eip191_sign(data) +``` + +**Fork-aware (v1):** + +``` +data = underlay || overlay || network_id || fork_digest +signature = eip191_sign(data) +``` + +The `"bee-handshake-"` prefix is removed in v1 as EIP-191 personal sign already provides domain separation via `"\x19Ethereum Signed Message:\n"`. + +During migration, nodes must support verifying both signature formats: + +1. If `fork_digest` is present, verify using the v1 scheme. +2. If `fork_digest` is absent, verify using the legacy v0 scheme. + +Nodes should generate v1 signatures when creating new BzzAddress entries once fork digest support is enabled. + +### Grace Period + +During fork transitions (a one-hour window around activation), nodes may accept both pre-fork and post-fork digests to accommodate clock skew. + +## Rationale + +**4-byte digest.** A 4-byte value is compact yet sufficient (2^32 possible values) for network and fork disambiguation. This matches Ethereum's approach. + +**Timestamp activation.** Swarm has no block consensus, making timestamps the natural coordination mechanism. + +**Hive integration.** Without fork-aware gossip, nodes accumulate stale peer lists during upgrades, degrading connectivity. + +**Signature versioning.** Including the fork digest in the signature binds the address to a specific fork, preventing replay of old addresses on new forks. + +**Removing the "bee-handshake-" prefix.** The legacy prefix was redundant. EIP-191 personal sign already prefixes messages with `"\x19Ethereum Signed Message:\n"`, providing sufficient domain separation. Removing it simplifies the protocol without reducing security. + +## Backwards Compatibility + +This proposal introduces a breaking change to the handshake and Hive protocols. + +Migration: + +1. **Release N.** Fork digest is optional. BzzAddress accepts both v0 and v1 signatures. Nodes generate v1 signatures. +2. **Release N+1.** Fork digest is required. v0 signatures are rejected. + +Once Release N is deployed, fork digests will propagate through Hive gossip as nodes exchange peer information. By the time Release N+1 is deployed, the network should be predominantly fork-aware, easing the transition to mandatory fork digest. + +Nodes that have not upgraded by Release N+1 will be unable to connect. + +## Test Cases + +| Scenario | Expected | +|----------|----------| +| Same network, same fork | Connection accepted | +| Different networks | Connection rejected | +| Pre/post fork during grace period | Connection accepted | +| Pre/post fork outside grace period | Connection rejected | +| Hive gossip with matching fork | Peer accepted | +| Hive gossip with mismatched fork | Peer ignored | +| BzzAddress with v0 signature, no fork_digest | Accepted (Release N) | +| BzzAddress with v1 signature, with fork_digest | Accepted | +| BzzAddress with v0 signature after Release N+1 | Rejected | + +## Implementation + +Reference: [vertex-swarm-forks](https://github.com/nxm-rs/vertex) + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). From bcbc4a07ba38a147191fab2a7b3b7e344ecc9be2 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Wed, 21 Jan 2026 11:33:02 +0000 Subject: [PATCH 2/3] docs(swip): use RFC 2119 keywords (MUST/SHOULD/MAY) --- SWIPs/swip-fork-digest.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SWIPs/swip-fork-digest.md b/SWIPs/swip-fork-digest.md index 0ed44b3..b303152 100644 --- a/SWIPs/swip-fork-digest.md +++ b/SWIPs/swip-fork-digest.md @@ -84,7 +84,7 @@ message Handshake { } ``` -Nodes must reject connections where `peer.fork_digest != local.fork_digest`. +Nodes MUST reject connections where `peer.fork_digest != local.fork_digest`. ### Hive Protocol Integration @@ -126,16 +126,16 @@ signature = eip191_sign(data) The `"bee-handshake-"` prefix is removed in v1 as EIP-191 personal sign already provides domain separation via `"\x19Ethereum Signed Message:\n"`. -During migration, nodes must support verifying both signature formats: +During migration, nodes MUST support verifying both signature formats: 1. If `fork_digest` is present, verify using the v1 scheme. 2. If `fork_digest` is absent, verify using the legacy v0 scheme. -Nodes should generate v1 signatures when creating new BzzAddress entries once fork digest support is enabled. +Nodes SHOULD generate v1 signatures when creating new BzzAddress entries once fork digest support is enabled. ### Grace Period -During fork transitions (a one-hour window around activation), nodes may accept both pre-fork and post-fork digests to accommodate clock skew. +During fork transitions (a one-hour window around activation), nodes MAY accept both pre-fork and post-fork digests to accommodate clock skew. ## Rationale From 8fbf07a6421508b95478e397f8cd476134d3cc2f Mon Sep 17 00:00:00 2001 From: mfw78 Date: Thu, 22 Jan 2026 09:45:33 +0000 Subject: [PATCH 3/3] feat: add BzzAddress underlay encoding changes Extend the fork digest SWIP to also clean up underlay encoding: - Change `bytes underlay` to `repeated bytes underlays` in protobuf - Remove custom 0x99 prefix and varint length encoding - Signature v1 uses simple concatenation of multiaddr bytes - Add rationale for why no additional framing is needed - Add test cases for signature verification and underlay encoding This separates wire encoding (protobuf) from signature construction (application logic) and simplifies implementations. --- SWIPs/swip-fork-digest.md | 128 +++++++++++++++++++++++++++++--------- 1 file changed, 97 insertions(+), 31 deletions(-) diff --git a/SWIPs/swip-fork-digest.md b/SWIPs/swip-fork-digest.md index b303152..3520635 100644 --- a/SWIPs/swip-fork-digest.md +++ b/SWIPs/swip-fork-digest.md @@ -1,6 +1,6 @@ --- swip: -title: Fork Digest for Network Upgrade Coordination +title: Fork Digest and BzzAddress Encoding author: mfw78 (@mfw78) status: Draft type: Standards Track @@ -10,12 +10,14 @@ created: 2026-01-21 ## Simple Summary -Introduce a fork digest mechanism that enables nodes to identify compatible peers during network upgrades via the handshake and Hive peer gossip. +Introduce a fork digest mechanism that enables nodes to identify compatible peers during network upgrades via the handshake and Hive peer gossip. Additionally, clean up the underlay encoding by using proper protobuf repeated fields instead of custom serialization. ## Abstract A fork digest is a 4-byte identifier derived from the network configuration and current fork version. By including the fork digest in both the handshake protocol and Hive peer advertisements, nodes can efficiently filter incompatible peers before connection and avoid gossiping peers that recipients cannot use. +This proposal also replaces the custom underlay serialization format (magic `0x99` prefix with varint-length encoding) with idiomatic protobuf `repeated` fields, simplifying implementations and improving interoperability. + ## Motivation Swarm lacks a standardised mechanism for: @@ -23,6 +25,7 @@ Swarm lacks a standardised mechanism for: 1. **Peer compatibility detection.** Nodes cannot determine protocol compatibility before establishing connections. 2. **Efficient peer gossip.** Hive broadcasts all known peers regardless of fork compatibility, wasting bandwidth. 3. **Graceful upgrades.** During network upgrades, incompatible nodes waste resources attempting failed connections. +4. **Clean underlay encoding.** The current protocol uses a custom serialization format for multiple underlay addresses: a magic `0x99` prefix byte followed by varint-length-prefixed multiaddr bytes. This deviates from idiomatic protobuf usage, complicates implementations, and conflates wire encoding with application logic. ## Specification @@ -70,36 +73,48 @@ fork_active = current_timestamp >= activation_timestamp ### Handshake Integration -Add `fork_digest` to the handshake message: +The handshake protocol is updated with fork digest and proper underlay encoding: ```protobuf -message Handshake { - bytes overlay_address = 1; - bytes underlay_address = 2; - bytes signature = 3; - bytes nonce = 4; - uint64 network_id = 5; - bool full_node = 6; - bytes fork_digest = 7; // 4 bytes -} -``` +syntax = "proto3"; -Nodes MUST reject connections where `peer.fork_digest != local.fork_digest`. +package handshake; -### Hive Protocol Integration +message Syn { + bytes observed_underlay = 1; +} -Include the fork digest in peer advertisements: +message Ack { + BzzAddress address = 1; + uint64 network_id = 2; + bool full_node = 3; + bytes nonce = 4; + string welcome_message = 99; +} + +message SynAck { + Syn syn = 1; + Ack ack = 2; +} -```protobuf message BzzAddress { - bytes underlay = 1; + repeated bytes underlays = 1; // Multiple multiaddr bytes (was: single bytes with custom encoding) bytes signature = 2; bytes overlay = 3; - bytes fork_digest = 4; // 4 bytes + bytes fork_digest = 4; // 4 bytes } ``` -Gossip filtering rules: +Key changes: + +1. **`underlays` becomes `repeated bytes`** - Each multiaddr is a separate element. No custom serialization (no `0x99` prefix, no varint length encoding). Protobuf handles the wire format. +2. **`fork_digest` is added** - 4-byte fork identifier. + +Nodes MUST reject connections where `peer.fork_digest != local.fork_digest`. + +### Hive Protocol Integration + +The Hive protocol uses the same `BzzAddress` message defined above. Gossip filtering rules: - When receiving peers, ignore those with an incompatible fork digest. - When sending peers, only advertise those matching the recipient's fork digest. @@ -113,23 +128,31 @@ The `BzzAddress.signature` field authenticates the address fields using EIP-191 **Legacy (v0):** ``` -data = "bee-handshake-" || underlay || overlay || network_id +serialized_underlay = custom_serialize(underlays) // 0x99 prefix + varint lengths, or raw for single +data = "bee-handshake-" || serialized_underlay || overlay || network_id signature = eip191_sign(data) ``` **Fork-aware (v1):** ``` -data = underlay || overlay || network_id || fork_digest +underlays_concat = underlays[0].bytes || underlays[1].bytes || ... || underlays[n].bytes +data = underlays_concat || overlay || network_id || fork_digest signature = eip191_sign(data) ``` -The `"bee-handshake-"` prefix is removed in v1 as EIP-191 personal sign already provides domain separation via `"\x19Ethereum Signed Message:\n"`. +Key changes in v1: + +1. **Simple concatenation for underlays.** The signature is computed over the concatenation of all underlay multiaddr bytes in order. No length prefixes, no magic bytes, no padding. The multiaddr self-describing format and the fixed-length `overlay` (32 bytes) provide implicit framing. + +2. **No "bee-handshake-" prefix.** EIP-191 personal sign already provides domain separation via `"\x19Ethereum Signed Message:\n"`. The legacy prefix was redundant. + +3. **Fork digest included.** Binds the signature to a specific fork, preventing replay on incompatible networks. During migration, nodes MUST support verifying both signature formats: 1. If `fork_digest` is present, verify using the v1 scheme. -2. If `fork_digest` is absent, verify using the legacy v0 scheme. +2. If `fork_digest` is absent, verify using the legacy v0 scheme (with custom underlay deserialization). Nodes SHOULD generate v1 signatures when creating new BzzAddress entries once fork digest support is enabled. @@ -149,21 +172,47 @@ During fork transitions (a one-hour window around activation), nodes MAY accept **Removing the "bee-handshake-" prefix.** The legacy prefix was redundant. EIP-191 personal sign already prefixes messages with `"\x19Ethereum Signed Message:\n"`, providing sufficient domain separation. Removing it simplifies the protocol without reducing security. +**Repeated bytes for underlays.** The legacy custom encoding (magic `0x99` prefix + varint length prefixes) was a workaround for backward compatibility with single-underlay nodes. This conflates wire encoding with application logic and complicates implementations. Using protobuf's native `repeated bytes` field: + +- Leverages protobuf's built-in length-prefixed encoding for wire format +- Simplifies parsing - no custom deserialization logic needed +- Improves interoperability - standard protobuf tooling works correctly +- Separates concerns - wire encoding is handled by protobuf, signature construction is application logic + +**Concatenation for signature.** The signature is over the simple concatenation of multiaddr bytes. No additional framing is required because: + +- Multiaddrs are self-describing (each component includes its protocol code and length) +- The overlay address is fixed-length (32 bytes) +- EIP-191 includes the total message length, providing overall framing +- The fork digest is fixed-length (4 bytes) + +This provides sufficient domain separation without introducing complexity. + ## Backwards Compatibility -This proposal introduces a breaking change to the handshake and Hive protocols. +This proposal introduces breaking changes to the handshake and Hive protocols: + +1. **Fork digest field** - New required field in BzzAddress +2. **Underlay encoding** - Changes from `bytes underlay` (custom encoding) to `repeated bytes underlays` (native protobuf) +3. **Signature scheme** - Changes from v0 (with prefix and custom underlay) to v1 (no prefix, concatenated underlays, fork digest) Migration: -1. **Release N.** Fork digest is optional. BzzAddress accepts both v0 and v1 signatures. Nodes generate v1 signatures. -2. **Release N+1.** Fork digest is required. v0 signatures are rejected. +1. **Release N.** Fork digest is optional. BzzAddress accepts both: + - Legacy format: `bytes underlay` with custom encoding, v0 signature + - New format: `repeated bytes underlays`, v1 signature with fork digest + Nodes generate the new format but verify both. + +2. **Release N+1.** Only the new format is accepted. Legacy format is rejected. -Once Release N is deployed, fork digests will propagate through Hive gossip as nodes exchange peer information. By the time Release N+1 is deployed, the network should be predominantly fork-aware, easing the transition to mandatory fork digest. +Once Release N is deployed, the new format will propagate through Hive gossip as nodes exchange peer information. By the time Release N+1 is deployed, the network should be predominantly using the new format. Nodes that have not upgraded by Release N+1 will be unable to connect. ## Test Cases +### Fork Digest + | Scenario | Expected | |----------|----------| | Same network, same fork | Connection accepted | @@ -172,9 +221,26 @@ Nodes that have not upgraded by Release N+1 will be unable to connect. | Pre/post fork outside grace period | Connection rejected | | Hive gossip with matching fork | Peer accepted | | Hive gossip with mismatched fork | Peer ignored | -| BzzAddress with v0 signature, no fork_digest | Accepted (Release N) | -| BzzAddress with v1 signature, with fork_digest | Accepted | -| BzzAddress with v0 signature after Release N+1 | Rejected | + +### Signature Verification + +| Scenario | Expected | +|----------|----------| +| v0 signature (legacy underlay encoding, no fork_digest) | Accepted (Release N only) | +| v1 signature (repeated underlays, with fork_digest) | Accepted | +| v0 signature after Release N+1 | Rejected | +| v1 signature with wrong fork_digest | Rejected | +| v1 signature with underlays in different order | Rejected (signature mismatch) | + +### Underlay Encoding + +| Scenario | Expected | +|----------|----------| +| Single underlay in repeated field | Valid | +| Multiple underlays in repeated field | Valid | +| Empty underlays (zero elements) | Rejected | +| Legacy 0x99-prefixed encoding in bytes field | Accepted (Release N only) | +| Raw multiaddr in bytes field (single underlay) | Accepted (Release N only) | ## Implementation