Skip to main content

nectar_primitives/
overlay.rs

1//! Canonical overlay address derivation.
2//!
3//! The Swarm overlay address is `keccak256(ethereum_address || network_id || nonce)`
4//! per bee `pkg/crypto/crypto.go:45-57`. Network ID is encoded in
5//! **little-endian** in this hash (distinct from the big-endian encoding used
6//! in the BzzAddress sign-data, see [`crate::signing::sign_data`]).
7
8use alloy_primitives::{Address, Keccak256};
9
10use crate::{NetworkId, Nonce, SwarmAddress};
11
12/// Compute the Swarm overlay address from `eth_addr`, `network_id`, `nonce`.
13///
14/// Layout: `keccak256(eth_addr(20) || network_id_le(8) || nonce(32))`.
15///
16/// This is the **only** canonical derivation - any other formula creates a
17/// peer with a different overlay that won't be reachable on the same network.
18#[must_use]
19pub fn compute_overlay(eth_addr: &Address, network_id: NetworkId, nonce: &Nonce) -> SwarmAddress {
20    let mut hasher = Keccak256::new();
21    hasher.update(eth_addr.as_slice());
22    hasher.update(network_id.to_le_bytes());
23    hasher.update(nonce.as_slice());
24    SwarmAddress::from(hasher.finalize())
25}
26
27#[cfg(test)]
28mod tests {
29    use super::*;
30    use alloy_primitives::{address, b256};
31
32    #[test]
33    fn deterministic_zero_nonce_zero_addr() {
34        let eth = Address::ZERO;
35        let nonce = Nonce::ZERO;
36        let net = NetworkId::MAINNET;
37        let overlay = compute_overlay(&eth, net, &nonce);
38        // Reproduce the exact bytes: keccak256(20 zeros || 1u64.to_le_bytes() || 32 zeros).
39        let mut h = Keccak256::new();
40        h.update([0u8; 20]);
41        h.update(1u64.to_le_bytes());
42        h.update([0u8; 32]);
43        let expected = SwarmAddress::from(h.finalize());
44        assert_eq!(overlay, expected);
45    }
46
47    #[test]
48    fn different_network_yields_different_overlay() {
49        let eth = address!("0102030405060708091011121314151617181920");
50        let nonce = Nonce::new([0x55; 32]);
51        let m = compute_overlay(&eth, NetworkId::MAINNET, &nonce);
52        let t = compute_overlay(&eth, NetworkId::TESTNET, &nonce);
53        assert_ne!(m, t);
54    }
55
56    #[test]
57    fn different_nonce_yields_different_overlay() {
58        let eth = address!("0102030405060708091011121314151617181920");
59        let a = compute_overlay(&eth, NetworkId::MAINNET, &Nonce::new([0; 32]));
60        let b = compute_overlay(&eth, NetworkId::MAINNET, &Nonce::new([1; 32]));
61        assert_ne!(a, b);
62    }
63
64    #[test]
65    fn network_id_is_little_endian() {
66        // If the implementation accidentally used big-endian, this would change.
67        let eth = address!("aabbccddeeff00112233445566778899aabbccdd");
68        let nonce = Nonce::new([0x42; 32]);
69        let net = NetworkId::new(0x0102_0304_0506_0708);
70        let overlay = compute_overlay(&eth, net, &nonce);
71
72        let mut h = Keccak256::new();
73        h.update(eth.as_slice());
74        h.update([0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]); // LE bytes
75        h.update(nonce.as_slice());
76        assert_eq!(overlay, SwarmAddress::from(h.finalize()));
77
78        // Sanity-check the BE encoding would have produced a different overlay.
79        let mut h_be = Keccak256::new();
80        h_be.update(eth.as_slice());
81        h_be.update([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); // BE bytes
82        h_be.update(nonce.as_slice());
83        let be_overlay = SwarmAddress::from(h_be.finalize());
84        assert_ne!(overlay, be_overlay);
85        let _ = b256!("0000000000000000000000000000000000000000000000000000000000000000");
86    }
87}