iroh_blobs/
hash.rs

1//! The blake3 hash used in Iroh.
2
3use std::{borrow::Borrow, fmt, str::FromStr};
4
5use postcard::experimental::max_size::MaxSize;
6use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
7
8/// Hash type used throughout.
9#[derive(PartialEq, Eq, Copy, Clone, Hash)]
10pub struct Hash(blake3::Hash);
11
12impl fmt::Debug for Hash {
13    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14        f.debug_tuple("Hash").field(&DD(self.to_hex())).finish()
15    }
16}
17
18struct DD<T: fmt::Display>(T);
19
20impl<T: fmt::Display> fmt::Debug for DD<T> {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        fmt::Display::fmt(&self.0, f)
23    }
24}
25
26impl Hash {
27    /// The hash for the empty byte range (`b""`).
28    pub const EMPTY: Hash = Hash::from_bytes([
29        175, 19, 73, 185, 245, 249, 161, 166, 160, 64, 77, 234, 54, 220, 201, 73, 155, 203, 37,
30        201, 173, 193, 18, 183, 204, 154, 147, 202, 228, 31, 50, 98,
31    ]);
32
33    /// Calculate the hash of the provided bytes.
34    pub fn new(buf: impl AsRef<[u8]>) -> Self {
35        let val = blake3::hash(buf.as_ref());
36        Hash(val)
37    }
38
39    /// Bytes of the hash.
40    pub fn as_bytes(&self) -> &[u8; 32] {
41        self.0.as_bytes()
42    }
43
44    /// Create a `Hash` from its raw bytes representation.
45    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
46        Self(blake3::Hash::from_bytes(bytes))
47    }
48
49    /// Convert the hash to a hex string.
50    pub fn to_hex(&self) -> String {
51        self.0.to_hex().to_string()
52    }
53
54    /// Convert to a hex string limited to the first 5bytes for a friendly string
55    /// representation of the hash.
56    pub fn fmt_short(&self) -> String {
57        data_encoding::HEXLOWER.encode(&self.as_bytes()[..5])
58    }
59}
60
61impl AsRef<[u8]> for Hash {
62    fn as_ref(&self) -> &[u8] {
63        self.0.as_bytes()
64    }
65}
66
67impl Borrow<[u8]> for Hash {
68    fn borrow(&self) -> &[u8] {
69        self.0.as_bytes()
70    }
71}
72
73impl Borrow<[u8; 32]> for Hash {
74    fn borrow(&self) -> &[u8; 32] {
75        self.0.as_bytes()
76    }
77}
78
79impl From<Hash> for blake3::Hash {
80    fn from(value: Hash) -> Self {
81        value.0
82    }
83}
84
85impl From<blake3::Hash> for Hash {
86    fn from(value: blake3::Hash) -> Self {
87        Hash(value)
88    }
89}
90
91impl From<[u8; 32]> for Hash {
92    fn from(value: [u8; 32]) -> Self {
93        Hash(blake3::Hash::from(value))
94    }
95}
96
97impl From<Hash> for [u8; 32] {
98    fn from(value: Hash) -> Self {
99        *value.as_bytes()
100    }
101}
102
103impl From<&[u8; 32]> for Hash {
104    fn from(value: &[u8; 32]) -> Self {
105        Hash(blake3::Hash::from(*value))
106    }
107}
108
109impl PartialOrd for Hash {
110    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
111        Some(self.0.as_bytes().cmp(other.0.as_bytes()))
112    }
113}
114
115impl Ord for Hash {
116    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
117        self.0.as_bytes().cmp(other.0.as_bytes())
118    }
119}
120
121impl fmt::Display for Hash {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        // result will be 52 bytes
124        let mut res = [b'b'; 52];
125        // write the encoded bytes
126        data_encoding::BASE32_NOPAD.encode_mut(self.as_bytes(), &mut res);
127        // convert to string, this is guaranteed to succeed
128        let t = std::str::from_utf8_mut(res.as_mut()).unwrap();
129        // hack since data_encoding doesn't have BASE32LOWER_NOPAD as a const
130        t.make_ascii_lowercase();
131        // write the str, no allocations
132        f.write_str(t)
133    }
134}
135
136#[derive(Debug, thiserror::Error)]
137pub enum HexOrBase32ParseError {
138    #[error("Invalid length")]
139    DecodeInvalidLength,
140    #[error("Failed to decode {0}")]
141    Decode(#[from] data_encoding::DecodeError),
142}
143
144impl FromStr for Hash {
145    type Err = HexOrBase32ParseError;
146
147    fn from_str(s: &str) -> Result<Self, Self::Err> {
148        let mut bytes = [0u8; 32];
149
150        let res = if s.len() == 64 {
151            // hex
152            data_encoding::HEXLOWER.decode_mut(s.as_bytes(), &mut bytes)
153        } else {
154            let input = s.to_ascii_uppercase();
155            let input = input.as_bytes();
156            if data_encoding::BASE32_NOPAD.decode_len(input.len())? != bytes.len() {
157                return Err(HexOrBase32ParseError::DecodeInvalidLength);
158            }
159            data_encoding::BASE32_NOPAD.decode_mut(input, &mut bytes)
160        };
161        match res {
162            Ok(len) => {
163                if len != 32 {
164                    return Err(HexOrBase32ParseError::DecodeInvalidLength);
165                }
166            }
167            Err(partial) => return Err(partial.error.into()),
168        }
169        Ok(Self(blake3::Hash::from_bytes(bytes)))
170    }
171}
172
173impl Serialize for Hash {
174    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
175    where
176        S: Serializer,
177    {
178        if serializer.is_human_readable() {
179            serializer.serialize_str(self.to_string().as_str())
180        } else {
181            self.0.as_bytes().serialize(serializer)
182        }
183    }
184}
185
186impl<'de> Deserialize<'de> for Hash {
187    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
188    where
189        D: Deserializer<'de>,
190    {
191        if deserializer.is_human_readable() {
192            let s = String::deserialize(deserializer)?;
193            s.parse().map_err(de::Error::custom)
194        } else {
195            let data: [u8; 32] = Deserialize::deserialize(deserializer)?;
196            Ok(Self(blake3::Hash::from(data)))
197        }
198    }
199}
200
201impl MaxSize for Hash {
202    const POSTCARD_MAX_SIZE: usize = 32;
203}
204
205/// A format identifier
206#[derive(
207    Clone,
208    Copy,
209    PartialEq,
210    Eq,
211    PartialOrd,
212    Ord,
213    Serialize,
214    Deserialize,
215    Default,
216    Debug,
217    MaxSize,
218    Hash,
219    derive_more::Display,
220)]
221pub enum BlobFormat {
222    /// Raw blob
223    #[default]
224    Raw,
225    /// A sequence of BLAKE3 hashes
226    HashSeq,
227}
228
229impl From<BlobFormat> for u64 {
230    fn from(value: BlobFormat) -> Self {
231        match value {
232            BlobFormat::Raw => 0,
233            BlobFormat::HashSeq => 1,
234        }
235    }
236}
237
238impl BlobFormat {
239    /// Is raw format
240    pub const fn is_raw(&self) -> bool {
241        matches!(self, BlobFormat::Raw)
242    }
243
244    /// Is hash seq format
245    pub const fn is_hash_seq(&self) -> bool {
246        matches!(self, BlobFormat::HashSeq)
247    }
248}
249
250/// A hash and format pair
251#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, MaxSize, Hash)]
252pub struct HashAndFormat {
253    /// The hash
254    pub hash: Hash,
255    /// The format
256    pub format: BlobFormat,
257}
258
259impl From<Hash> for HashAndFormat {
260    fn from(hash: Hash) -> Self {
261        Self::raw(hash)
262    }
263}
264
265#[cfg(feature = "redb")]
266mod redb_support {
267    use postcard::experimental::max_size::MaxSize;
268    use redb::{Key as RedbKey, Value as RedbValue};
269
270    use super::{Hash, HashAndFormat};
271
272    impl RedbValue for Hash {
273        type SelfType<'a> = Self;
274
275        type AsBytes<'a> = &'a [u8; 32];
276
277        fn fixed_width() -> Option<usize> {
278            Some(32)
279        }
280
281        fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
282        where
283            Self: 'a,
284        {
285            let contents: &'a [u8; 32] = data.try_into().unwrap();
286            (*contents).into()
287        }
288
289        fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
290        where
291            Self: 'a,
292            Self: 'b,
293        {
294            value.as_bytes()
295        }
296
297        fn type_name() -> redb::TypeName {
298            redb::TypeName::new("iroh_blobs::Hash")
299        }
300    }
301
302    impl RedbKey for Hash {
303        fn compare(data1: &[u8], data2: &[u8]) -> std::cmp::Ordering {
304            data1.cmp(data2)
305        }
306    }
307
308    impl RedbValue for HashAndFormat {
309        type SelfType<'a> = Self;
310
311        type AsBytes<'a> = [u8; Self::POSTCARD_MAX_SIZE];
312
313        fn fixed_width() -> Option<usize> {
314            Some(Self::POSTCARD_MAX_SIZE)
315        }
316
317        fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
318        where
319            Self: 'a,
320        {
321            let t: &'a [u8; Self::POSTCARD_MAX_SIZE] = data.try_into().unwrap();
322            postcard::from_bytes(t.as_slice()).unwrap()
323        }
324
325        fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
326        where
327            Self: 'a,
328            Self: 'b,
329        {
330            let mut res = [0u8; 33];
331            postcard::to_slice(&value, &mut res).unwrap();
332            res
333        }
334
335        fn type_name() -> redb::TypeName {
336            redb::TypeName::new("iroh_blobs::HashAndFormat")
337        }
338    }
339}
340
341impl HashAndFormat {
342    /// Create a new hash and format pair.
343    pub fn new(hash: Hash, format: BlobFormat) -> Self {
344        Self { hash, format }
345    }
346
347    /// Create a new hash and format pair, using the default (raw) format.
348    pub fn raw(hash: Hash) -> Self {
349        Self {
350            hash,
351            format: BlobFormat::Raw,
352        }
353    }
354
355    /// Create a new hash and format pair, using the collection format.
356    pub fn hash_seq(hash: Hash) -> Self {
357        Self {
358            hash,
359            format: BlobFormat::HashSeq,
360        }
361    }
362}
363
364impl fmt::Display for HashAndFormat {
365    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366        let mut slice = [0u8; 65];
367        hex::encode_to_slice(self.hash.as_bytes(), &mut slice[1..]).unwrap();
368        match self.format {
369            BlobFormat::Raw => {
370                write!(f, "{}", std::str::from_utf8(&slice[1..]).unwrap())
371            }
372            BlobFormat::HashSeq => {
373                slice[0] = b's';
374                write!(f, "{}", std::str::from_utf8(&slice).unwrap())
375            }
376        }
377    }
378}
379
380impl FromStr for HashAndFormat {
381    type Err = anyhow::Error;
382
383    fn from_str(s: &str) -> Result<Self, Self::Err> {
384        let s = s.as_bytes();
385        let mut hash = [0u8; 32];
386        match s.len() {
387            64 => {
388                hex::decode_to_slice(s, &mut hash)?;
389                Ok(Self::raw(hash.into()))
390            }
391            65 if s[0].eq_ignore_ascii_case(&b's') => {
392                hex::decode_to_slice(&s[1..], &mut hash)?;
393                Ok(Self::hash_seq(hash.into()))
394            }
395            _ => anyhow::bail!("invalid hash and format"),
396        }
397    }
398}
399
400impl Serialize for HashAndFormat {
401    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
402    where
403        S: Serializer,
404    {
405        if serializer.is_human_readable() {
406            serializer.serialize_str(self.to_string().as_str())
407        } else {
408            (self.hash, self.format).serialize(serializer)
409        }
410    }
411}
412
413impl<'de> Deserialize<'de> for HashAndFormat {
414    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
415    where
416        D: Deserializer<'de>,
417    {
418        if deserializer.is_human_readable() {
419            let s = String::deserialize(deserializer)?;
420            s.parse().map_err(de::Error::custom)
421        } else {
422            let (hash, format) = <(Hash, BlobFormat)>::deserialize(deserializer)?;
423            Ok(Self { hash, format })
424        }
425    }
426}
427
428#[cfg(test)]
429mod tests {
430    use serde_test::{assert_tokens, Configure, Token};
431
432    use super::*;
433    use crate::{assert_eq_hex, util::hexdump::parse_hexdump};
434
435    #[test]
436    fn test_display_parse_roundtrip() {
437        for i in 0..100 {
438            let hash: Hash = blake3::hash(&[i]).into();
439            let text = hash.to_string();
440            let hash1 = text.parse::<Hash>().unwrap();
441            assert_eq!(hash, hash1);
442
443            let text = hash.to_hex();
444            let hash1 = Hash::from_str(&text).unwrap();
445            assert_eq!(hash, hash1);
446        }
447    }
448
449    #[test]
450    fn test_hash() {
451        let data = b"hello world";
452        let hash = Hash::new(data);
453
454        let encoded = hash.to_string();
455        assert_eq!(encoded.parse::<Hash>().unwrap(), hash);
456    }
457
458    #[test]
459    fn test_empty_hash() {
460        let hash = Hash::new(b"");
461        assert_eq!(hash, Hash::EMPTY);
462    }
463
464    #[test]
465    fn hash_wire_format() {
466        let hash = Hash::from([0xab; 32]);
467        let serialized = postcard::to_stdvec(&hash).unwrap();
468        let expected = parse_hexdump(r"
469            ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab # hash
470        ").unwrap();
471        assert_eq_hex!(serialized, expected);
472    }
473
474    #[cfg(feature = "redb")]
475    #[test]
476    fn hash_redb() {
477        use redb::Value as RedbValue;
478        let bytes: [u8; 32] = (0..32).collect::<Vec<_>>().as_slice().try_into().unwrap();
479        let hash = Hash::from(bytes);
480        assert_eq!(<Hash as RedbValue>::fixed_width(), Some(32));
481        assert_eq!(
482            <Hash as RedbValue>::type_name(),
483            redb::TypeName::new("iroh_blobs::Hash")
484        );
485        let serialized = <Hash as RedbValue>::as_bytes(&hash);
486        assert_eq!(serialized, &bytes);
487        let deserialized = <Hash as RedbValue>::from_bytes(serialized.as_slice());
488        assert_eq!(deserialized, hash);
489        let expected = parse_hexdump(
490            r"
491            00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
492            10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f # hash
493        ",
494        )
495        .unwrap();
496        assert_eq_hex!(serialized, expected);
497    }
498
499    #[cfg(feature = "redb")]
500    #[test]
501    fn hash_and_format_redb() {
502        use redb::Value as RedbValue;
503        let hash_bytes: [u8; 32] = (0..32).collect::<Vec<_>>().as_slice().try_into().unwrap();
504        let hash = Hash::from(hash_bytes);
505        let haf = HashAndFormat::raw(hash);
506        assert_eq!(<HashAndFormat as RedbValue>::fixed_width(), Some(33));
507        assert_eq!(
508            <HashAndFormat as RedbValue>::type_name(),
509            redb::TypeName::new("iroh_blobs::HashAndFormat")
510        );
511        let serialized = <HashAndFormat as RedbValue>::as_bytes(&haf);
512        let mut bytes = [0u8; 33];
513        bytes[0..32].copy_from_slice(&hash_bytes);
514        assert_eq!(serialized, bytes);
515        let deserialized = <HashAndFormat as RedbValue>::from_bytes(serialized.as_slice());
516        assert_eq!(deserialized, haf);
517        let expected = parse_hexdump(
518            r"
519            00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
520            10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f # hash
521            00 # format (raw)
522        ",
523        )
524        .unwrap();
525        assert_eq_hex!(serialized, expected);
526    }
527
528    #[test]
529    fn test_hash_serde() {
530        let hash = Hash::new("hello");
531
532        // Hashes are serialized as 32 tuples
533        let mut tokens = Vec::new();
534        tokens.push(Token::Tuple { len: 32 });
535        for byte in hash.as_bytes() {
536            tokens.push(Token::U8(*byte));
537        }
538        tokens.push(Token::TupleEnd);
539        assert_eq!(tokens.len(), 34);
540
541        assert_tokens(&hash.compact(), &tokens);
542
543        let tokens = vec![Token::String(
544            "5khrmpntq2bjexseshc6ldklwnig56gbj23yvbxjbdcwestheahq",
545        )];
546        assert_tokens(&hash.readable(), &tokens);
547    }
548
549    #[test]
550    fn test_hash_postcard() {
551        let hash = Hash::new("hello");
552        let ser = postcard::to_stdvec(&hash).unwrap();
553        let de = postcard::from_bytes(&ser).unwrap();
554        assert_eq!(hash, de);
555
556        assert_eq!(ser.len(), 32);
557    }
558
559    #[test]
560    fn test_hash_json() {
561        let hash = Hash::new("hello");
562        let ser = serde_json::to_string(&hash).unwrap();
563        let de = serde_json::from_str(&ser).unwrap();
564        assert_eq!(hash, de);
565        // 52 bytes of base32 + 2 quotes
566        assert_eq!(ser.len(), 54);
567    }
568
569    #[test]
570    fn test_hash_and_format_parse() {
571        let hash = Hash::new("hello");
572
573        let expected = HashAndFormat::raw(hash);
574        let actual = expected.to_string().parse::<HashAndFormat>().unwrap();
575        assert_eq!(expected, actual);
576
577        let expected = HashAndFormat::hash_seq(hash);
578        let actual = expected.to_string().parse::<HashAndFormat>().unwrap();
579        assert_eq!(expected, actual);
580    }
581
582    #[test]
583    fn test_hash_and_format_postcard() {
584        let haf = HashAndFormat::raw(Hash::new("hello"));
585        let ser = postcard::to_stdvec(&haf).unwrap();
586        let de = postcard::from_bytes(&ser).unwrap();
587        assert_eq!(haf, de);
588    }
589
590    #[test]
591    fn test_hash_and_format_json() {
592        let haf = HashAndFormat::raw(Hash::new("hello"));
593        let ser = serde_json::to_string(&haf).unwrap();
594        let de = serde_json::from_str(&ser).unwrap();
595        assert_eq!(haf, de);
596    }
597
598    #[test]
599    fn test_hash_invalid() {
600        let _ = Hash::from_str("invalid").unwrap_err();
601    }
602}