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