iroh_blobs/
hash.rs

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