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}