Skip to main content

nectar_primitives/
address.rs

1//! Swarm address implementation
2//!
3//! This module provides the SwarmAddress type, which is a 32-byte identifier
4//! used for addressing chunks in the swarm network. It includes functionality
5//! for calculating distances between addresses and determining their proximity.
6//!
7//! ## Proximity Order (PO)
8//!
9//! Proximity order is a key concept in Kademlia-based routing. It measures
10//! how "close" two addresses are in the XOR metric space by counting the
11//! number of leading matching bits.
12//!
13//! ### Standard vs Extended Proximity
14//!
15//! - **Standard PO** (`MAX_PO = 31`): Used for most routing operations.
16//!   Returns a value 0-31, giving 32 Kademlia bins. The algorithm counts
17//!   leading matching bits up to 32 bits (4 bytes) and caps at 31.
18//!
19//! - **Extended PO** (`EXTENDED_PO = 36`): Used for Kademlia bin balancing.
20//!   When balancing bins, the algorithm needs finer granularity:
21//!   `po + BitSuffixLength + 1` where `BitSuffixLength = 4`.
22//!   For bin 31, this yields 31 + 4 + 1 = 36, hence `ExtendedPO = MaxPO + 5`.
23//!
24//! ### Compatibility
25//!
26//! This implementation matches the Swarm reference implementation exactly:
27//!
28//! - `MaxPO = 31` and `ExtendedPO = 36`
29//! - Counts leading matching BITS (not bytes)
30//! - Caps at the respective maximum values
31//!
32//! ### Distance vs Proximity
33//!
34//! - **Distance** (`distance()`): Returns the full 256-bit XOR distance as `U256`.
35//! - **Proximity** (`proximity()`): Returns a small integer (0-31) representing
36//!   the count of leading matching bits, capped at `MAX_PO`.
37//!
38//! Higher proximity = closer addresses = smaller XOR distance.
39//!
40//! ## Example Usage
41//!
42//! ```
43//! use nectar_primitives::SwarmAddress;
44//! use alloy_primitives::B256;
45//!
46//! // Create addresses
47//! let addr1 = SwarmAddress::from(B256::random());
48//! let addr2 = SwarmAddress::from(B256::random());
49//!
50//! // Calculate proximity
51//! let po = addr1.proximity(&addr2);
52//! println!("Proximity order: {}", po);
53//!
54//! // Calculate distance
55//! let distance = addr1.distance(&addr2);
56//! println!("Distance: {}", distance);
57//!
58//! // Compare distances
59//! let addr3 = SwarmAddress::from(B256::random());
60//! if addr1.closer(&addr2, &addr3) {
61//!     println!("addr1 is closer to addr2 than addr3");
62//! }
63//! ```
64//!
65//! [`extended_proximity()`]: SwarmAddress::extended_proximity
66
67use std::cmp::Ordering;
68use std::fmt;
69use std::ops::Deref;
70
71use alloy_primitives::{B256, U256};
72
73#[cfg(feature = "serde")]
74use serde::{Deserialize, Serialize};
75
76use crate::error::Result;
77use crate::{Bin, ProximityOrder};
78
79/// Maximum proximity order for standard routing operations.
80///
81/// Value 31 gives 32 Kademlia bins (0-31).
82pub const MAX_PO: u8 = 31;
83
84/// Extended proximity order for Kademlia bin balancing.
85///
86/// Value 36 = MaxPO (31) + BitSuffixLength (4) + 1. Used when the Kademlia
87/// bin balancing algorithm needs to check proximity at finer granularity
88/// than standard routing.
89pub const EXTENDED_PO: u8 = MAX_PO + 5;
90
91/// A 256-bit address for a chunk in the Swarm network
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
93#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
94#[cfg_attr(feature = "serde", serde(transparent))]
95pub struct SwarmAddress(pub B256);
96
97impl SwarmAddress {
98    /// Creates an address with only the first byte set, rest zeros.
99    ///
100    /// The first byte controls proximity order (leading bits determine PO).
101    pub const fn with_first_byte(byte: u8) -> Self {
102        let mut bytes = [0u8; 32];
103        bytes[0] = byte;
104        Self(B256::new(bytes))
105    }
106
107    /// Creates a new SwarmAddress from raw bytes
108    pub fn new(bytes: [u8; 32]) -> Self {
109        Self(B256::from(bytes))
110    }
111
112    /// Returns the underlying bytes
113    pub const fn as_bytes(&self) -> &[u8] {
114        self.0.as_slice()
115    }
116
117    /// Creates a new address from a slice, checking the length
118    pub fn from_slice(slice: &[u8]) -> Result<Self> {
119        let address = B256::try_from(slice)?;
120        Ok(Self(address))
121    }
122
123    /// Checks if this address is zeros
124    pub fn is_zero(&self) -> bool {
125        self.0.is_zero()
126    }
127
128    /// Create a new zero-filled address
129    pub const fn zero() -> Self {
130        Self(B256::ZERO)
131    }
132
133    /// Calculate the distance between Self and address `y` in big-endian
134    #[inline(always)]
135    #[must_use]
136    pub fn distance(&self, y: &Self) -> U256 {
137        let mut result = [0u8; 32];
138
139        for (i, (&a, &b)) in self
140            .0
141            .as_slice()
142            .iter()
143            .zip(y.0.as_slice().iter())
144            .enumerate()
145        {
146            result[i] = a ^ b;
147        }
148
149        U256::from_be_bytes(result)
150    }
151
152    /// Compares addresses `x` and `y` by their distance from `self`.
153    ///
154    /// Returns:
155    /// - `Ordering::Less` if `x` is farther from `self` than `y` (i.e., `y` is closer)
156    /// - `Ordering::Greater` if `x` is closer to `self` than `y`
157    /// - `Ordering::Equal` if `x` and `y` are equidistant from `self`
158    ///
159    /// # Usage with `min_by`
160    ///
161    /// This comparator is designed for use with `Iterator::min_by` to find
162    /// the address closest to `self`:
163    ///
164    /// ```
165    /// # use nectar_primitives::SwarmAddress;
166    /// # use alloy_primitives::B256;
167    /// let target = SwarmAddress::zero();
168    /// let addresses = vec![
169    ///     SwarmAddress::from(B256::repeat_byte(0x01)),
170    ///     SwarmAddress::from(B256::repeat_byte(0x02)),
171    /// ];
172    /// let closest = addresses.iter().min_by(|a, b| target.distance_cmp(a, b));
173    /// ```
174    ///
175    /// Note: The ordering may seem inverted from intuition. `Greater` means `x`
176    /// is closer (smaller distance), because `min_by` selects the element for
177    /// which the comparator returns `Less` - and we want to select the one
178    /// that is NOT closer (i.e., has a larger distance), leaving the closest.
179    #[inline(always)]
180    #[must_use]
181    pub fn distance_cmp(&self, x: &Self, y: &Self) -> Ordering {
182        let (ab, xb, yb) = (self.0.as_slice(), x.0.as_slice(), y.0.as_slice());
183
184        for i in 0..ab.len() {
185            let dx = xb[i] ^ ab[i];
186            let dy = yb[i] ^ ab[i];
187
188            if dx != dy {
189                return match dx < dy {
190                    true => Ordering::Greater,
191                    false => Ordering::Less,
192                };
193            }
194        }
195
196        Ordering::Equal
197    }
198
199    /// Determine if `self` is closer to `x` than to `y`.
200    ///
201    /// Returns `true` if `distance(self, x) < distance(self, y)`.
202    #[must_use]
203    pub fn closer(&self, x: &Self, y: &Self) -> bool {
204        // distance_cmp returns Greater when x is closer to self
205        self.distance_cmp(x, y) == Ordering::Greater
206    }
207
208    /// Check if this address is within the given proximity to another address
209    pub fn is_within_proximity(&self, other: &Self, min_proximity: ProximityOrder) -> bool {
210        self.proximity(other) >= min_proximity
211    }
212
213    /// Calculate the proximity order between self and another address.
214    ///
215    /// Returns the number of leading bits that match between the two addresses,
216    /// capped at `MAX_PO` (31). Use this for standard Kademlia routing operations.
217    ///
218    /// For operations requiring finer granularity (like reserve sampling),
219    /// use [`extended_proximity()`](Self::extended_proximity) instead.
220    #[inline(always)]
221    #[must_use]
222    pub fn proximity(&self, other: &Self) -> ProximityOrder {
223        // `proximity_helper` is bounded by MAX_PO, so the cast is sound.
224        ProximityOrder::new_unchecked(self.proximity_helper(other, MAX_PO.into()))
225    }
226
227    /// Calculate the extended proximity order between self and another address.
228    ///
229    /// Returns the number of leading bits that match between the two addresses,
230    /// capped at `EXTENDED_PO` (36). Use this for Kademlia bin balancing where
231    /// the algorithm checks `po + BitSuffixLength + 1` (up to 36 for bin 31).
232    ///
233    /// Returns a raw `u8` because the extended range exceeds `ProximityOrder`'s
234    /// invariant (`0..=MAX_PO`). For standard routing, use
235    /// [`proximity()`](Self::proximity) instead.
236    #[inline(always)]
237    #[must_use]
238    pub fn extended_proximity(&self, other: &Self) -> u8 {
239        self.proximity_helper(other, EXTENDED_PO.into())
240    }
241
242    /// XOR distance - bitwise XOR of the two 32-byte addresses as a new
243    /// [`SwarmAddress`]. Useful when callers want the raw distance bytes
244    /// (e.g. for content-routing bias) rather than the proximity-order metric.
245    #[inline(always)]
246    #[must_use]
247    pub fn xor(&self, other: &Self) -> Self {
248        let mut out = [0u8; 32];
249        for (i, (a, b)) in self.0.as_slice().iter().zip(other.0.as_slice()).enumerate() {
250            out[i] = a ^ b;
251        }
252        Self(B256::from(out))
253    }
254
255    /// Kademlia bin index of `self` relative to `anchor` - semantic alias for
256    /// `Bin::from(self.proximity(anchor))`. The routing-table convention is
257    /// "the bin a peer occupies is its proximity order to our own overlay".
258    #[inline(always)]
259    #[must_use]
260    pub fn bin(&self, anchor: &Self) -> Bin {
261        Bin::from(self.proximity(anchor))
262    }
263
264    /// Helper function to calculate proximity with a maximum
265    #[inline(always)]
266    fn proximity_helper(&self, other: &Self, max: usize) -> u8 {
267        let max_bytes = max / 8;
268        let max_bits = max as u8;
269
270        let bytes1 = self.0.as_slice();
271        let bytes2 = other.0.as_slice();
272
273        for i in 0..=max_bytes {
274            let xor = bytes1[i] ^ bytes2[i];
275            if xor != 0 {
276                // Found a difference - use leading_zeros to count matching bits
277                let leading_zeros = xor.leading_zeros() as u8;
278                let proximity = (i as u8 * 8) + leading_zeros;
279
280                // Return the smaller of proximity or max_bits
281                return if proximity < max_bits {
282                    proximity
283                } else {
284                    max_bits
285                };
286            }
287
288            // If we're at the last byte we might need to check
289            if i == max_bytes {
290                return max_bits; // All bits match up to max
291            }
292        }
293
294        // If we've examined all bytes and found no differences
295        max_bits
296    }
297}
298
299impl Default for SwarmAddress {
300    fn default() -> Self {
301        Self(B256::ZERO)
302    }
303}
304
305impl fmt::Display for SwarmAddress {
306    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307        write!(f, "{}", self.0)
308    }
309}
310
311impl Deref for SwarmAddress {
312    type Target = B256;
313
314    fn deref(&self) -> &Self::Target {
315        &self.0
316    }
317}
318
319impl From<B256> for SwarmAddress {
320    fn from(value: B256) -> Self {
321        Self(value)
322    }
323}
324
325impl From<[u8; 32]> for SwarmAddress {
326    fn from(bytes: [u8; 32]) -> Self {
327        Self::new(bytes)
328    }
329}
330
331impl From<SwarmAddress> for B256 {
332    fn from(addr: SwarmAddress) -> Self {
333        addr.0
334    }
335}
336
337impl AsRef<[u8]> for SwarmAddress {
338    fn as_ref(&self) -> &[u8] {
339        self.as_bytes()
340    }
341}
342
343impl From<SwarmAddress> for [u8; 32] {
344    fn from(addr: SwarmAddress) -> Self {
345        addr.0.into()
346    }
347}
348
349#[cfg(feature = "arbitrary")]
350impl<'a> arbitrary::Arbitrary<'a> for SwarmAddress {
351    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
352        Ok(Self(B256::arbitrary(u)?))
353    }
354}