Skip to main content

dig_dht/
key.rs

1//! The 256-bit Kademlia keyspace: [`Key`], XOR [`Distance`], and the maps from a [`PeerId`] and a
2//! [`ContentId`](crate::ContentId) into it.
3//!
4//! Kademlia organizes both **nodes** and **content** in a single 256-bit metric space and measures
5//! closeness by the **XOR** of two keys interpreted as a big-endian integer (Maymounkov & Mazières,
6//! 2002). This module is the pure, network-free core of that metric:
7//!
8//! - A **node**'s key is its `peer_id` verbatim — `peer_id = SHA-256(TLS SPKI DER)` is already a
9//!   uniform 256-bit value ([`Key::from_peer_id`]), so the DHT node id IS the peer id.
10//! - A **content** key is the SHA-256 of the content id's canonical bytes
11//!   ([`ContentId::to_key`](crate::ContentId::to_key)),
12//!   which lands DIG content (store / capsule / root / resource) in the same 256-bit space so
13//!   `find_node(key)` and `find_providers(content_key)` share one lookup.
14//!
15//! `Distance` is XOR; the routing table's **bucket index** for a key is `255 - leading_zeros` of the
16//! distance from this node — the standard Kademlia longest-common-prefix bucketing.
17
18use std::cmp::Ordering;
19
20use dig_nat::PeerId;
21
22/// A point in the 256-bit Kademlia keyspace — 32 big-endian bytes.
23///
24/// Both node ids (a peer's `peer_id`) and content keys live here; closeness is the XOR
25/// [`Distance`]. A `Key` is `Ord` by its raw big-endian byte value (NOT a distance — distances are
26/// only meaningful relative to a reference key), so it can be used as a stable map key.
27#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
28pub struct Key([u8; 32]);
29
30impl Key {
31    /// Construct from raw 32 bytes.
32    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
33        Key(bytes)
34    }
35
36    /// The raw 32 bytes.
37    pub const fn as_bytes(&self) -> &[u8; 32] {
38        &self.0
39    }
40
41    /// A node's key is its `peer_id` verbatim — both are a uniform SHA-256 256-bit value, so the
42    /// DHT node id and the peer id are one and the same.
43    pub fn from_peer_id(peer_id: &PeerId) -> Self {
44        Key(*peer_id.as_bytes())
45    }
46
47    /// The XOR distance between this key and `other` (symmetric: `a.distance(b) == b.distance(a)`).
48    pub fn distance(&self, other: &Key) -> Distance {
49        let mut out = [0u8; 32];
50        for (i, o) in out.iter_mut().enumerate() {
51            *o = self.0[i] ^ other.0[i];
52        }
53        Distance(out)
54    }
55
56    /// Lowercase hex (64 chars) — the canonical text rendering (matches `peer_id` hex).
57    pub fn to_hex(&self) -> String {
58        to_hex(&self.0)
59    }
60}
61
62impl std::fmt::Debug for Key {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        write!(f, "Key({})", self.to_hex())
65    }
66}
67
68/// The XOR distance between two [`Key`]s — a point in the same 256-bit space, ordered as a
69/// big-endian unsigned integer (smaller = closer).
70///
71/// Kademlia's metric: `d(a,b) = a XOR b`, and `d` is a valid metric (identity, symmetry, triangle
72/// inequality) which is what makes iterative lookups converge. [`Ord`] compares big-endian so
73/// `Distance::ZERO` (a key to itself) is the minimum.
74#[derive(Clone, Copy, PartialEq, Eq, Hash)]
75pub struct Distance([u8; 32]);
76
77impl Distance {
78    /// The zero distance (a key to itself) — the minimum possible distance.
79    pub const ZERO: Distance = Distance([0u8; 32]);
80
81    /// The raw 32 bytes of the distance.
82    pub const fn as_bytes(&self) -> &[u8; 32] {
83        &self.0
84    }
85
86    /// The Kademlia **bucket index** for this distance: `255 - leading_zero_bits`, i.e. the position
87    /// of the most-significant set bit. Returns `None` for [`Distance::ZERO`] (a node never buckets
88    /// itself). A larger index means a more-distant key (shares a shorter prefix with the reference).
89    ///
90    /// This is exactly the index into a 256-bucket routing table keyed by the length of the shared
91    /// prefix between this node's id and the other key.
92    pub fn bucket_index(&self) -> Option<usize> {
93        for (i, byte) in self.0.iter().enumerate() {
94            if *byte != 0 {
95                let bit_in_byte = byte.leading_zeros() as usize; // 0..=7
96                let leading_zero_bits = i * 8 + bit_in_byte;
97                return Some(255 - leading_zero_bits);
98            }
99        }
100        None
101    }
102
103    /// Lowercase hex (64 chars).
104    pub fn to_hex(&self) -> String {
105        to_hex(&self.0)
106    }
107}
108
109impl PartialOrd for Distance {
110    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
111        Some(self.cmp(other))
112    }
113}
114
115impl Ord for Distance {
116    /// Big-endian unsigned-integer comparison — byte 0 is most significant.
117    fn cmp(&self, other: &Self) -> Ordering {
118        self.0.cmp(&other.0)
119    }
120}
121
122impl std::fmt::Debug for Distance {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        write!(f, "Distance({})", self.to_hex())
125    }
126}
127
128/// Lowercase hex of a 32-byte array (shared by [`Key`] + [`Distance`]).
129fn to_hex(bytes: &[u8; 32]) -> String {
130    let mut s = String::with_capacity(64);
131    for b in bytes {
132        s.push(char::from_digit((b >> 4) as u32, 16).unwrap());
133        s.push(char::from_digit((b & 0x0f) as u32, 16).unwrap());
134    }
135    s
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    fn key(byte0: u8, rest: u8) -> Key {
143        let mut b = [rest; 32];
144        b[0] = byte0;
145        Key::from_bytes(b)
146    }
147
148    #[test]
149    fn xor_distance_is_symmetric() {
150        let a = key(0x12, 0x34);
151        let b = key(0xAB, 0xCD);
152        assert_eq!(a.distance(&b), b.distance(&a));
153    }
154
155    #[test]
156    fn distance_to_self_is_zero() {
157        let a = key(0x99, 0x01);
158        assert_eq!(a.distance(&a), Distance::ZERO);
159        assert_eq!(a.distance(&a).bucket_index(), None);
160    }
161
162    #[test]
163    fn xor_is_the_distance() {
164        let a = Key::from_bytes([0x00; 32]);
165        let mut bb = [0u8; 32];
166        bb[31] = 0x01;
167        let b = Key::from_bytes(bb);
168        // distance(0, 1) == 1
169        assert_eq!(a.distance(&b).as_bytes()[31], 0x01);
170        assert_eq!(a.distance(&b).bucket_index(), Some(0));
171    }
172
173    #[test]
174    fn bucket_index_tracks_most_significant_set_bit() {
175        let zero = Key::from_bytes([0u8; 32]);
176
177        // Distance with MSB set in byte 0 → shares no prefix → bucket 255.
178        let mut hi = [0u8; 32];
179        hi[0] = 0x80;
180        assert_eq!(
181            zero.distance(&Key::from_bytes(hi)).bucket_index(),
182            Some(255)
183        );
184
185        // Distance == 1 (LSB) → longest shared prefix → bucket 0.
186        let mut lo = [0u8; 32];
187        lo[31] = 0x01;
188        assert_eq!(zero.distance(&Key::from_bytes(lo)).bucket_index(), Some(0));
189
190        // 0x40 in byte 0 → one leading zero bit → bucket 254.
191        let mut b = [0u8; 32];
192        b[0] = 0x40;
193        assert_eq!(zero.distance(&Key::from_bytes(b)).bucket_index(), Some(254));
194
195        // 0x01 in byte 0 → 7 leading zero bits → bucket 248.
196        let mut c = [0u8; 32];
197        c[0] = 0x01;
198        assert_eq!(zero.distance(&Key::from_bytes(c)).bucket_index(), Some(248));
199    }
200
201    #[test]
202    fn closer_keys_have_smaller_distance() {
203        let target = Key::from_bytes([0u8; 32]);
204        let near = key(0x00, 0x01); // differs only in low bytes
205        let far = key(0xFF, 0x00); // differs in the top byte
206        assert!(target.distance(&near) < target.distance(&far));
207    }
208
209    #[test]
210    fn from_peer_id_is_verbatim() {
211        let mut raw = [0u8; 32];
212        raw[0] = 0xDE;
213        raw[31] = 0xAD;
214        let pid = PeerId::from_bytes(raw);
215        assert_eq!(Key::from_peer_id(&pid).as_bytes(), &raw);
216    }
217
218    #[test]
219    fn hex_round_trips_length() {
220        let k = key(0x0a, 0xf0);
221        assert_eq!(k.to_hex().len(), 64);
222        assert!(k.to_hex().starts_with("0a"));
223    }
224
225    #[test]
226    fn distance_ordering_is_big_endian() {
227        // A difference high up dominates a difference low down.
228        let mut a = [0u8; 32];
229        a[0] = 0x01;
230        let mut b = [0u8; 32];
231        b[31] = 0xFF;
232        assert!(Distance(b) < Distance(a));
233    }
234}