1#[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 pub const LENGTH: usize = 32;
34
35 pub const ZERO: Self = Self([0; Self::LENGTH]);
37
38 pub const MIN: Self = Self([u8::MIN; 32]);
40
41 pub const MAX: Self = Self([u8::MAX; 32]);
43
44 pub const fn new(digest: [u8; Self::LENGTH]) -> Self {
47 Self(digest)
48 }
49
50 #[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 pub const fn inner(&self) -> &[u8; Self::LENGTH] {
70 &self.0
71 }
72
73 pub const fn into_inner(self) -> [u8; Self::LENGTH] {
75 self.0
76 }
77
78 pub const fn as_bytes(&self) -> &[u8] {
80 &self.0
81 }
82
83 pub fn from_base58<T: AsRef<[u8]>>(base58: T) -> Result<Self, DigestParseError> {
85 Self::from_bytes(bs58::decode(base58).into_vec()?)
86 }
87
88 pub fn to_base58(&self) -> String {
90 self.to_string()
91 }
92
93 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 pub const fn next_lexicographical(&self) -> Self {
105 Self(crate::next_lexicographical_array(&self.0))
106 }
107
108 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 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#[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
283pub type SigningDigest = [u8; Digest::LENGTH];
286
287macro_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 pub const LENGTH: usize = Digest::LENGTH;
307
308 pub const ZERO: Self = Self(Digest::ZERO);
310
311 pub const MIN: Self = Self(Digest::MIN);
313
314 pub const MAX: Self = Self(Digest::MAX);
316
317 pub const fn new(digest: [u8; Self::LENGTH]) -> Self {
319 Self(Digest::new(digest))
320 }
321
322 #[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 #[cfg(feature = "rand")]
334 #[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
335 pub fn random() -> Self {
336 Self(Digest::random())
337 }
338
339 pub const fn as_digest(&self) -> &Digest {
341 &self.0
342 }
343
344 pub const fn into_digest(self) -> Digest {
346 self.0
347 }
348
349 pub const fn inner(&self) -> &[u8; Self::LENGTH] {
351 self.0.inner()
352 }
353
354 pub const fn into_inner(self) -> [u8; Self::LENGTH] {
356 self.0.into_inner()
357 }
358
359 pub const fn as_bytes(&self) -> &[u8] {
361 self.0.as_bytes()
362 }
363
364 pub fn from_base58<T: AsRef<[u8]>>(base58: T) -> Result<Self, DigestParseError> {
366 Digest::from_base58(base58).map(Self)
367 }
368
369 pub fn to_base58(&self) -> String {
371 self.0.to_base58()
372 }
373
374 pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, DigestParseError> {
376 Digest::from_bytes(bytes).map(Self)
377 }
378
379 pub const fn next_lexicographical(&self) -> Self {
381 Self(self.0.next_lexicographical())
382 }
383
384 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 CheckpointDigest
469}
470
471impl_digest_wrapper! {
472 CheckpointContentsDigest
474}
475
476impl_digest_wrapper! {
477 CertificateDigest
479}
480
481impl_digest_wrapper! {
482 SenderSignedDataDigest
484}
485
486impl_digest_wrapper! {
487 TransactionDigest
489}
490
491impl_digest_wrapper! {
492 TransactionEffectsDigest
494}
495
496impl_digest_wrapper! {
497 TransactionEventsDigest
499}
500
501impl_digest_wrapper! {
502 EffectsAuxDataDigest
504}
505
506impl_digest_wrapper! {
507 ObjectDigest
509}
510
511impl_digest_wrapper! {
512 ConsensusCommitDigest
514}
515
516impl_digest_wrapper! {
517 MisbehaviorReportDigest
519}
520
521impl_digest_wrapper! {
522 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 pub const OBJECT_DELETED: Self = Self(Digest::new([OBJECT_DIGEST_DELETED_BYTE_VAL; 32]));
533
534 pub const OBJECT_WRAPPED: Self = Self(Digest::new([OBJECT_DIGEST_WRAPPED_BYTE_VAL; 32]));
536
537 pub const OBJECT_CANCELED: Self = Self(Digest::new([OBJECT_DIGEST_CANCELED_BYTE_VAL; 32]));
539
540 pub fn is_alive(&self) -> bool {
543 !self.is_deleted() && !self.is_wrapped()
544 }
545
546 pub fn is_deleted(&self) -> bool {
548 *self == Self::OBJECT_DELETED
549 }
550
551 pub fn is_wrapped(&self) -> bool {
554 *self == Self::OBJECT_WRAPPED
555 }
556}
557
558impl TransactionDigest {
559 pub const GENESIS_MARKER: Self = Self::ZERO;
563
564 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 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 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 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 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 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 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}