Skip to main content

twenty_first/tip5/
digest.rs

1use core::fmt;
2use std::str::FromStr;
3
4use arbitrary::Arbitrary;
5use bfieldcodec_derive::BFieldCodec;
6use get_size2::GetSize;
7use itertools::Itertools;
8use num_bigint::BigUint;
9use num_traits::ConstZero;
10use num_traits::Zero;
11use rand::Rng;
12use rand::RngExt;
13use rand::distr::Distribution;
14use rand::distr::StandardUniform;
15use serde::Deserialize;
16use serde::Deserializer;
17use serde::Serialize;
18use serde::Serializer;
19
20use crate::error::TryFromDigestError;
21use crate::error::TryFromHexDigestError;
22use crate::math::b_field_element::BFieldElement;
23use crate::prelude::Tip5;
24
25/// The result of hashing a sequence of elements, for example using [Tip5].
26/// Sometimes called a “hash”.
27// note: Serialize and Deserialize have custom implementations below
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, GetSize, BFieldCodec, Arbitrary)]
29pub struct Digest(pub [BFieldElement; Digest::LEN]);
30
31impl PartialOrd for Digest {
32    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
33        Some(self.cmp(other))
34    }
35}
36
37impl Ord for Digest {
38    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
39        let Digest(self_inner) = self;
40        let Digest(other_inner) = other;
41        let self_as_u64s = self_inner.iter().rev().map(|bfe| bfe.value());
42        let other_as_u64s = other_inner.iter().rev().map(|bfe| bfe.value());
43        self_as_u64s.cmp(other_as_u64s)
44    }
45}
46
47impl Digest {
48    /// The number of [elements](BFieldElement) in a digest.
49    pub const LEN: usize = 5;
50
51    /// The number of bytes in a digest.
52    pub const BYTES: usize = Self::LEN * BFieldElement::BYTES;
53
54    /// The all-zero digest.
55    pub(crate) const ALL_ZERO: Self = Self([BFieldElement::ZERO; Self::LEN]);
56
57    pub const fn values(self) -> [BFieldElement; Self::LEN] {
58        self.0
59    }
60
61    pub const fn new(digest: [BFieldElement; Self::LEN]) -> Self {
62        Self(digest)
63    }
64
65    /// Returns a new digest but whose elements are reversed relative to self.
66    /// This function is an involutive endomorphism.
67    pub const fn reversed(self) -> Digest {
68        let Digest([d0, d1, d2, d3, d4]) = self;
69        Digest([d4, d3, d2, d1, d0])
70    }
71}
72
73impl Default for Digest {
74    fn default() -> Self {
75        Self::ALL_ZERO
76    }
77}
78
79impl fmt::Display for Digest {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        write!(f, "{}", self.0.map(|elem| elem.to_string()).join(","))
82    }
83}
84
85impl fmt::LowerHex for Digest {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        let bytes = <[u8; Self::BYTES]>::from(*self);
88        write!(f, "{}", hex::encode(bytes))
89    }
90}
91
92impl fmt::UpperHex for Digest {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        let bytes = <[u8; Self::BYTES]>::from(*self);
95        write!(f, "{}", hex::encode_upper(bytes))
96    }
97}
98
99impl Distribution<Digest> for StandardUniform {
100    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Digest {
101        Digest::new(rng.random())
102    }
103}
104
105impl FromStr for Digest {
106    type Err = TryFromDigestError;
107
108    fn from_str(string: &str) -> Result<Self, Self::Err> {
109        let bfes: Vec<_> = string
110            .split(',')
111            .map(str::parse::<BFieldElement>)
112            .try_collect()?;
113        let invalid_len_err = Self::Err::InvalidLength(bfes.len());
114        let digest_innards = bfes.try_into().map_err(|_| invalid_len_err)?;
115
116        Ok(Digest(digest_innards))
117    }
118}
119
120impl TryFrom<&[BFieldElement]> for Digest {
121    type Error = TryFromDigestError;
122
123    fn try_from(value: &[BFieldElement]) -> Result<Self, Self::Error> {
124        let len = value.len();
125        let maybe_digest = value.try_into().map(Digest::new);
126        maybe_digest.map_err(|_| Self::Error::InvalidLength(len))
127    }
128}
129
130impl TryFrom<Vec<BFieldElement>> for Digest {
131    type Error = TryFromDigestError;
132
133    fn try_from(value: Vec<BFieldElement>) -> Result<Self, Self::Error> {
134        Digest::try_from(&value as &[BFieldElement])
135    }
136}
137
138impl From<Digest> for Vec<BFieldElement> {
139    fn from(val: Digest) -> Self {
140        val.0.to_vec()
141    }
142}
143
144impl From<Digest> for [u8; Digest::BYTES] {
145    fn from(Digest(innards): Digest) -> Self {
146        innards
147            .map(<[u8; BFieldElement::BYTES]>::from)
148            .concat()
149            .try_into()
150            .unwrap()
151    }
152}
153
154impl TryFrom<[u8; Digest::BYTES]> for Digest {
155    type Error = TryFromDigestError;
156
157    fn try_from(item: [u8; Self::BYTES]) -> Result<Self, Self::Error> {
158        let digest_innards: Vec<_> = item
159            .as_chunks::<{ BFieldElement::BYTES }>()
160            .0
161            .iter()
162            .map(|&bytes| BFieldElement::try_from(bytes))
163            .try_collect()?;
164
165        Ok(Self(digest_innards.try_into().unwrap()))
166    }
167}
168
169impl TryFrom<&[u8]> for Digest {
170    type Error = TryFromDigestError;
171
172    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
173        let array = <[u8; Self::BYTES]>::try_from(slice)
174            .map_err(|_e| TryFromDigestError::InvalidLength(slice.len()))?;
175        Self::try_from(array)
176    }
177}
178
179impl TryFrom<BigUint> for Digest {
180    type Error = TryFromDigestError;
181
182    fn try_from(value: BigUint) -> Result<Self, Self::Error> {
183        let mut remaining = value;
184        let mut digest_innards = [BFieldElement::ZERO; Self::LEN];
185        let modulus: BigUint = BFieldElement::P.into();
186        for digest_element in digest_innards.iter_mut() {
187            let element = u64::try_from(remaining.clone() % modulus.clone()).unwrap();
188            *digest_element = BFieldElement::new(element);
189            remaining /= modulus.clone();
190        }
191
192        if !remaining.is_zero() {
193            return Err(Self::Error::Overflow);
194        }
195
196        Ok(Digest::new(digest_innards))
197    }
198}
199
200impl From<Digest> for BigUint {
201    fn from(digest: Digest) -> Self {
202        let Digest(digest_innards) = digest;
203        let mut ret = BigUint::zero();
204        let modulus: BigUint = BFieldElement::P.into();
205        for i in (0..Digest::LEN).rev() {
206            ret *= modulus.clone();
207            let digest_element: BigUint = digest_innards[i].value().into();
208            ret += digest_element;
209        }
210
211        ret
212    }
213}
214
215impl Digest {
216    /// Hash this digest using [Tip5], producing a new digest.
217    ///
218    /// A digest can be used as a source of entropy. It can be beneficial or even
219    /// necessary to not reveal the entropy itself, but use it as the seed for
220    /// some deterministic computation. In such cases, hashing the digest using
221    /// this method is probably the right thing to do.
222    /// If the digest in question is used for its entropy only, there might not be a
223    /// known meaningful pre-image for that digest.
224    ///
225    /// This method invokes [`Tip5::hash_pair`] with the right operand being the
226    /// zero digest, agreeing with the standard way to hash a digest in the virtual
227    /// machine.
228    pub fn hash(self) -> Digest {
229        Tip5::hash_pair(self, Self::ALL_ZERO)
230    }
231
232    /// Encode digest as hex.
233    ///
234    /// Since `Digest` also implements [`LowerHex`][lo] and [`UpperHex`][up], it is
235    /// possible to `{:x}`-format directly, _e.g._, `print!("{digest:x}")`.
236    ///
237    /// [lo]: fmt::LowerHex
238    /// [up]: fmt::UpperHex
239    pub fn to_hex(self) -> String {
240        format!("{self:x}")
241    }
242
243    /// Decode hex string to [`Digest`]. Must not include leading “0x”.
244    pub fn try_from_hex(data: impl AsRef<[u8]>) -> Result<Self, TryFromHexDigestError> {
245        let slice = hex::decode(data)?;
246        Ok(Self::try_from(&slice as &[u8])?)
247    }
248}
249
250// we implement Serialize so that we can serialize as hex for human readable
251// formats like JSON but use default serializer for other formats likes bincode
252impl Serialize for Digest {
253    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
254        if serializer.is_human_readable() {
255            self.to_hex().serialize(serializer)
256        } else {
257            self.0.serialize(serializer)
258        }
259    }
260}
261
262// we impl Deserialize so that we can deserialize as hex for human readable
263// formats like JSON but use default deserializer for other formats like bincode
264impl<'de> Deserialize<'de> for Digest {
265    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
266    where
267        D: Deserializer<'de>,
268    {
269        if deserializer.is_human_readable() {
270            let hex_string = String::deserialize(deserializer)?;
271            Self::try_from_hex(hex_string).map_err(serde::de::Error::custom)
272        } else {
273            <[_; _]>::deserialize(deserializer).map(Self::new)
274        }
275    }
276}
277
278#[cfg(test)]
279#[cfg_attr(coverage_nightly, coverage(off))]
280pub(crate) mod tests {
281    use num_traits::One;
282    use proptest::collection::vec;
283    use proptest::prelude::Arbitrary as ProptestArbitrary;
284    use proptest::prelude::*;
285    use proptest_arbitrary_adapter::arb;
286
287    use super::*;
288    use crate::error::ParseBFieldElementError;
289    use crate::prelude::*;
290    use crate::tests::proptest;
291    use crate::tests::test;
292
293    impl ProptestArbitrary for Digest {
294        type Parameters = ();
295        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
296            arb().prop_map(|d| d).no_shrink().boxed()
297        }
298
299        type Strategy = BoxedStrategy<Self>;
300    }
301
302    /// Test helper struct for corrupting digests. Primarily used for negative tests.
303    #[derive(Debug, Clone, PartialEq, Eq, test_strategy::Arbitrary)]
304    pub(crate) struct DigestCorruptor {
305        #[strategy(vec(0..Digest::LEN, 1..=Digest::LEN))]
306        #[filter(#corrupt_indices.iter().all_unique())]
307        corrupt_indices: Vec<usize>,
308
309        #[strategy(vec(arb(), #corrupt_indices.len()))]
310        corrupt_elements: Vec<BFieldElement>,
311    }
312
313    impl DigestCorruptor {
314        pub fn corrupt_digest(&self, digest: Digest) -> Result<Digest, TestCaseError> {
315            let mut corrupt_digest = digest;
316            for (&i, &element) in self.corrupt_indices.iter().zip(&self.corrupt_elements) {
317                corrupt_digest.0[i] = element;
318            }
319            if corrupt_digest == digest {
320                let reject_reason = "corruption must change digest".into();
321                return Err(TestCaseError::Reject(reject_reason));
322            }
323
324            Ok(corrupt_digest)
325        }
326    }
327
328    #[macro_rules_attr::apply(test)]
329    fn digest_corruptor_rejects_uncorrupting_corruption() {
330        let digest = Digest(bfe_array![1, 2, 3, 4, 5]);
331        let corruptor = DigestCorruptor {
332            corrupt_indices: vec![0],
333            corrupt_elements: bfe_vec![1],
334        };
335        let err = corruptor.corrupt_digest(digest).unwrap_err();
336        assert!(matches!(err, TestCaseError::Reject(_)));
337    }
338
339    #[macro_rules_attr::apply(test)]
340    fn display_is_as_expected() {
341        let digest = Digest::new(bfe_array![1, 2, 3, 4, 5]);
342        assert_eq!("1,2,3,4,5", format!("{digest}"));
343
344        let hex_digest =
345            "01000000000000000200000000000000030000000000000004000000000000000500000000000000";
346        assert_eq!(hex_digest, format!("{digest:x}"));
347    }
348
349    #[macro_rules_attr::apply(test)]
350    fn get_size() {
351        let stack = Digest::get_stack_size();
352
353        let bfes = bfe_array![12, 24, 36, 48, 60];
354        let tip5_digest_type_from_array: Digest = Digest::new(bfes);
355        let heap = tip5_digest_type_from_array.get_heap_size();
356        let total = tip5_digest_type_from_array.get_size();
357        println!("stack: {stack} + heap: {heap} = {total}");
358
359        assert_eq!(stack + heap, total)
360    }
361
362    #[macro_rules_attr::apply(test)]
363    fn digest_from_str() {
364        let valid_digest_string = "12063201067205522823,\
365            1529663126377206632,\
366            2090171368883726200,\
367            12975872837767296928,\
368            11492877804687889759";
369        let valid_digest = Digest::from_str(valid_digest_string);
370        assert!(valid_digest.is_ok());
371
372        let invalid_digest_string = "00059361073062755064,05168490802189810700";
373        let invalid_digest = Digest::from_str(invalid_digest_string);
374        assert!(invalid_digest.is_err());
375
376        let second_invalid_digest_string = "this_is_not_a_bfield_element,05168490802189810700";
377        let second_invalid_digest = Digest::from_str(second_invalid_digest_string);
378        assert!(second_invalid_digest.is_err());
379    }
380
381    #[macro_rules_attr::apply(proptest)]
382    fn test_reversed_involution(digest: Digest) {
383        prop_assert_eq!(digest, digest.reversed().reversed())
384    }
385
386    #[macro_rules_attr::apply(test)]
387    fn digest_biguint_conversion_simple_test() {
388        let fourteen: BigUint = 14u128.into();
389        let fourteen_converted_expected = Digest(bfe_array![14, 0, 0, 0, 0]);
390
391        let bfe_max: BigUint = BFieldElement::MAX.into();
392        let bfe_max_converted_expected = Digest(bfe_array![BFieldElement::MAX, 0, 0, 0, 0]);
393
394        let bfe_max_plus_one: BigUint = BFieldElement::P.into();
395        let bfe_max_plus_one_converted_expected = Digest(bfe_array![0, 1, 0, 0, 0]);
396
397        let two_pow_64: BigUint = (1u128 << 64).into();
398        let two_pow_64_converted_expected = Digest(bfe_array![(1u64 << 32) - 1, 1, 0, 0, 0]);
399
400        let two_pow_123: BigUint = (1u128 << 123).into();
401        let two_pow_123_converted_expected =
402            Digest([18446744069280366593, 576460752437641215, 0, 0, 0].map(BFieldElement::new));
403
404        let two_pow_315: BigUint = BigUint::from(2u128).pow(315);
405
406        // Result calculated on Wolfram alpha
407        let two_pow_315_converted_expected = Digest(bfe_array![
408            18446744069280366593_u64,
409            1729382257312923647_u64,
410            13258597298683772929_u64,
411            3458764513015234559_u64,
412            576460752840294400_u64,
413        ]);
414
415        // Verify conversion from BigUint to Digest
416        assert_eq!(
417            fourteen_converted_expected,
418            fourteen.clone().try_into().unwrap()
419        );
420        assert_eq!(
421            bfe_max_converted_expected,
422            bfe_max.clone().try_into().unwrap()
423        );
424        assert_eq!(
425            bfe_max_plus_one_converted_expected,
426            bfe_max_plus_one.clone().try_into().unwrap()
427        );
428        assert_eq!(
429            two_pow_64_converted_expected,
430            two_pow_64.clone().try_into().unwrap()
431        );
432        assert_eq!(
433            two_pow_123_converted_expected,
434            two_pow_123.clone().try_into().unwrap()
435        );
436        assert_eq!(
437            two_pow_315_converted_expected,
438            two_pow_315.clone().try_into().unwrap()
439        );
440
441        // Verify conversion from Digest to BigUint
442        assert_eq!(fourteen, fourteen_converted_expected.into());
443        assert_eq!(bfe_max, bfe_max_converted_expected.into());
444        assert_eq!(bfe_max_plus_one, bfe_max_plus_one_converted_expected.into());
445        assert_eq!(two_pow_64, two_pow_64_converted_expected.into());
446        assert_eq!(two_pow_123, two_pow_123_converted_expected.into());
447        assert_eq!(two_pow_315, two_pow_315_converted_expected.into());
448    }
449
450    #[macro_rules_attr::apply(proptest)]
451    fn digest_biguint_conversion_pbt(components_0: [u64; 4], component_1: u32) {
452        let big_uint = components_0
453            .into_iter()
454            .fold(BigUint::one(), |acc, x| acc * x);
455        let big_uint = big_uint * component_1;
456
457        let as_digest: Digest = big_uint.clone().try_into().unwrap();
458        let big_uint_again: BigUint = as_digest.into();
459        prop_assert_eq!(big_uint, big_uint_again);
460    }
461
462    #[macro_rules_attr::apply(test)]
463    fn digest_ordering() {
464        let val0 = Digest::new(bfe_array![0; Digest::LEN]);
465        let val1 = Digest::new(bfe_array![14, 0, 0, 0, 0]);
466        assert!(val1 > val0);
467
468        let val2 = Digest::new(bfe_array![14; Digest::LEN]);
469        assert!(val2 > val1);
470        assert!(val2 > val0);
471
472        let val3 = Digest::new(bfe_array![15, 14, 14, 14, 14]);
473        assert!(val3 > val2);
474        assert!(val3 > val1);
475        assert!(val3 > val0);
476
477        let val4 = Digest::new(bfe_array![14, 15, 14, 14, 14]);
478        assert!(val4 > val3);
479        assert!(val4 > val2);
480        assert!(val4 > val1);
481        assert!(val4 > val0);
482    }
483
484    #[macro_rules_attr::apply(test)]
485    fn digest_biguint_overflow_test() {
486        let mut two_pow_384: BigUint = (1u128 << 96).into();
487        two_pow_384 = two_pow_384.pow(4);
488        let err = Digest::try_from(two_pow_384).unwrap_err();
489
490        assert_eq!(TryFromDigestError::Overflow, err);
491    }
492
493    #[macro_rules_attr::apply(proptest)]
494    fn digest_to_bfe_vector_involution(digest: Digest) {
495        let bfes = <Vec<BFieldElement>>::from(digest);
496        let digest_again = Digest::try_from(bfes)?;
497        prop_assert_eq!(digest, digest_again);
498    }
499
500    #[macro_rules_attr::apply(proptest)]
501    fn bfe_vector_of_incorrect_length_cannot_become_a_digest(
502        #[filter(#bfes.len() != Digest::LEN)] bfes: Vec<BFieldElement>,
503    ) {
504        let bfes_len = bfes.len();
505        let Err(TryFromDigestError::InvalidLength(len)) = Digest::try_from(bfes) else {
506            return Err(TestCaseError::Fail("expected an error".into()));
507        };
508        prop_assert_eq!(bfes_len, len);
509    }
510
511    #[macro_rules_attr::apply(proptest)]
512    fn forty_bytes_can_be_converted_to_digest(bytes: [u8; Digest::BYTES]) {
513        let digest = Digest::try_from(bytes).unwrap();
514        let bytes_again: [u8; Digest::BYTES] = digest.into();
515        prop_assert_eq!(bytes, bytes_again);
516    }
517
518    // note: for background on this test, see issue 195
519    #[macro_rules_attr::apply(test)]
520    fn try_from_bytes_not_canonical() -> Result<(), TryFromDigestError> {
521        let bytes: [u8; Digest::BYTES] = [255; Digest::BYTES];
522
523        assert!(Digest::try_from(bytes).is_err_and(|e| matches!(
524            e,
525            TryFromDigestError::InvalidBFieldElement(ParseBFieldElementError::NotCanonical(_))
526        )));
527
528        Ok(())
529    }
530
531    // note: for background on this test, see issue 195
532    #[macro_rules_attr::apply(test)]
533    fn from_str_not_canonical() -> Result<(), TryFromDigestError> {
534        let str = format!("0,0,0,0,{}", u64::MAX);
535
536        assert!(Digest::from_str(&str).is_err_and(|e| matches!(
537            e,
538            TryFromDigestError::InvalidBFieldElement(ParseBFieldElementError::NotCanonical(_))
539        )));
540
541        Ok(())
542    }
543
544    #[macro_rules_attr::apply(test)]
545    fn bytes_in_matches_bytes_out() -> Result<(), TryFromDigestError> {
546        let bytes1: [u8; Digest::BYTES] = [254; Digest::BYTES];
547        let d1 = Digest::try_from(bytes1)?;
548
549        let bytes2: [u8; Digest::BYTES] = d1.into();
550        let d2 = Digest::try_from(bytes2)?;
551
552        assert_eq!(d1, d2);
553        assert_eq!(bytes1, bytes2);
554
555        Ok(())
556    }
557
558    #[macro_rules_attr::apply(proptest)]
559    fn any_digest_can_be_hashed(digest: Digest) {
560        digest.hash();
561    }
562
563    mod hex_test {
564        use super::*;
565        use crate::tests::proptest;
566        use crate::tests::test;
567
568        pub(super) fn hex_examples() -> Vec<(Digest, &'static str)> {
569            vec![
570                (
571                    Digest::default(),
572                    concat!(
573                        "0000000000000000000000000000000000000000",
574                        "0000000000000000000000000000000000000000"
575                    ),
576                ),
577                (
578                    Digest::new(bfe_array![0, 1, 10, 15, 255]),
579                    concat!(
580                        "000000000000000001000000000000000a000000",
581                        "000000000f00000000000000ff00000000000000"
582                    ),
583                ),
584                // note: this would result in NotCanonical error. See issue 195
585                // (
586                //     Digest::new(bfe_array![0, 1, 10, 15, 255]),
587                //     concat!("ffffffffffffffffffffffffffffffffffffffff",
588                //             "ffffffffffffffffffffffffffffffffffffffff"),
589                // ),
590            ]
591        }
592
593        #[macro_rules_attr::apply(test)]
594        fn digest_to_hex() {
595            for (digest, hex) in hex_examples() {
596                assert_eq!(&digest.to_hex(), hex);
597            }
598        }
599
600        #[macro_rules_attr::apply(proptest)]
601        fn to_hex_and_from_hex_are_reciprocal_proptest(bytes: [u8; Digest::BYTES]) {
602            let digest = Digest::try_from(bytes).unwrap();
603            let hex = digest.to_hex();
604            let digest_again = Digest::try_from_hex(&hex).unwrap();
605            let hex_again = digest_again.to_hex();
606            prop_assert_eq!(digest, digest_again);
607            prop_assert_eq!(hex, hex_again);
608
609            let lower_hex = format!("{digest:x}");
610            let digest_from_lower_hex = Digest::try_from_hex(lower_hex).unwrap();
611            prop_assert_eq!(digest, digest_from_lower_hex);
612
613            let upper_hex = format!("{digest:X}");
614            let digest_from_upper_hex = Digest::try_from_hex(upper_hex).unwrap();
615            prop_assert_eq!(digest, digest_from_upper_hex);
616        }
617
618        #[macro_rules_attr::apply(test)]
619        fn to_hex_and_from_hex_are_reciprocal() -> Result<(), TryFromHexDigestError> {
620            let hex_vals = vec![
621                "00000000000000000000000000000000000000000000000000000000000000000000000000000000",
622                "10000000000000000000000000000000000000000000000000000000000000000000000000000000",
623                "0000000000000000000000000000000000000000000000000000000000000000000000000000000f",
624                "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
625                // note: all "ff…ff" would result in NotCanonical error. See issue 195
626            ];
627            for hex in hex_vals {
628                let digest = Digest::try_from_hex(hex)?;
629                assert_eq!(hex, &digest.to_hex())
630            }
631            Ok(())
632        }
633
634        #[macro_rules_attr::apply(test)]
635        fn digest_from_hex() -> Result<(), TryFromHexDigestError> {
636            for (digest, hex) in hex_examples() {
637                assert_eq!(digest, Digest::try_from_hex(hex)?);
638            }
639
640            Ok(())
641        }
642
643        #[macro_rules_attr::apply(test)]
644        fn digest_from_invalid_hex_errors() {
645            use hex::FromHexError;
646
647            assert!(Digest::try_from_hex("taco").is_err_and(|e| matches!(
648                e,
649                TryFromHexDigestError::HexDecode(FromHexError::InvalidHexCharacter { .. })
650            )));
651
652            assert!(Digest::try_from_hex("0").is_err_and(|e| matches!(
653                e,
654                TryFromHexDigestError::HexDecode(FromHexError::OddLength)
655            )));
656
657            assert!(Digest::try_from_hex("00").is_err_and(|e| matches!(
658                e,
659                TryFromHexDigestError::Digest(TryFromDigestError::InvalidLength(_))
660            )));
661
662            // NotCanonical error. See issue 195
663            assert!(Digest::try_from_hex(
664                "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
665            )
666            .is_err_and(|e| matches!(
667                e,
668                TryFromHexDigestError::Digest(TryFromDigestError::InvalidBFieldElement(
669                    ParseBFieldElementError::NotCanonical(_)
670                ))
671            )));
672        }
673    }
674
675    mod serde_test {
676        use super::hex_test::hex_examples;
677        use super::*;
678
679        mod json_test {
680            use super::*;
681            use crate::tests::test;
682
683            #[macro_rules_attr::apply(test)]
684            fn serialize() -> Result<(), serde_json::Error> {
685                for (digest, hex) in hex_examples() {
686                    assert_eq!(serde_json::to_string(&digest)?, format!("\"{hex}\""));
687                }
688                Ok(())
689            }
690
691            #[macro_rules_attr::apply(test)]
692            fn deserialize() -> Result<(), serde_json::Error> {
693                for (digest, hex) in hex_examples() {
694                    let json_hex = format!("\"{hex}\"");
695                    let digest_deserialized: Digest = serde_json::from_str::<Digest>(&json_hex)?;
696                    assert_eq!(digest_deserialized, digest);
697                }
698                Ok(())
699            }
700        }
701
702        mod bincode_test {
703            use super::*;
704            use crate::tests::test;
705
706            fn bincode_examples() -> Vec<(Digest, [u8; Digest::BYTES])> {
707                vec![
708                    (Digest::default(), [0u8; Digest::BYTES]),
709                    (
710                        Digest::new(bfe_array![0, 1, 10, 15, 255]),
711                        [
712                            0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0,
713                            0, 15, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0,
714                        ],
715                    ),
716                ]
717            }
718
719            #[macro_rules_attr::apply(test)]
720            fn serialize() {
721                for (digest, bytes) in bincode_examples() {
722                    assert_eq!(bincode::serialize(&digest).unwrap(), bytes);
723                }
724            }
725
726            #[macro_rules_attr::apply(test)]
727            fn deserialize() {
728                for (digest, bytes) in bincode_examples() {
729                    assert_eq!(bincode::deserialize::<Digest>(&bytes).unwrap(), digest);
730                }
731            }
732        }
733    }
734}