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
259#[cfg(feature = "redb")]
260mod redb_support {
261    use postcard::experimental::max_size::MaxSize;
262    use redb::{Key as RedbKey, Value as RedbValue};
263
264    use super::{Hash, HashAndFormat};
265
266    impl RedbValue for Hash {
267        type SelfType<'a> = Self;
268
269        type AsBytes<'a> = &'a [u8; 32];
270
271        fn fixed_width() -> Option<usize> {
272            Some(32)
273        }
274
275        fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
276        where
277            Self: 'a,
278        {
279            let contents: &'a [u8; 32] = data.try_into().unwrap();
280            (*contents).into()
281        }
282
283        fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
284        where
285            Self: 'a,
286            Self: 'b,
287        {
288            value.as_bytes()
289        }
290
291        fn type_name() -> redb::TypeName {
292            redb::TypeName::new("iroh_blobs::Hash")
293        }
294    }
295
296    impl RedbKey for Hash {
297        fn compare(data1: &[u8], data2: &[u8]) -> std::cmp::Ordering {
298            data1.cmp(data2)
299        }
300    }
301
302    impl RedbValue for HashAndFormat {
303        type SelfType<'a> = Self;
304
305        type AsBytes<'a> = [u8; Self::POSTCARD_MAX_SIZE];
306
307        fn fixed_width() -> Option<usize> {
308            Some(Self::POSTCARD_MAX_SIZE)
309        }
310
311        fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
312        where
313            Self: 'a,
314        {
315            let t: &'a [u8; Self::POSTCARD_MAX_SIZE] = data.try_into().unwrap();
316            postcard::from_bytes(t.as_slice()).unwrap()
317        }
318
319        fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
320        where
321            Self: 'a,
322            Self: 'b,
323        {
324            let mut res = [0u8; 33];
325            postcard::to_slice(&value, &mut res).unwrap();
326            res
327        }
328
329        fn type_name() -> redb::TypeName {
330            redb::TypeName::new("iroh_blobs::HashAndFormat")
331        }
332    }
333}
334
335impl HashAndFormat {
336    /// Create a new hash and format pair.
337    pub fn new(hash: Hash, format: BlobFormat) -> Self {
338        Self { hash, format }
339    }
340
341    /// Create a new hash and format pair, using the default (raw) format.
342    pub fn raw(hash: Hash) -> Self {
343        Self {
344            hash,
345            format: BlobFormat::Raw,
346        }
347    }
348
349    /// Create a new hash and format pair, using the collection format.
350    pub fn hash_seq(hash: Hash) -> Self {
351        Self {
352            hash,
353            format: BlobFormat::HashSeq,
354        }
355    }
356}
357
358impl fmt::Display for HashAndFormat {
359    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360        let mut slice = [0u8; 65];
361        hex::encode_to_slice(self.hash.as_bytes(), &mut slice[1..]).unwrap();
362        match self.format {
363            BlobFormat::Raw => {
364                write!(f, "{}", std::str::from_utf8(&slice[1..]).unwrap())
365            }
366            BlobFormat::HashSeq => {
367                slice[0] = b's';
368                write!(f, "{}", std::str::from_utf8(&slice).unwrap())
369            }
370        }
371    }
372}
373
374impl FromStr for HashAndFormat {
375    type Err = anyhow::Error;
376
377    fn from_str(s: &str) -> Result<Self, Self::Err> {
378        let s = s.as_bytes();
379        let mut hash = [0u8; 32];
380        match s.len() {
381            64 => {
382                hex::decode_to_slice(s, &mut hash)?;
383                Ok(Self::raw(hash.into()))
384            }
385            65 if s[0].eq_ignore_ascii_case(&b's') => {
386                hex::decode_to_slice(&s[1..], &mut hash)?;
387                Ok(Self::hash_seq(hash.into()))
388            }
389            _ => anyhow::bail!("invalid hash and format"),
390        }
391    }
392}
393
394impl Serialize for HashAndFormat {
395    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
396    where
397        S: Serializer,
398    {
399        if serializer.is_human_readable() {
400            serializer.serialize_str(self.to_string().as_str())
401        } else {
402            (self.hash, self.format).serialize(serializer)
403        }
404    }
405}
406
407impl<'de> Deserialize<'de> for HashAndFormat {
408    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
409    where
410        D: Deserializer<'de>,
411    {
412        if deserializer.is_human_readable() {
413            let s = String::deserialize(deserializer)?;
414            s.parse().map_err(de::Error::custom)
415        } else {
416            let (hash, format) = <(Hash, BlobFormat)>::deserialize(deserializer)?;
417            Ok(Self { hash, format })
418        }
419    }
420}
421
422#[cfg(test)]
423mod tests {
424    use serde_test::{assert_tokens, Configure, Token};
425
426    use super::*;
427    use crate::{assert_eq_hex, util::hexdump::parse_hexdump};
428
429    #[test]
430    fn test_display_parse_roundtrip() {
431        for i in 0..100 {
432            let hash: Hash = blake3::hash(&[i]).into();
433            let text = hash.to_string();
434            let hash1 = text.parse::<Hash>().unwrap();
435            assert_eq!(hash, hash1);
436
437            let text = hash.to_hex();
438            let hash1 = Hash::from_str(&text).unwrap();
439            assert_eq!(hash, hash1);
440        }
441    }
442
443    #[test]
444    fn test_hash() {
445        let data = b"hello world";
446        let hash = Hash::new(data);
447
448        let encoded = hash.to_string();
449        assert_eq!(encoded.parse::<Hash>().unwrap(), hash);
450    }
451
452    #[test]
453    fn test_empty_hash() {
454        let hash = Hash::new(b"");
455        assert_eq!(hash, Hash::EMPTY);
456    }
457
458    #[test]
459    fn hash_wire_format() {
460        let hash = Hash::from([0xab; 32]);
461        let serialized = postcard::to_stdvec(&hash).unwrap();
462        let expected = parse_hexdump(r"
463            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
464        ").unwrap();
465        assert_eq_hex!(serialized, expected);
466    }
467
468    #[cfg(feature = "redb")]
469    #[test]
470    fn hash_redb() {
471        use redb::Value as RedbValue;
472        let bytes: [u8; 32] = (0..32).collect::<Vec<_>>().as_slice().try_into().unwrap();
473        let hash = Hash::from(bytes);
474        assert_eq!(<Hash as RedbValue>::fixed_width(), Some(32));
475        assert_eq!(
476            <Hash as RedbValue>::type_name(),
477            redb::TypeName::new("iroh_blobs::Hash")
478        );
479        let serialized = <Hash as RedbValue>::as_bytes(&hash);
480        assert_eq!(serialized, &bytes);
481        let deserialized = <Hash as RedbValue>::from_bytes(serialized.as_slice());
482        assert_eq!(deserialized, hash);
483        let expected = parse_hexdump(
484            r"
485            00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
486            10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f # hash
487        ",
488        )
489        .unwrap();
490        assert_eq_hex!(serialized, expected);
491    }
492
493    #[cfg(feature = "redb")]
494    #[test]
495    fn hash_and_format_redb() {
496        use redb::Value as RedbValue;
497        let hash_bytes: [u8; 32] = (0..32).collect::<Vec<_>>().as_slice().try_into().unwrap();
498        let hash = Hash::from(hash_bytes);
499        let haf = HashAndFormat::raw(hash);
500        assert_eq!(<HashAndFormat as RedbValue>::fixed_width(), Some(33));
501        assert_eq!(
502            <HashAndFormat as RedbValue>::type_name(),
503            redb::TypeName::new("iroh_blobs::HashAndFormat")
504        );
505        let serialized = <HashAndFormat as RedbValue>::as_bytes(&haf);
506        let mut bytes = [0u8; 33];
507        bytes[0..32].copy_from_slice(&hash_bytes);
508        assert_eq!(serialized, bytes);
509        let deserialized = <HashAndFormat as RedbValue>::from_bytes(serialized.as_slice());
510        assert_eq!(deserialized, haf);
511        let expected = parse_hexdump(
512            r"
513            00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
514            10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f # hash
515            00 # format (raw)
516        ",
517        )
518        .unwrap();
519        assert_eq_hex!(serialized, expected);
520    }
521
522    #[test]
523    fn test_hash_serde() {
524        let hash = Hash::new("hello");
525
526        // Hashes are serialized as 32 tuples
527        let mut tokens = Vec::new();
528        tokens.push(Token::Tuple { len: 32 });
529        for byte in hash.as_bytes() {
530            tokens.push(Token::U8(*byte));
531        }
532        tokens.push(Token::TupleEnd);
533        assert_eq!(tokens.len(), 34);
534
535        assert_tokens(&hash.compact(), &tokens);
536
537        let tokens = vec![Token::String(
538            "5khrmpntq2bjexseshc6ldklwnig56gbj23yvbxjbdcwestheahq",
539        )];
540        assert_tokens(&hash.readable(), &tokens);
541    }
542
543    #[test]
544    fn test_hash_postcard() {
545        let hash = Hash::new("hello");
546        let ser = postcard::to_stdvec(&hash).unwrap();
547        let de = postcard::from_bytes(&ser).unwrap();
548        assert_eq!(hash, de);
549
550        assert_eq!(ser.len(), 32);
551    }
552
553    #[test]
554    fn test_hash_json() {
555        let hash = Hash::new("hello");
556        let ser = serde_json::to_string(&hash).unwrap();
557        let de = serde_json::from_str(&ser).unwrap();
558        assert_eq!(hash, de);
559        // 52 bytes of base32 + 2 quotes
560        assert_eq!(ser.len(), 54);
561    }
562
563    #[test]
564    fn test_hash_and_format_parse() {
565        let hash = Hash::new("hello");
566
567        let expected = HashAndFormat::raw(hash);
568        let actual = expected.to_string().parse::<HashAndFormat>().unwrap();
569        assert_eq!(expected, actual);
570
571        let expected = HashAndFormat::hash_seq(hash);
572        let actual = expected.to_string().parse::<HashAndFormat>().unwrap();
573        assert_eq!(expected, actual);
574    }
575
576    #[test]
577    fn test_hash_and_format_postcard() {
578        let haf = HashAndFormat::raw(Hash::new("hello"));
579        let ser = postcard::to_stdvec(&haf).unwrap();
580        let de = postcard::from_bytes(&ser).unwrap();
581        assert_eq!(haf, de);
582    }
583
584    #[test]
585    fn test_hash_and_format_json() {
586        let haf = HashAndFormat::raw(Hash::new("hello"));
587        let ser = serde_json::to_string(&haf).unwrap();
588        let de = serde_json::from_str(&ser).unwrap();
589        assert_eq!(haf, de);
590    }
591
592    #[test]
593    fn test_hash_invalid() {
594        let _ = Hash::from_str("invalid").unwrap_err();
595    }
596}