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