Skip to main content

irontide_core/
hash.rs

1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::Error;
6
7/// 20-byte identifier used for SHA1 info-hashes and peer IDs.
8#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
9pub struct Id20(pub [u8; 20]);
10
11/// 32-byte identifier used for SHA-256 (`BitTorrent` v2).
12#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
13pub struct Id32(pub [u8; 32]);
14
15// ---- Id20 ----
16
17impl Id20 {
18    /// All zeros.
19    pub const ZERO: Self = Self([0u8; 20]);
20
21    /// Create from a hex string (40 chars).
22    ///
23    /// # Errors
24    ///
25    /// Returns an error if the string is not valid hex or has the wrong length.
26    pub fn from_hex(s: &str) -> Result<Self, Error> {
27        let bytes = hex::decode(s).map_err(|e| Error::InvalidHex(e.to_string()))?;
28        Self::from_bytes(&bytes)
29    }
30
31    /// Create from a base32 string (32 chars, used in magnet links).
32    ///
33    /// # Errors
34    ///
35    /// Returns an error if the string is not valid base32 or has the wrong length.
36    pub fn from_base32(s: &str) -> Result<Self, Error> {
37        let bytes = data_encoding::BASE32_NOPAD
38            .decode(s.as_bytes())
39            .map_err(|e| Error::InvalidHex(format!("base32: {e}")))?;
40        Self::from_bytes(&bytes)
41    }
42
43    /// Create from a byte slice, validating length.
44    ///
45    /// # Errors
46    ///
47    /// Returns an error if the byte slice is not exactly 20 bytes.
48    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
49        let arr: [u8; 20] = bytes.try_into().map_err(|_| Error::InvalidHashLength {
50            expected: 20,
51            got: bytes.len(),
52        })?;
53        Ok(Self(arr))
54    }
55
56    /// Encode as lowercase hex string.
57    #[must_use]
58    pub fn to_hex(&self) -> String {
59        hex::encode(self.0)
60    }
61
62    /// Encode as base32 string (no padding), used in magnet links.
63    #[must_use]
64    pub fn to_base32(&self) -> String {
65        data_encoding::BASE32_NOPAD.encode(&self.0)
66    }
67
68    /// XOR distance to another Id20 (used in DHT/Kademlia).
69    #[must_use]
70    pub fn xor_distance(&self, other: &Self) -> Self {
71        let mut result = [0u8; 20];
72        for (i, byte) in result.iter_mut().enumerate() {
73            *byte = self.0[i] ^ other.0[i];
74        }
75        Self(result)
76    }
77
78    /// Return the raw bytes.
79    #[must_use]
80    pub fn as_bytes(&self) -> &[u8; 20] {
81        &self.0
82    }
83}
84
85impl fmt::Debug for Id20 {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        write!(f, "Id20({})", self.to_hex())
88    }
89}
90
91impl fmt::Display for Id20 {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(f, "{}", self.to_hex())
94    }
95}
96
97impl AsRef<[u8]> for Id20 {
98    fn as_ref(&self) -> &[u8] {
99        &self.0
100    }
101}
102
103impl From<[u8; 20]> for Id20 {
104    fn from(arr: [u8; 20]) -> Self {
105        Self(arr)
106    }
107}
108
109impl Serialize for Id20 {
110    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
111        serializer.serialize_bytes(&self.0)
112    }
113}
114
115impl<'de> Deserialize<'de> for Id20 {
116    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
117        let bytes: Vec<u8> = serde_bytes::deserialize(deserializer)?;
118        Self::from_bytes(&bytes).map_err(serde::de::Error::custom)
119    }
120}
121
122// ---- Id32 ----
123
124impl Id32 {
125    /// All zeros.
126    pub const ZERO: Self = Self([0u8; 32]);
127
128    /// BEP 52 multihash prefix: SHA-256 function code (0x12) + digest length (0x20 = 32).
129    const MULTIHASH_PREFIX: [u8; 2] = [0x12, 0x20];
130
131    /// Create from a hex string (64 chars).
132    ///
133    /// # Errors
134    ///
135    /// Returns an error if the string is not valid hex or has the wrong length.
136    pub fn from_hex(s: &str) -> Result<Self, Error> {
137        let bytes = hex::decode(s).map_err(|e| Error::InvalidHex(e.to_string()))?;
138        Self::from_bytes(&bytes)
139    }
140
141    /// Create from a base32 string (used in v2 magnet links).
142    ///
143    /// # Errors
144    ///
145    /// Returns an error if the string is not valid base32 or has the wrong length.
146    pub fn from_base32(s: &str) -> Result<Self, Error> {
147        let bytes = data_encoding::BASE32_NOPAD
148            .decode(s.as_bytes())
149            .map_err(|e| Error::InvalidHex(format!("base32: {e}")))?;
150        Self::from_bytes(&bytes)
151    }
152
153    /// Create from a byte slice, validating length.
154    ///
155    /// # Errors
156    ///
157    /// Returns an error if the byte slice is not exactly 32 bytes.
158    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
159        let arr: [u8; 32] = bytes.try_into().map_err(|_| Error::InvalidHashLength {
160            expected: 32,
161            got: bytes.len(),
162        })?;
163        Ok(Self(arr))
164    }
165
166    /// Encode as lowercase hex string.
167    #[must_use]
168    pub fn to_hex(&self) -> String {
169        hex::encode(self.0)
170    }
171
172    /// Encode as base32 string (no padding), used in v2 magnet links.
173    #[must_use]
174    pub fn to_base32(&self) -> String {
175        data_encoding::BASE32_NOPAD.encode(&self.0)
176    }
177
178    /// Encode as multihash hex string for BEP 52 magnet URIs (`urn:btmh:`).
179    ///
180    /// Format: `1220` prefix (SHA-256 function code + 32-byte length) + 64 hex chars.
181    #[must_use]
182    pub fn to_multihash_hex(&self) -> String {
183        let mut buf = Vec::with_capacity(34);
184        buf.extend_from_slice(&Self::MULTIHASH_PREFIX);
185        buf.extend_from_slice(&self.0);
186        hex::encode(buf)
187    }
188
189    /// Decode from a multihash hex string (BEP 52 magnet format).
190    ///
191    /// Validates the `1220` prefix (SHA-256 function code + 32-byte length).
192    ///
193    /// # Errors
194    ///
195    /// Returns an error if the string is not valid hex or lacks the correct multihash prefix.
196    pub fn from_multihash_hex(s: &str) -> Result<Self, Error> {
197        let bytes = hex::decode(s).map_err(|e| Error::InvalidHex(e.to_string()))?;
198        if bytes.len() != 34 {
199            return Err(Error::InvalidHashLength {
200                expected: 34,
201                got: bytes.len(),
202            });
203        }
204        if bytes[0] != Self::MULTIHASH_PREFIX[0] || bytes[1] != Self::MULTIHASH_PREFIX[1] {
205            return Err(Error::InvalidHex(format!(
206                "invalid multihash prefix: expected 1220, got {:02x}{:02x}",
207                bytes[0], bytes[1]
208            )));
209        }
210        Self::from_bytes(&bytes[2..])
211    }
212
213    /// Return the raw bytes.
214    #[must_use]
215    pub fn as_bytes(&self) -> &[u8; 32] {
216        &self.0
217    }
218}
219
220impl fmt::Debug for Id32 {
221    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222        write!(f, "Id32({})", self.to_hex())
223    }
224}
225
226impl fmt::Display for Id32 {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        write!(f, "{}", self.to_hex())
229    }
230}
231
232impl AsRef<[u8]> for Id32 {
233    fn as_ref(&self) -> &[u8] {
234        &self.0
235    }
236}
237
238impl From<[u8; 32]> for Id32 {
239    fn from(arr: [u8; 32]) -> Self {
240        Self(arr)
241    }
242}
243
244impl Serialize for Id32 {
245    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
246        serializer.serialize_bytes(&self.0)
247    }
248}
249
250impl<'de> Deserialize<'de> for Id32 {
251    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
252        let bytes: Vec<u8> = serde_bytes::deserialize(deserializer)?;
253        Self::from_bytes(&bytes).map_err(serde::de::Error::custom)
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260
261    #[test]
262    fn id20_hex_round_trip() {
263        let hex_str = "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d";
264        let id = Id20::from_hex(hex_str).unwrap();
265        assert_eq!(id.to_hex(), hex_str);
266    }
267
268    #[test]
269    fn id20_base32_round_trip() {
270        let id = Id20::from_hex("aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d").unwrap();
271        let b32 = id.to_base32();
272        let id2 = Id20::from_base32(&b32).unwrap();
273        assert_eq!(id, id2);
274    }
275
276    #[test]
277    fn id20_xor_distance() {
278        let a = Id20::from_hex("0000000000000000000000000000000000000001").unwrap();
279        let b = Id20::from_hex("0000000000000000000000000000000000000003").unwrap();
280        let dist = a.xor_distance(&b);
281        assert_eq!(
282            dist,
283            Id20::from_hex("0000000000000000000000000000000000000002").unwrap()
284        );
285    }
286
287    #[test]
288    fn id20_xor_self_is_zero() {
289        let a = Id20::from_hex("aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d").unwrap();
290        assert_eq!(a.xor_distance(&a), Id20::ZERO);
291    }
292
293    #[test]
294    fn id20_invalid_hex() {
295        assert!(Id20::from_hex("not_hex").is_err());
296        assert!(Id20::from_hex("aabbcc").is_err()); // too short
297    }
298
299    #[test]
300    fn id20_display() {
301        let id = Id20::from_hex("aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d").unwrap();
302        assert_eq!(format!("{id}"), "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
303    }
304
305    #[test]
306    fn id32_hex_round_trip() {
307        let hex_str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
308        let id = Id32::from_hex(hex_str).unwrap();
309        assert_eq!(id.to_hex(), hex_str);
310    }
311
312    #[test]
313    fn id32_base32_round_trip() {
314        let id = Id32::from_hex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
315            .unwrap();
316        let b32 = id.to_base32();
317        let id2 = Id32::from_base32(&b32).unwrap();
318        assert_eq!(id, id2);
319    }
320
321    #[test]
322    fn id32_multihash_round_trip() {
323        let id = Id32::from_hex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
324            .unwrap();
325        let mh = id.to_multihash_hex();
326        // Must start with "1220" (SHA-256 function code + 32-byte length)
327        assert!(mh.starts_with("1220"));
328        assert_eq!(mh.len(), 68); // 2 prefix bytes + 32 hash bytes = 34 bytes = 68 hex chars
329        let id2 = Id32::from_multihash_hex(&mh).unwrap();
330        assert_eq!(id, id2);
331    }
332
333    #[test]
334    fn id32_multihash_reject_wrong_function_code() {
335        // Use 0x11 instead of 0x12 (wrong function code)
336        let bad = "1120e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
337        assert!(Id32::from_multihash_hex(bad).is_err());
338    }
339}