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}