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            data_encoding::BASE32_NOPAD.decode_mut(s.to_ascii_uppercase().as_bytes(), &mut bytes)
155        };
156        match res {
157            Ok(len) => {
158                if len != 32 {
159                    return Err(HexOrBase32ParseError::DecodeInvalidLength);
160                }
161            }
162            Err(partial) => return Err(partial.error.into()),
163        }
164        Ok(Self(blake3::Hash::from_bytes(bytes)))
165    }
166}
167
168impl Serialize for Hash {
169    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
170    where
171        S: Serializer,
172    {
173        if serializer.is_human_readable() {
174            serializer.serialize_str(self.to_string().as_str())
175        } else {
176            self.0.as_bytes().serialize(serializer)
177        }
178    }
179}
180
181impl<'de> Deserialize<'de> for Hash {
182    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
183    where
184        D: Deserializer<'de>,
185    {
186        if deserializer.is_human_readable() {
187            let s = String::deserialize(deserializer)?;
188            s.parse().map_err(de::Error::custom)
189        } else {
190            let data: [u8; 32] = Deserialize::deserialize(deserializer)?;
191            Ok(Self(blake3::Hash::from(data)))
192        }
193    }
194}
195
196impl MaxSize for Hash {
197    const POSTCARD_MAX_SIZE: usize = 32;
198}
199
200/// A format identifier
201#[derive(
202    Clone,
203    Copy,
204    PartialEq,
205    Eq,
206    PartialOrd,
207    Ord,
208    Serialize,
209    Deserialize,
210    Default,
211    Debug,
212    MaxSize,
213    Hash,
214    derive_more::Display,
215)]
216pub enum BlobFormat {
217    /// Raw blob
218    #[default]
219    Raw,
220    /// A sequence of BLAKE3 hashes
221    HashSeq,
222}
223
224impl From<BlobFormat> for u64 {
225    fn from(value: BlobFormat) -> Self {
226        match value {
227            BlobFormat::Raw => 0,
228            BlobFormat::HashSeq => 1,
229        }
230    }
231}
232
233impl BlobFormat {
234    /// Is raw format
235    pub const fn is_raw(&self) -> bool {
236        matches!(self, BlobFormat::Raw)
237    }
238
239    /// Is hash seq format
240    pub const fn is_hash_seq(&self) -> bool {
241        matches!(self, BlobFormat::HashSeq)
242    }
243}
244
245/// A hash and format pair
246#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, MaxSize, Hash)]
247pub struct HashAndFormat {
248    /// The hash
249    pub hash: Hash,
250    /// The format
251    pub format: BlobFormat,
252}
253
254#[cfg(feature = "redb")]
255mod redb_support {
256    use postcard::experimental::max_size::MaxSize;
257    use redb::{Key as RedbKey, Value as RedbValue};
258
259    use super::{Hash, HashAndFormat};
260
261    impl RedbValue for Hash {
262        type SelfType<'a> = Self;
263
264        type AsBytes<'a> = &'a [u8; 32];
265
266        fn fixed_width() -> Option<usize> {
267            Some(32)
268        }
269
270        fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
271        where
272            Self: 'a,
273        {
274            let contents: &'a [u8; 32] = data.try_into().unwrap();
275            (*contents).into()
276        }
277
278        fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
279        where
280            Self: 'a,
281            Self: 'b,
282        {
283            value.as_bytes()
284        }
285
286        fn type_name() -> redb::TypeName {
287            redb::TypeName::new("iroh_blobs::Hash")
288        }
289    }
290
291    impl RedbKey for Hash {
292        fn compare(data1: &[u8], data2: &[u8]) -> std::cmp::Ordering {
293            data1.cmp(data2)
294        }
295    }
296
297    impl RedbValue for HashAndFormat {
298        type SelfType<'a> = Self;
299
300        type AsBytes<'a> = [u8; Self::POSTCARD_MAX_SIZE];
301
302        fn fixed_width() -> Option<usize> {
303            Some(Self::POSTCARD_MAX_SIZE)
304        }
305
306        fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
307        where
308            Self: 'a,
309        {
310            let t: &'a [u8; Self::POSTCARD_MAX_SIZE] = data.try_into().unwrap();
311            postcard::from_bytes(t.as_slice()).unwrap()
312        }
313
314        fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
315        where
316            Self: 'a,
317            Self: 'b,
318        {
319            let mut res = [0u8; 33];
320            postcard::to_slice(&value, &mut res).unwrap();
321            res
322        }
323
324        fn type_name() -> redb::TypeName {
325            redb::TypeName::new("iroh_blobs::HashAndFormat")
326        }
327    }
328}
329
330impl HashAndFormat {
331    /// Create a new hash and format pair.
332    pub fn new(hash: Hash, format: BlobFormat) -> Self {
333        Self { hash, format }
334    }
335
336    /// Create a new hash and format pair, using the default (raw) format.
337    pub fn raw(hash: Hash) -> Self {
338        Self {
339            hash,
340            format: BlobFormat::Raw,
341        }
342    }
343
344    /// Create a new hash and format pair, using the collection format.
345    pub fn hash_seq(hash: Hash) -> Self {
346        Self {
347            hash,
348            format: BlobFormat::HashSeq,
349        }
350    }
351}
352
353impl fmt::Display for HashAndFormat {
354    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355        let mut slice = [0u8; 65];
356        hex::encode_to_slice(self.hash.as_bytes(), &mut slice[1..]).unwrap();
357        match self.format {
358            BlobFormat::Raw => {
359                write!(f, "{}", std::str::from_utf8(&slice[1..]).unwrap())
360            }
361            BlobFormat::HashSeq => {
362                slice[0] = b's';
363                write!(f, "{}", std::str::from_utf8(&slice).unwrap())
364            }
365        }
366    }
367}
368
369impl FromStr for HashAndFormat {
370    type Err = anyhow::Error;
371
372    fn from_str(s: &str) -> Result<Self, Self::Err> {
373        let s = s.as_bytes();
374        let mut hash = [0u8; 32];
375        match s.len() {
376            64 => {
377                hex::decode_to_slice(s, &mut hash)?;
378                Ok(Self::raw(hash.into()))
379            }
380            65 if s[0].eq_ignore_ascii_case(&b's') => {
381                hex::decode_to_slice(&s[1..], &mut hash)?;
382                Ok(Self::hash_seq(hash.into()))
383            }
384            _ => anyhow::bail!("invalid hash and format"),
385        }
386    }
387}
388
389impl Serialize for HashAndFormat {
390    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
391    where
392        S: Serializer,
393    {
394        if serializer.is_human_readable() {
395            serializer.serialize_str(self.to_string().as_str())
396        } else {
397            (self.hash, self.format).serialize(serializer)
398        }
399    }
400}
401
402impl<'de> Deserialize<'de> for HashAndFormat {
403    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
404    where
405        D: Deserializer<'de>,
406    {
407        if deserializer.is_human_readable() {
408            let s = String::deserialize(deserializer)?;
409            s.parse().map_err(de::Error::custom)
410        } else {
411            let (hash, format) = <(Hash, BlobFormat)>::deserialize(deserializer)?;
412            Ok(Self { hash, format })
413        }
414    }
415}
416
417#[cfg(test)]
418mod tests {
419    use serde_test::{assert_tokens, Configure, Token};
420
421    use super::*;
422    use crate::{assert_eq_hex, util::hexdump::parse_hexdump};
423
424    #[test]
425    fn test_display_parse_roundtrip() {
426        for i in 0..100 {
427            let hash: Hash = blake3::hash(&[i]).into();
428            let text = hash.to_string();
429            let hash1 = text.parse::<Hash>().unwrap();
430            assert_eq!(hash, hash1);
431
432            let text = hash.to_hex();
433            let hash1 = Hash::from_str(&text).unwrap();
434            assert_eq!(hash, hash1);
435        }
436    }
437
438    #[test]
439    fn test_hash() {
440        let data = b"hello world";
441        let hash = Hash::new(data);
442
443        let encoded = hash.to_string();
444        assert_eq!(encoded.parse::<Hash>().unwrap(), hash);
445    }
446
447    #[test]
448    fn test_empty_hash() {
449        let hash = Hash::new(b"");
450        assert_eq!(hash, Hash::EMPTY);
451    }
452
453    #[test]
454    fn hash_wire_format() {
455        let hash = Hash::from([0xab; 32]);
456        let serialized = postcard::to_stdvec(&hash).unwrap();
457        let expected = parse_hexdump(r"
458            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
459        ").unwrap();
460        assert_eq_hex!(serialized, expected);
461    }
462
463    #[cfg(feature = "redb")]
464    #[test]
465    fn hash_redb() {
466        use redb::Value as RedbValue;
467        let bytes: [u8; 32] = (0..32).collect::<Vec<_>>().as_slice().try_into().unwrap();
468        let hash = Hash::from(bytes);
469        assert_eq!(<Hash as RedbValue>::fixed_width(), Some(32));
470        assert_eq!(
471            <Hash as RedbValue>::type_name(),
472            redb::TypeName::new("iroh_blobs::Hash")
473        );
474        let serialized = <Hash as RedbValue>::as_bytes(&hash);
475        assert_eq!(serialized, &bytes);
476        let deserialized = <Hash as RedbValue>::from_bytes(serialized.as_slice());
477        assert_eq!(deserialized, hash);
478        let expected = parse_hexdump(
479            r"
480            00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
481            10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f # hash
482        ",
483        )
484        .unwrap();
485        assert_eq_hex!(serialized, expected);
486    }
487
488    #[cfg(feature = "redb")]
489    #[test]
490    fn hash_and_format_redb() {
491        use redb::Value as RedbValue;
492        let hash_bytes: [u8; 32] = (0..32).collect::<Vec<_>>().as_slice().try_into().unwrap();
493        let hash = Hash::from(hash_bytes);
494        let haf = HashAndFormat::raw(hash);
495        assert_eq!(<HashAndFormat as RedbValue>::fixed_width(), Some(33));
496        assert_eq!(
497            <HashAndFormat as RedbValue>::type_name(),
498            redb::TypeName::new("iroh_blobs::HashAndFormat")
499        );
500        let serialized = <HashAndFormat as RedbValue>::as_bytes(&haf);
501        let mut bytes = [0u8; 33];
502        bytes[0..32].copy_from_slice(&hash_bytes);
503        assert_eq!(serialized, bytes);
504        let deserialized = <HashAndFormat as RedbValue>::from_bytes(serialized.as_slice());
505        assert_eq!(deserialized, haf);
506        let expected = parse_hexdump(
507            r"
508            00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
509            10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f # hash
510            00 # format (raw)
511        ",
512        )
513        .unwrap();
514        assert_eq_hex!(serialized, expected);
515    }
516
517    #[test]
518    fn test_hash_serde() {
519        let hash = Hash::new("hello");
520
521        // Hashes are serialized as 32 tuples
522        let mut tokens = Vec::new();
523        tokens.push(Token::Tuple { len: 32 });
524        for byte in hash.as_bytes() {
525            tokens.push(Token::U8(*byte));
526        }
527        tokens.push(Token::TupleEnd);
528        assert_eq!(tokens.len(), 34);
529
530        assert_tokens(&hash.compact(), &tokens);
531
532        let tokens = vec![Token::String(
533            "5khrmpntq2bjexseshc6ldklwnig56gbj23yvbxjbdcwestheahq",
534        )];
535        assert_tokens(&hash.readable(), &tokens);
536    }
537
538    #[test]
539    fn test_hash_postcard() {
540        let hash = Hash::new("hello");
541        let ser = postcard::to_stdvec(&hash).unwrap();
542        let de = postcard::from_bytes(&ser).unwrap();
543        assert_eq!(hash, de);
544
545        assert_eq!(ser.len(), 32);
546    }
547
548    #[test]
549    fn test_hash_json() {
550        let hash = Hash::new("hello");
551        let ser = serde_json::to_string(&hash).unwrap();
552        let de = serde_json::from_str(&ser).unwrap();
553        assert_eq!(hash, de);
554        // 52 bytes of base32 + 2 quotes
555        assert_eq!(ser.len(), 54);
556    }
557
558    #[test]
559    fn test_hash_and_format_parse() {
560        let hash = Hash::new("hello");
561
562        let expected = HashAndFormat::raw(hash);
563        let actual = expected.to_string().parse::<HashAndFormat>().unwrap();
564        assert_eq!(expected, actual);
565
566        let expected = HashAndFormat::hash_seq(hash);
567        let actual = expected.to_string().parse::<HashAndFormat>().unwrap();
568        assert_eq!(expected, actual);
569    }
570
571    #[test]
572    fn test_hash_and_format_postcard() {
573        let haf = HashAndFormat::raw(Hash::new("hello"));
574        let ser = postcard::to_stdvec(&haf).unwrap();
575        let de = postcard::from_bytes(&ser).unwrap();
576        assert_eq!(haf, de);
577    }
578
579    #[test]
580    fn test_hash_and_format_json() {
581        let haf = HashAndFormat::raw(Hash::new("hello"));
582        let ser = serde_json::to_string(&haf).unwrap();
583        let de = serde_json::from_str(&ser).unwrap();
584        assert_eq!(haf, de);
585    }
586}