Skip to main content

iota_sdk_types/
digest.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5/// A 32-byte Blake2b256 hash output.
6///
7/// # BCS
8///
9/// A `Digest`'s BCS serialized form is defined by the following:
10///
11/// ```text
12/// digest = %d32 32OCTET
13/// ```
14///
15/// Due to historical reasons, even though a `Digest` has a fixed-length of 32,
16/// IOTA's binary representation of a `Digest` is prefixed with its length
17/// meaning its serialized binary form (in bcs) is 33 bytes long vs a more
18/// compact 32 bytes.
19#[derive(Clone, Copy, Default, derive_more::Deref, Eq, Hash, Ord, PartialEq, PartialOrd)]
20#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
21#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
22#[cfg_attr(
23    feature = "bcs-schema",
24    derive(iota_bcs_schema::BcsSchema),
25    bcs_schema(definition = "%d32 32OCTET")
26)]
27pub struct Digest(
28    #[cfg_attr(feature = "serde", serde(with = "DigestSerialization"))] [u8; Self::LENGTH],
29);
30
31impl Digest {
32    /// A constant representing the length of a digest in bytes.
33    pub const LENGTH: usize = 32;
34
35    /// A constant representing a zero digest.
36    pub const ZERO: Self = Self([0; Self::LENGTH]);
37
38    /// The lexicographically minimum digest
39    pub const MIN: Self = Self([u8::MIN; 32]);
40
41    /// The lexicographically maximum digest
42    pub const MAX: Self = Self([u8::MAX; 32]);
43
44    /// Generates a new digest from the provided 32 byte array containing [`u8`]
45    /// values.
46    pub const fn new(digest: [u8; Self::LENGTH]) -> Self {
47        Self(digest)
48    }
49
50    /// Generates a new digest from the provided random number generator.
51    #[cfg(feature = "rand")]
52    #[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
53    pub fn random_with<R>(mut rng: R) -> Self
54    where
55        R: rand_core::RngCore + rand_core::CryptoRng,
56    {
57        let mut buf: [u8; Self::LENGTH] = [0; Self::LENGTH];
58        rng.fill_bytes(&mut buf);
59        Self::new(buf)
60    }
61
62    #[cfg(feature = "rand")]
63    #[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
64    pub fn random() -> Self {
65        Self::random_with(rand_core::OsRng)
66    }
67
68    /// Returns a slice to the inner array representation of this digest.
69    pub const fn inner(&self) -> &[u8; Self::LENGTH] {
70        &self.0
71    }
72
73    /// Returns the inner array representation of this digest.
74    pub const fn into_inner(self) -> [u8; Self::LENGTH] {
75        self.0
76    }
77
78    /// Returns a slice of bytes representing the digest.
79    pub const fn as_bytes(&self) -> &[u8] {
80        &self.0
81    }
82
83    /// Decodes a digest from a Base58 encoded string.
84    pub fn from_base58<T: AsRef<[u8]>>(base58: T) -> Result<Self, DigestParseError> {
85        Self::from_bytes(bs58::decode(base58).into_vec()?)
86    }
87
88    /// Returns a Base58 encoded string representation of this digest.
89    pub fn to_base58(&self) -> String {
90        self.to_string()
91    }
92
93    /// Generates a digest from bytes.
94    pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, DigestParseError> {
95        let bytes = bytes.as_ref();
96        <[u8; Self::LENGTH]>::try_from(bytes)
97            .map_err(|_| DigestParseError::InvalidByteLength {
98                actual: bytes.len(),
99            })
100            .map(Self)
101    }
102
103    /// Returns the next digest in byte-increasing order.
104    pub const fn next_lexicographical(&self) -> Self {
105        Self(crate::next_lexicographical_array(&self.0))
106    }
107
108    /// Returns the next digest in byte-increasing order, or `None` if the
109    /// result would overflow.
110    pub const fn next_lexicographical_opt(&self) -> Option<Self> {
111        match crate::next_lexicographical_array_opt(&self.0) {
112            Some(val) => Some(Self(val)),
113            None => None,
114        }
115    }
116}
117
118impl std::str::FromStr for Digest {
119    type Err = DigestParseError;
120
121    fn from_str(s: &str) -> Result<Self, Self::Err> {
122        Self::from_base58(s)
123    }
124}
125
126impl AsRef<[u8]> for Digest {
127    fn as_ref(&self) -> &[u8] {
128        &self.0
129    }
130}
131
132impl AsRef<[u8; Self::LENGTH]> for Digest {
133    fn as_ref(&self) -> &[u8; Self::LENGTH] {
134        &self.0
135    }
136}
137
138impl From<Digest> for [u8; Digest::LENGTH] {
139    fn from(digest: Digest) -> Self {
140        digest.into_inner()
141    }
142}
143
144impl From<[u8; Self::LENGTH]> for Digest {
145    fn from(digest: [u8; Self::LENGTH]) -> Self {
146        Self::new(digest)
147    }
148}
149
150impl PartialEq<[u8; Self::LENGTH]> for Digest {
151    fn eq(&self, other: &[u8; Self::LENGTH]) -> bool {
152        &self.0 == other
153    }
154}
155
156impl PartialEq<Digest> for [u8; Digest::LENGTH] {
157    fn eq(&self, other: &Digest) -> bool {
158        self == &other.0
159    }
160}
161
162impl PartialEq<Digest> for &[u8] {
163    fn eq(&self, other: &Digest) -> bool {
164        *self == other.0.as_slice()
165    }
166}
167
168impl PartialEq<&[u8]> for Digest {
169    fn eq(&self, other: &&[u8]) -> bool {
170        self.0.as_slice() == *other
171    }
172}
173
174impl PartialEq<Vec<u8>> for Digest {
175    fn eq(&self, other: &Vec<u8>) -> bool {
176        self.0.as_slice() == other.as_slice()
177    }
178}
179
180impl PartialEq<Digest> for Vec<u8> {
181    fn eq(&self, other: &Digest) -> bool {
182        self.as_slice() == other.0.as_slice()
183    }
184}
185
186impl std::fmt::Display for Digest {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        // output size is determined via the following formula:
189        //      N * log(256) / log(58) + 1 (round up)
190        // where N = 32 this results in a value of 45
191        let mut buf = [0; 45];
192
193        let len = bs58::encode(&self.0).onto(&mut buf[..]).unwrap();
194        let encoded = std::str::from_utf8(&buf[..len]).unwrap();
195
196        f.write_str(encoded)
197    }
198}
199
200impl std::fmt::Debug for Digest {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        f.debug_tuple("Digest")
203            .field(&format_args!("\"{self}\""))
204            .finish()
205    }
206}
207
208impl std::fmt::LowerHex for Digest {
209    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210        if f.alternate() {
211            write!(f, "0x")?;
212        }
213
214        for byte in self.0 {
215            write!(f, "{byte:02x}")?;
216        }
217
218        Ok(())
219    }
220}
221
222impl std::fmt::UpperHex for Digest {
223    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224        if f.alternate() {
225            write!(f, "0x")?;
226        }
227
228        for byte in self.0 {
229            write!(f, "{byte:02X}")?;
230        }
231
232        Ok(())
233    }
234}
235
236// Unfortunately IOTA's binary representation of digests is prefixed with its
237// length meaning its serialized binary form is 33 bytes long (in bcs) vs a more
238// compact 32 bytes.
239#[cfg(feature = "serde")]
240type DigestSerialization =
241    ::serde_with::As<::serde_with::IfIsHumanReadable<ReadableDigest, ::serde_with::Bytes>>;
242
243#[cfg(feature = "serde")]
244#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
245struct ReadableDigest;
246
247#[cfg(feature = "serde")]
248#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
249impl serde_with::SerializeAs<[u8; Digest::LENGTH]> for ReadableDigest {
250    fn serialize_as<S>(source: &[u8; Digest::LENGTH], serializer: S) -> Result<S::Ok, S::Error>
251    where
252        S: serde::Serializer,
253    {
254        let digest = Digest::new(*source);
255        serde_with::DisplayFromStr::serialize_as(&digest, serializer)
256    }
257}
258
259#[cfg(feature = "serde")]
260#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
261impl<'de> serde_with::DeserializeAs<'de, [u8; Digest::LENGTH]> for ReadableDigest {
262    fn deserialize_as<D>(deserializer: D) -> Result<[u8; Digest::LENGTH], D::Error>
263    where
264        D: serde::Deserializer<'de>,
265    {
266        let digest: Digest = serde_with::DisplayFromStr::deserialize_as(deserializer)?;
267        Ok(digest.into_inner())
268    }
269}
270
271#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
272#[non_exhaustive]
273pub enum DigestParseError {
274    #[error("digest must be Base58 string of length 44")]
275    Base58(#[from] bs58::decode::Error),
276    #[error(
277        "invalid digest byte length: expected {}, got {actual}",
278        Digest::LENGTH
279    )]
280    InvalidByteLength { actual: usize },
281}
282
283// Don't implement like the other digest type since this isn't intended to be
284// serialized
285pub type SigningDigest = [u8; Digest::LENGTH];
286
287/// Defines a domain-specific newtype wrapper around [`Digest`].
288///
289/// Each wrapper has the same 32-byte representation and BCS/JSON form as a bare
290/// [`Digest`] (it is a transparent newtype), but is a distinct type so that,
291/// for example, a [`TransactionDigest`] can't be mixed up with an
292/// [`ObjectDigest`]. The shared, domain-agnostic surface (construction,
293/// encoding, ordering) is generated here; domain-specific behavior is added in
294/// separate `impl` blocks.
295macro_rules! impl_digest_wrapper {
296    ($(#[$meta:meta])* $name:ident) => {
297        $(#[$meta])*
298        #[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
299        #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
300        #[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
301        #[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
302        pub struct $name(Digest);
303
304        impl $name {
305            /// A constant representing the length of the digest in bytes.
306            pub const LENGTH: usize = Digest::LENGTH;
307
308            /// A constant representing a zero digest.
309            pub const ZERO: Self = Self(Digest::ZERO);
310
311            /// The lexicographically minimum digest.
312            pub const MIN: Self = Self(Digest::MIN);
313
314            /// The lexicographically maximum digest.
315            pub const MAX: Self = Self(Digest::MAX);
316
317            /// Generates a new digest from the provided 32 byte array.
318            pub const fn new(digest: [u8; Self::LENGTH]) -> Self {
319                Self(Digest::new(digest))
320            }
321
322            /// Generates a new digest from the provided random number generator.
323            #[cfg(feature = "rand")]
324            #[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
325            pub fn random_with<R>(rng: R) -> Self
326            where
327                R: rand_core::RngCore + rand_core::CryptoRng,
328            {
329                Self(Digest::random_with(rng))
330            }
331
332            /// Generates a new random digest.
333            #[cfg(feature = "rand")]
334            #[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
335            pub fn random() -> Self {
336                Self(Digest::random())
337            }
338
339            /// Returns a reference to the underlying [`Digest`].
340            pub const fn as_digest(&self) -> &Digest {
341                &self.0
342            }
343
344            /// Returns the underlying [`Digest`].
345            pub const fn into_digest(self) -> Digest {
346                self.0
347            }
348
349            /// Returns a reference to the inner array representation of this digest.
350            pub const fn inner(&self) -> &[u8; Self::LENGTH] {
351                self.0.inner()
352            }
353
354            /// Returns the inner array representation of this digest.
355            pub const fn into_inner(self) -> [u8; Self::LENGTH] {
356                self.0.into_inner()
357            }
358
359            /// Returns a slice of bytes representing the digest.
360            pub const fn as_bytes(&self) -> &[u8] {
361                self.0.as_bytes()
362            }
363
364            /// Decodes a digest from a Base58 encoded string.
365            pub fn from_base58<T: AsRef<[u8]>>(base58: T) -> Result<Self, DigestParseError> {
366                Digest::from_base58(base58).map(Self)
367            }
368
369            /// Returns a Base58 encoded string representation of this digest.
370            pub fn to_base58(&self) -> String {
371                self.0.to_base58()
372            }
373
374            /// Generates a digest from bytes.
375            pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, DigestParseError> {
376                Digest::from_bytes(bytes).map(Self)
377            }
378
379            /// Returns the next digest in byte-increasing order.
380            pub const fn next_lexicographical(&self) -> Self {
381                Self(self.0.next_lexicographical())
382            }
383
384            /// Returns the next digest in byte-increasing order, or `None` if the
385            /// result would overflow.
386            pub const fn next_lexicographical_opt(&self) -> Option<Self> {
387                match self.0.next_lexicographical_opt() {
388                    Some(val) => Some(Self(val)),
389                    None => None,
390                }
391            }
392        }
393
394        impl std::str::FromStr for $name {
395            type Err = DigestParseError;
396
397            fn from_str(s: &str) -> Result<Self, Self::Err> {
398                Self::from_base58(s)
399            }
400        }
401
402        impl AsRef<[u8]> for $name {
403            fn as_ref(&self) -> &[u8] {
404                self.0.as_bytes()
405            }
406        }
407
408        impl AsRef<[u8; Self::LENGTH]> for $name {
409            fn as_ref(&self) -> &[u8; Self::LENGTH] {
410                self.0.inner()
411            }
412        }
413
414        impl From<Digest> for $name {
415            fn from(digest: Digest) -> Self {
416                Self(digest)
417            }
418        }
419
420        impl From<$name> for Digest {
421            fn from(digest: $name) -> Self {
422                digest.0
423            }
424        }
425
426        impl From<[u8; Self::LENGTH]> for $name {
427            fn from(digest: [u8; Self::LENGTH]) -> Self {
428                Self::new(digest)
429            }
430        }
431
432        impl From<$name> for [u8; Digest::LENGTH] {
433            fn from(digest: $name) -> Self {
434                digest.into_inner()
435            }
436        }
437
438        impl std::fmt::Display for $name {
439            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
440                std::fmt::Display::fmt(&self.0, f)
441            }
442        }
443
444        impl std::fmt::Debug for $name {
445            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
446                f.debug_tuple(stringify!($name))
447                    .field(&format_args!("\"{}\"", self.0))
448                    .finish()
449            }
450        }
451
452        impl std::fmt::LowerHex for $name {
453            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454                std::fmt::LowerHex::fmt(&self.0, f)
455            }
456        }
457
458        impl std::fmt::UpperHex for $name {
459            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
460                std::fmt::UpperHex::fmt(&self.0, f)
461            }
462        }
463    };
464}
465
466impl_digest_wrapper! {
467    /// The digest of a `CheckpointSummary`.
468    CheckpointDigest
469}
470
471impl_digest_wrapper! {
472    /// The digest of a `CheckpointContents`.
473    CheckpointContentsDigest
474}
475
476impl_digest_wrapper! {
477    /// The digest of a certificate.
478    CertificateDigest
479}
480
481impl_digest_wrapper! {
482    /// The digest of `SenderSignedData`.
483    SenderSignedDataDigest
484}
485
486impl_digest_wrapper! {
487    /// The digest of a `Transaction`.
488    TransactionDigest
489}
490
491impl_digest_wrapper! {
492    /// The digest of `TransactionEffects`.
493    TransactionEffectsDigest
494}
495
496impl_digest_wrapper! {
497    /// The digest of `TransactionEvents`.
498    TransactionEventsDigest
499}
500
501impl_digest_wrapper! {
502    /// The digest of the auxiliary data associated with `TransactionEffects`.
503    EffectsAuxDataDigest
504}
505
506impl_digest_wrapper! {
507    /// The digest of an `Object`.
508    ObjectDigest
509}
510
511impl_digest_wrapper! {
512    /// The digest of a consensus commit.
513    ConsensusCommitDigest
514}
515
516impl_digest_wrapper! {
517    /// The digest of a misbehavior report.
518    MisbehaviorReportDigest
519}
520
521impl_digest_wrapper! {
522    /// The digest of a `MoveAuthenticator`.
523    MoveAuthenticatorDigest
524}
525
526const OBJECT_DIGEST_DELETED_BYTE_VAL: u8 = 99;
527const OBJECT_DIGEST_WRAPPED_BYTE_VAL: u8 = 88;
528const OBJECT_DIGEST_CANCELED_BYTE_VAL: u8 = 77;
529
530impl ObjectDigest {
531    /// A marker that signifies the object is deleted.
532    pub const OBJECT_DELETED: Self = Self(Digest::new([OBJECT_DIGEST_DELETED_BYTE_VAL; 32]));
533
534    /// A marker that signifies the object is wrapped into another object.
535    pub const OBJECT_WRAPPED: Self = Self(Digest::new([OBJECT_DIGEST_WRAPPED_BYTE_VAL; 32]));
536
537    /// A marker that signifies the object is canceled.
538    pub const OBJECT_CANCELED: Self = Self(Digest::new([OBJECT_DIGEST_CANCELED_BYTE_VAL; 32]));
539
540    /// Returns whether the digest represents an object that is neither deleted
541    /// nor wrapped.
542    pub fn is_alive(&self) -> bool {
543        !self.is_deleted() && !self.is_wrapped()
544    }
545
546    /// Returns whether the digest represents a deleted object.
547    pub fn is_deleted(&self) -> bool {
548        *self == Self::OBJECT_DELETED
549    }
550
551    /// Returns whether the digest represents an object wrapped in another
552    /// object.
553    pub fn is_wrapped(&self) -> bool {
554        *self == Self::OBJECT_WRAPPED
555    }
556}
557
558impl TransactionDigest {
559    /// A digest used to signify the parent transaction was the genesis.
560    /// Note that this is not the same as the digest of the genesis transaction,
561    /// which cannot be known ahead of time.
562    pub const GENESIS_MARKER: Self = Self::ZERO;
563
564    /// Returns the digest used to signify the parent transaction was the
565    /// genesis.
566    pub const fn genesis_marker() -> Self {
567        Self::GENESIS_MARKER
568    }
569}
570
571#[cfg(all(test, feature = "proptest"))]
572mod tests {
573    use test_strategy::proptest;
574
575    use super::*;
576
577    #[proptest]
578    fn roundtrip_display_fromstr(digest: Digest) {
579        let s = digest.to_string();
580        let d = s.parse::<Digest>().unwrap();
581        assert_eq!(digest, d);
582    }
583
584    #[test]
585    fn parse_valid_base58() {
586        // A valid Base58 encoded digest (32 bytes)
587        let digest = Digest::new([1u8; 32]);
588        let base58 = digest.to_base58();
589        let parsed = Digest::from_base58(&base58).unwrap();
590        assert_eq!(digest, parsed);
591    }
592
593    #[test]
594    fn parse_invalid_base58_characters() {
595        // '0', 'O', 'I', 'l' are not valid Base58 characters
596        let result = Digest::from_base58("0OIl");
597        assert_eq!(
598            result,
599            Err(DigestParseError::Base58(
600                bs58::decode::Error::InvalidCharacter {
601                    character: '0',
602                    index: 0
603                }
604            ))
605        );
606    }
607
608    #[test]
609    fn parse_empty_string() {
610        let result = Digest::from_base58("");
611        assert_eq!(
612            result,
613            Err(DigestParseError::InvalidByteLength { actual: 0 })
614        );
615    }
616
617    #[test]
618    fn parse_too_short_base58() {
619        // This decodes to fewer than 32 bytes
620        let result = Digest::from_base58("abc");
621        assert_eq!(
622            result,
623            Err(DigestParseError::InvalidByteLength { actual: 3 })
624        );
625    }
626
627    #[test]
628    fn parse_too_long_base58() {
629        // Create a string that would decode to more than 32 bytes
630        let long_base58 = "1".repeat(100);
631        let result = Digest::from_base58(&long_base58);
632        assert_eq!(
633            result,
634            Err(DigestParseError::InvalidByteLength { actual: 100 })
635        );
636    }
637
638    #[test]
639    fn from_bytes_valid() {
640        let bytes = [42u8; 32];
641        let digest = Digest::from_bytes(bytes).unwrap();
642        assert_eq!(digest.into_inner(), bytes);
643    }
644
645    #[test]
646    fn from_bytes_too_short() {
647        let bytes = [1u8; 31];
648        let result = Digest::from_bytes(bytes);
649        assert_eq!(
650            result,
651            Err(DigestParseError::InvalidByteLength { actual: 31 })
652        );
653    }
654
655    #[test]
656    fn from_bytes_too_long() {
657        let bytes = [1u8; 33];
658        let result = Digest::from_bytes(bytes);
659        assert_eq!(
660            result,
661            Err(DigestParseError::InvalidByteLength { actual: 33 })
662        );
663    }
664
665    #[test]
666    fn partial_eq_array_u8() {
667        let digest = Digest::new([1u8; 32]);
668        let matching = [1u8; 32];
669        let non_matching = [2u8; 32];
670
671        assert_eq!(digest, matching);
672        assert_eq!(matching, digest);
673        assert_ne!(digest, non_matching);
674        assert_ne!(non_matching, digest);
675    }
676
677    #[test]
678    fn partial_eq_vec_u8() {
679        let digest = Digest::new([1u8; 32]);
680        let matching = vec![1u8; 32];
681        let non_matching = vec![2u8; 32];
682        let wrong_length = vec![1u8; 31];
683
684        assert_eq!(digest, matching);
685        assert_eq!(matching, digest);
686        assert_ne!(digest, non_matching);
687        assert_ne!(non_matching, digest);
688        assert_ne!(digest, wrong_length);
689        assert_ne!(wrong_length, digest);
690    }
691
692    #[test]
693    fn from_bytes_empty() {
694        let bytes: [u8; 0] = [];
695        let result = Digest::from_bytes(bytes);
696        assert_eq!(
697            result,
698            Err(DigestParseError::InvalidByteLength { actual: 0 })
699        );
700    }
701
702    #[cfg(feature = "serde")]
703    #[proptest]
704    fn wrappers_serialize_like_digest(digest: Digest) {
705        // Each wrapper is a transparent newtype, so its BCS and JSON forms must
706        // be byte-for-byte identical to the underlying `Digest`.
707        macro_rules! assert_transparent {
708            ($wrapper:ty) => {{
709                let wrapped = <$wrapper>::from(digest);
710                assert_eq!(
711                    bcs::to_bytes(&wrapped).unwrap(),
712                    bcs::to_bytes(&digest).unwrap()
713                );
714                assert_eq!(
715                    serde_json::to_string(&wrapped).unwrap(),
716                    serde_json::to_string(&digest).unwrap()
717                );
718            }};
719        }
720
721        assert_transparent!(CheckpointDigest);
722        assert_transparent!(CheckpointContentsDigest);
723        assert_transparent!(CertificateDigest);
724        assert_transparent!(SenderSignedDataDigest);
725        assert_transparent!(TransactionDigest);
726        assert_transparent!(TransactionEffectsDigest);
727        assert_transparent!(TransactionEventsDigest);
728        assert_transparent!(EffectsAuxDataDigest);
729        assert_transparent!(ObjectDigest);
730        assert_transparent!(ConsensusCommitDigest);
731        assert_transparent!(MisbehaviorReportDigest);
732        assert_transparent!(MoveAuthenticatorDigest);
733    }
734
735    #[test]
736    fn object_digest_markers() {
737        assert!(ObjectDigest::OBJECT_DELETED.is_deleted());
738        assert!(!ObjectDigest::OBJECT_DELETED.is_alive());
739        assert!(ObjectDigest::OBJECT_WRAPPED.is_wrapped());
740        assert!(!ObjectDigest::OBJECT_WRAPPED.is_alive());
741        assert!(ObjectDigest::ZERO.is_alive());
742        // `is_alive` only accounts for deleted/wrapped objects, so a canceled
743        // marker is still considered alive.
744        assert!(ObjectDigest::OBJECT_CANCELED.is_alive());
745    }
746
747    #[test]
748    fn transaction_digest_genesis_marker() {
749        assert_eq!(TransactionDigest::genesis_marker(), TransactionDigest::ZERO);
750    }
751}