1use std::{env, fmt};
5
6use crate::{
7 error::{SuiError, SuiErrorKind, SuiResult},
8 sui_serde::Readable,
9};
10use fastcrypto::encoding::{Base58, Encoding, Hex};
11use fastcrypto::hash::{Blake2b256, HashFunction};
12use once_cell::sync::{Lazy, OnceCell};
13use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15use serde_with::{Bytes, serde_as};
16use sui_protocol_config::Chain;
17use tracing::info;
18
19#[serde_as]
21#[derive(
22 Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
23)]
24pub struct Digest(
25 #[schemars(with = "Base58")]
26 #[serde_as(as = "Readable<Base58, Bytes>")]
27 [u8; 32],
28);
29
30impl Digest {
31 pub const ZERO: Self = Digest([0; 32]);
32
33 pub const fn new(digest: [u8; 32]) -> Self {
34 Self(digest)
35 }
36
37 pub fn generate<R: rand::RngCore + rand::CryptoRng>(mut rng: R) -> Self {
38 let mut bytes = [0; 32];
39 rng.fill_bytes(&mut bytes);
40 Self(bytes)
41 }
42
43 pub fn random() -> Self {
44 Self::generate(rand::thread_rng())
45 }
46
47 pub const fn inner(&self) -> &[u8; 32] {
48 &self.0
49 }
50
51 pub const fn into_inner(self) -> [u8; 32] {
52 self.0
53 }
54
55 pub fn next_lexicographical(&self) -> Option<Self> {
56 let mut next_digest = *self;
57 let pos = next_digest.0.iter().rposition(|&byte| byte != 255)?;
58 next_digest.0[pos] += 1;
59 next_digest
60 .0
61 .iter_mut()
62 .skip(pos + 1)
63 .for_each(|byte| *byte = 0);
64 Some(next_digest)
65 }
66}
67
68impl AsRef<[u8]> for Digest {
69 fn as_ref(&self) -> &[u8] {
70 &self.0
71 }
72}
73
74impl AsRef<[u8; 32]> for Digest {
75 fn as_ref(&self) -> &[u8; 32] {
76 &self.0
77 }
78}
79
80impl From<Digest> for [u8; 32] {
81 fn from(digest: Digest) -> Self {
82 digest.into_inner()
83 }
84}
85
86impl From<[u8; 32]> for Digest {
87 fn from(digest: [u8; 32]) -> Self {
88 Self::new(digest)
89 }
90}
91
92impl TryFrom<Vec<u8>> for Digest {
93 type Error = SuiError;
94
95 fn try_from(bytes: Vec<u8>) -> Result<Self, SuiError> {
96 let bytes: [u8; 32] =
97 <[u8; 32]>::try_from(&bytes[..]).map_err(|_| SuiErrorKind::InvalidDigestLength {
98 expected: 32,
99 actual: bytes.len(),
100 })?;
101
102 Ok(Self::from(bytes))
103 }
104}
105
106impl fmt::Display for Digest {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 f.write_str(&Base58::encode(self.0))
110 }
111}
112
113impl fmt::Debug for Digest {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 fmt::Display::fmt(self, f)
116 }
117}
118
119impl fmt::LowerHex for Digest {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 if f.alternate() {
122 write!(f, "0x")?;
123 }
124
125 for byte in self.0 {
126 write!(f, "{:02x}", byte)?;
127 }
128
129 Ok(())
130 }
131}
132
133impl fmt::UpperHex for Digest {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 if f.alternate() {
136 write!(f, "0x")?;
137 }
138
139 for byte in self.0 {
140 write!(f, "{:02X}", byte)?;
141 }
142
143 Ok(())
144 }
145}
146
147#[derive(
149 Clone,
150 Copy,
151 Debug,
152 Default,
153 PartialEq,
154 Eq,
155 PartialOrd,
156 Ord,
157 Hash,
158 Serialize,
159 Deserialize,
160 JsonSchema,
161)]
162pub struct ChainIdentifier(CheckpointDigest);
163
164pub const MAINNET_CHAIN_IDENTIFIER_BASE58: &str = "4btiuiMPvEENsttpZC7CZ53DruC3MAgfznDbASZ7DR6S";
165pub const TESTNET_CHAIN_IDENTIFIER_BASE58: &str = "69WiPg3DAQiwdxfncX6wYQ2siKwAe6L9BZthQea3JNMD";
166
167pub static MAINNET_CHAIN_IDENTIFIER: OnceCell<ChainIdentifier> = OnceCell::new();
168pub static TESTNET_CHAIN_IDENTIFIER: OnceCell<ChainIdentifier> = OnceCell::new();
169
170pub const SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE_ENV_VAR_NAME: &str =
173 "SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE";
174
175static SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE: Lazy<Option<Chain>> = Lazy::new(|| {
176 if let Ok(s) = env::var(SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE_ENV_VAR_NAME) {
177 info!("SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE: {:?}", s);
178 match s.as_str() {
179 "mainnet" => Some(Chain::Mainnet),
180 "testnet" => Some(Chain::Testnet),
181 "" => None,
182 _ => panic!("unrecognized SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE: {s:?}"),
183 }
184 } else {
185 None
186 }
187});
188
189impl ChainIdentifier {
190 pub fn from_chain_short_id(short_id: &String) -> Option<Self> {
193 if Hex::from_bytes(&Base58::decode(MAINNET_CHAIN_IDENTIFIER_BASE58).ok()?)
194 .encoded_with_format()
195 .starts_with(&format!("0x{}", short_id))
196 {
197 Some(get_mainnet_chain_identifier())
198 } else if Hex::from_bytes(&Base58::decode(TESTNET_CHAIN_IDENTIFIER_BASE58).ok()?)
199 .encoded_with_format()
200 .starts_with(&format!("0x{}", short_id))
201 {
202 Some(get_testnet_chain_identifier())
203 } else {
204 None
205 }
206 }
207
208 pub fn chain(&self) -> Chain {
209 let mainnet_id = get_mainnet_chain_identifier();
210 let testnet_id = get_testnet_chain_identifier();
211
212 let chain = match self {
213 id if *id == mainnet_id => Chain::Mainnet,
214 id if *id == testnet_id => Chain::Testnet,
215 _ => Chain::Unknown,
216 };
217 if let Some(override_chain) = *SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE {
218 if chain != Chain::Unknown {
219 panic!("not allowed to override real chain {chain:?}");
220 }
221 return override_chain;
222 }
223
224 chain
225 }
226
227 pub fn as_bytes(&self) -> &[u8; 32] {
228 self.0.inner()
229 }
230
231 pub fn random() -> Self {
232 Self(CheckpointDigest::random())
233 }
234}
235
236pub fn get_mainnet_chain_identifier() -> ChainIdentifier {
237 let digest = MAINNET_CHAIN_IDENTIFIER.get_or_init(|| {
238 let digest = CheckpointDigest::new(
239 Base58::decode(MAINNET_CHAIN_IDENTIFIER_BASE58)
240 .expect("mainnet genesis checkpoint digest literal is invalid")
241 .try_into()
242 .expect("Mainnet genesis checkpoint digest literal has incorrect length"),
243 );
244 ChainIdentifier::from(digest)
245 });
246 *digest
247}
248
249pub fn get_testnet_chain_identifier() -> ChainIdentifier {
250 let digest = TESTNET_CHAIN_IDENTIFIER.get_or_init(|| {
251 let digest = CheckpointDigest::new(
252 Base58::decode(TESTNET_CHAIN_IDENTIFIER_BASE58)
253 .expect("testnet genesis checkpoint digest literal is invalid")
254 .try_into()
255 .expect("Testnet genesis checkpoint digest literal has incorrect length"),
256 );
257 ChainIdentifier::from(digest)
258 });
259 *digest
260}
261
262impl fmt::Display for ChainIdentifier {
263 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 for byte in self.0.0.0[0..4].iter() {
265 write!(f, "{:02x}", byte)?;
266 }
267
268 Ok(())
269 }
270}
271
272impl From<CheckpointDigest> for ChainIdentifier {
273 fn from(digest: CheckpointDigest) -> Self {
274 Self(digest)
275 }
276}
277
278#[derive(
280 Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
281)]
282pub struct CheckpointDigest(Digest);
283
284impl CheckpointDigest {
285 pub const fn new(digest: [u8; 32]) -> Self {
286 Self(Digest::new(digest))
287 }
288
289 pub fn generate<R: rand::RngCore + rand::CryptoRng>(rng: R) -> Self {
290 Self(Digest::generate(rng))
291 }
292
293 pub fn random() -> Self {
294 Self(Digest::random())
295 }
296
297 pub const fn inner(&self) -> &[u8; 32] {
298 self.0.inner()
299 }
300
301 pub const fn into_inner(self) -> [u8; 32] {
302 self.0.into_inner()
303 }
304
305 pub fn base58_encode(&self) -> String {
306 Base58::encode(self.0)
307 }
308
309 pub fn next_lexicographical(&self) -> Option<Self> {
310 self.0.next_lexicographical().map(Self)
311 }
312}
313
314impl AsRef<[u8]> for CheckpointDigest {
315 fn as_ref(&self) -> &[u8] {
316 self.0.as_ref()
317 }
318}
319
320impl AsRef<[u8; 32]> for CheckpointDigest {
321 fn as_ref(&self) -> &[u8; 32] {
322 self.0.as_ref()
323 }
324}
325
326impl From<CheckpointDigest> for [u8; 32] {
327 fn from(digest: CheckpointDigest) -> Self {
328 digest.into_inner()
329 }
330}
331
332impl From<[u8; 32]> for CheckpointDigest {
333 fn from(digest: [u8; 32]) -> Self {
334 Self::new(digest)
335 }
336}
337
338impl TryFrom<Vec<u8>> for CheckpointDigest {
339 type Error = SuiError;
340
341 fn try_from(bytes: Vec<u8>) -> Result<Self, SuiError> {
342 Digest::try_from(bytes).map(CheckpointDigest)
343 }
344}
345
346impl fmt::Display for CheckpointDigest {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 fmt::Display::fmt(&self.0, f)
349 }
350}
351
352impl fmt::Debug for CheckpointDigest {
353 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354 f.debug_tuple("CheckpointDigest").field(&self.0).finish()
355 }
356}
357
358impl fmt::LowerHex for CheckpointDigest {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 fmt::LowerHex::fmt(&self.0, f)
361 }
362}
363
364impl fmt::UpperHex for CheckpointDigest {
365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366 fmt::UpperHex::fmt(&self.0, f)
367 }
368}
369
370impl std::str::FromStr for CheckpointDigest {
371 type Err = anyhow::Error;
372
373 fn from_str(s: &str) -> Result<Self, Self::Err> {
374 Ok(Self::new(digest_from_base58(s)?))
375 }
376}
377
378#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)]
379pub struct CheckpointContentsDigest(Digest);
380
381impl CheckpointContentsDigest {
382 pub const fn new(digest: [u8; 32]) -> Self {
383 Self(Digest::new(digest))
384 }
385
386 pub fn generate<R: rand::RngCore + rand::CryptoRng>(rng: R) -> Self {
387 Self(Digest::generate(rng))
388 }
389
390 pub fn random() -> Self {
391 Self(Digest::random())
392 }
393
394 pub const fn inner(&self) -> &[u8; 32] {
395 self.0.inner()
396 }
397
398 pub const fn into_inner(self) -> [u8; 32] {
399 self.0.into_inner()
400 }
401
402 pub fn base58_encode(&self) -> String {
403 Base58::encode(self.0)
404 }
405
406 pub fn next_lexicographical(&self) -> Option<Self> {
407 self.0.next_lexicographical().map(Self)
408 }
409}
410
411impl AsRef<[u8]> for CheckpointContentsDigest {
412 fn as_ref(&self) -> &[u8] {
413 self.0.as_ref()
414 }
415}
416
417impl AsRef<[u8; 32]> for CheckpointContentsDigest {
418 fn as_ref(&self) -> &[u8; 32] {
419 self.0.as_ref()
420 }
421}
422
423impl TryFrom<Vec<u8>> for CheckpointContentsDigest {
424 type Error = SuiError;
425
426 fn try_from(bytes: Vec<u8>) -> Result<Self, SuiError> {
427 Digest::try_from(bytes).map(CheckpointContentsDigest)
428 }
429}
430
431impl From<CheckpointContentsDigest> for [u8; 32] {
432 fn from(digest: CheckpointContentsDigest) -> Self {
433 digest.into_inner()
434 }
435}
436
437impl From<[u8; 32]> for CheckpointContentsDigest {
438 fn from(digest: [u8; 32]) -> Self {
439 Self::new(digest)
440 }
441}
442
443impl fmt::Display for CheckpointContentsDigest {
444 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445 fmt::Display::fmt(&self.0, f)
446 }
447}
448
449impl fmt::Debug for CheckpointContentsDigest {
450 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451 f.debug_tuple("CheckpointContentsDigest")
452 .field(&self.0)
453 .finish()
454 }
455}
456
457impl std::str::FromStr for CheckpointContentsDigest {
458 type Err = anyhow::Error;
459
460 fn from_str(s: &str) -> Result<Self, Self::Err> {
461 Ok(Self::new(digest_from_base58(s)?))
462 }
463}
464
465impl fmt::LowerHex for CheckpointContentsDigest {
466 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
467 fmt::LowerHex::fmt(&self.0, f)
468 }
469}
470
471impl fmt::UpperHex for CheckpointContentsDigest {
472 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473 fmt::UpperHex::fmt(&self.0, f)
474 }
475}
476
477#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
479pub struct CertificateDigest(Digest);
480
481impl CertificateDigest {
482 pub const fn new(digest: [u8; 32]) -> Self {
483 Self(Digest::new(digest))
484 }
485
486 pub fn random() -> Self {
487 Self(Digest::random())
488 }
489}
490
491impl fmt::Debug for CertificateDigest {
492 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
493 f.debug_tuple("CertificateDigest").field(&self.0).finish()
494 }
495}
496
497#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
499pub struct SenderSignedDataDigest(Digest);
500
501impl SenderSignedDataDigest {
502 pub const fn new(digest: [u8; 32]) -> Self {
503 Self(Digest::new(digest))
504 }
505}
506
507impl fmt::Debug for SenderSignedDataDigest {
508 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509 f.debug_tuple("SenderSignedDataDigest")
510 .field(&self.0)
511 .finish()
512 }
513}
514
515#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)]
517pub struct TransactionDigest(Digest);
518
519impl Default for TransactionDigest {
520 fn default() -> Self {
521 Self::ZERO
522 }
523}
524
525impl TransactionDigest {
526 pub const ZERO: Self = Self(Digest::ZERO);
527
528 pub const fn new(digest: [u8; 32]) -> Self {
529 Self(Digest::new(digest))
530 }
531
532 pub const fn from_digest(digest: Digest) -> Self {
533 Self(digest)
534 }
535
536 pub const fn genesis_marker() -> Self {
542 Self::ZERO
543 }
544
545 pub fn generate<R: rand::RngCore + rand::CryptoRng>(rng: R) -> Self {
546 Self(Digest::generate(rng))
547 }
548
549 pub fn random() -> Self {
550 Self(Digest::random())
551 }
552
553 pub fn inner(&self) -> &[u8; 32] {
554 self.0.inner()
555 }
556
557 pub fn into_inner(self) -> [u8; 32] {
558 self.0.into_inner()
559 }
560
561 pub fn base58_encode(&self) -> String {
562 Base58::encode(self.0)
563 }
564
565 pub fn next_lexicographical(&self) -> Option<Self> {
566 self.0.next_lexicographical().map(Self)
567 }
568}
569
570impl AsRef<[u8]> for TransactionDigest {
571 fn as_ref(&self) -> &[u8] {
572 self.0.as_ref()
573 }
574}
575
576impl AsRef<[u8; 32]> for TransactionDigest {
577 fn as_ref(&self) -> &[u8; 32] {
578 self.0.as_ref()
579 }
580}
581
582impl From<TransactionDigest> for [u8; 32] {
583 fn from(digest: TransactionDigest) -> Self {
584 digest.into_inner()
585 }
586}
587
588impl From<[u8; 32]> for TransactionDigest {
589 fn from(digest: [u8; 32]) -> Self {
590 Self::new(digest)
591 }
592}
593
594impl fmt::Display for TransactionDigest {
595 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596 fmt::Display::fmt(&self.0, f)
597 }
598}
599
600impl fmt::Debug for TransactionDigest {
601 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
602 f.debug_tuple("TransactionDigest").field(&self.0).finish()
603 }
604}
605
606impl fmt::LowerHex for TransactionDigest {
607 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608 fmt::LowerHex::fmt(&self.0, f)
609 }
610}
611
612impl fmt::UpperHex for TransactionDigest {
613 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
614 fmt::UpperHex::fmt(&self.0, f)
615 }
616}
617
618impl TryFrom<&[u8]> for TransactionDigest {
619 type Error = crate::error::SuiError;
620
621 fn try_from(bytes: &[u8]) -> Result<Self, crate::error::SuiError> {
622 let arr: [u8; 32] = bytes
623 .try_into()
624 .map_err(|_| crate::error::SuiErrorKind::InvalidTransactionDigest)?;
625 Ok(Self::new(arr))
626 }
627}
628
629impl TryFrom<Vec<u8>> for TransactionDigest {
630 type Error = crate::error::SuiError;
631
632 fn try_from(bytes: Vec<u8>) -> Result<Self, SuiError> {
633 Digest::try_from(bytes).map(TransactionDigest)
634 }
635}
636
637impl std::str::FromStr for TransactionDigest {
638 type Err = anyhow::Error;
639
640 fn from_str(s: &str) -> Result<Self, Self::Err> {
641 Ok(Self::new(digest_from_base58(s)?))
642 }
643}
644
645#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)]
646pub struct TransactionEffectsDigest(Digest);
647
648impl TransactionEffectsDigest {
649 pub const ZERO: Self = Self(Digest::ZERO);
650
651 pub const fn new(digest: [u8; 32]) -> Self {
652 Self(Digest::new(digest))
653 }
654
655 pub fn generate<R: rand::RngCore + rand::CryptoRng>(rng: R) -> Self {
656 Self(Digest::generate(rng))
657 }
658
659 pub fn random() -> Self {
660 Self(Digest::random())
661 }
662
663 pub const fn inner(&self) -> &[u8; 32] {
664 self.0.inner()
665 }
666
667 pub const fn into_inner(self) -> [u8; 32] {
668 self.0.into_inner()
669 }
670
671 pub fn base58_encode(&self) -> String {
672 Base58::encode(self.0)
673 }
674
675 pub fn next_lexicographical(&self) -> Option<Self> {
676 self.0.next_lexicographical().map(Self)
677 }
678}
679
680impl AsRef<[u8]> for TransactionEffectsDigest {
681 fn as_ref(&self) -> &[u8] {
682 self.0.as_ref()
683 }
684}
685
686impl AsRef<[u8; 32]> for TransactionEffectsDigest {
687 fn as_ref(&self) -> &[u8; 32] {
688 self.0.as_ref()
689 }
690}
691
692impl TryFrom<Vec<u8>> for TransactionEffectsDigest {
693 type Error = SuiError;
694
695 fn try_from(bytes: Vec<u8>) -> Result<Self, SuiError> {
696 Digest::try_from(bytes).map(TransactionEffectsDigest)
697 }
698}
699
700impl From<TransactionEffectsDigest> for [u8; 32] {
701 fn from(digest: TransactionEffectsDigest) -> Self {
702 digest.into_inner()
703 }
704}
705
706impl From<[u8; 32]> for TransactionEffectsDigest {
707 fn from(digest: [u8; 32]) -> Self {
708 Self::new(digest)
709 }
710}
711
712impl fmt::Display for TransactionEffectsDigest {
713 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
714 fmt::Display::fmt(&self.0, f)
715 }
716}
717
718impl fmt::Debug for TransactionEffectsDigest {
719 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
720 f.debug_tuple("TransactionEffectsDigest")
721 .field(&self.0)
722 .finish()
723 }
724}
725
726impl fmt::LowerHex for TransactionEffectsDigest {
727 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
728 fmt::LowerHex::fmt(&self.0, f)
729 }
730}
731
732impl fmt::UpperHex for TransactionEffectsDigest {
733 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
734 fmt::UpperHex::fmt(&self.0, f)
735 }
736}
737
738impl std::str::FromStr for TransactionEffectsDigest {
739 type Err = anyhow::Error;
740
741 fn from_str(s: &str) -> Result<Self, Self::Err> {
742 Ok(Self::new(digest_from_base58(s)?))
743 }
744}
745
746#[serde_as]
747#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)]
748pub struct TransactionEventsDigest(Digest);
749
750impl TransactionEventsDigest {
751 pub const ZERO: Self = Self(Digest::ZERO);
752
753 pub const fn new(digest: [u8; 32]) -> Self {
754 Self(Digest::new(digest))
755 }
756
757 pub fn random() -> Self {
758 Self(Digest::random())
759 }
760
761 pub fn next_lexicographical(&self) -> Option<Self> {
762 self.0.next_lexicographical().map(Self)
763 }
764
765 pub fn into_inner(self) -> [u8; 32] {
766 self.0.into_inner()
767 }
768
769 pub fn base58_encode(&self) -> String {
770 Base58::encode(self.0)
771 }
772}
773
774impl fmt::Display for TransactionEventsDigest {
775 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
776 fmt::Display::fmt(&self.0, f)
777 }
778}
779
780impl fmt::Debug for TransactionEventsDigest {
781 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
782 f.debug_tuple("TransactionEventsDigest")
783 .field(&self.0)
784 .finish()
785 }
786}
787
788impl AsRef<[u8]> for TransactionEventsDigest {
789 fn as_ref(&self) -> &[u8] {
790 self.0.as_ref()
791 }
792}
793
794impl AsRef<[u8; 32]> for TransactionEventsDigest {
795 fn as_ref(&self) -> &[u8; 32] {
796 self.0.as_ref()
797 }
798}
799
800impl TryFrom<Vec<u8>> for TransactionEventsDigest {
801 type Error = SuiError;
802
803 fn try_from(bytes: Vec<u8>) -> Result<Self, SuiError> {
804 Digest::try_from(bytes).map(TransactionEventsDigest)
805 }
806}
807
808impl std::str::FromStr for TransactionEventsDigest {
809 type Err = anyhow::Error;
810
811 fn from_str(s: &str) -> Result<Self, Self::Err> {
812 Ok(Self::new(digest_from_base58(s)?))
813 }
814}
815
816#[serde_as]
817#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)]
818pub struct EffectsAuxDataDigest(Digest);
819
820impl EffectsAuxDataDigest {
821 pub const ZERO: Self = Self(Digest::ZERO);
822
823 pub const fn new(digest: [u8; 32]) -> Self {
824 Self(Digest::new(digest))
825 }
826
827 pub fn random() -> Self {
828 Self(Digest::random())
829 }
830
831 pub fn next_lexicographical(&self) -> Option<Self> {
832 self.0.next_lexicographical().map(Self)
833 }
834
835 pub fn into_inner(self) -> [u8; 32] {
836 self.0.into_inner()
837 }
838}
839
840impl fmt::Display for EffectsAuxDataDigest {
841 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
842 fmt::Display::fmt(&self.0, f)
843 }
844}
845
846impl fmt::Debug for EffectsAuxDataDigest {
847 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
848 f.debug_tuple("EffectsAuxDataDigest")
849 .field(&self.0)
850 .finish()
851 }
852}
853
854impl AsRef<[u8]> for EffectsAuxDataDigest {
855 fn as_ref(&self) -> &[u8] {
856 self.0.as_ref()
857 }
858}
859
860impl AsRef<[u8; 32]> for EffectsAuxDataDigest {
861 fn as_ref(&self) -> &[u8; 32] {
862 self.0.as_ref()
863 }
864}
865
866impl std::str::FromStr for EffectsAuxDataDigest {
867 type Err = anyhow::Error;
868
869 fn from_str(s: &str) -> Result<Self, Self::Err> {
870 Ok(Self::new(digest_from_base58(s)?))
871 }
872}
873
874#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)]
876pub struct ObjectDigest(Digest);
877
878impl ObjectDigest {
879 pub const MIN: ObjectDigest = Self::new([u8::MIN; 32]);
880 pub const MAX: ObjectDigest = Self::new([u8::MAX; 32]);
881 pub const OBJECT_DIGEST_DELETED_BYTE_VAL: u8 = 99;
882 pub const OBJECT_DIGEST_WRAPPED_BYTE_VAL: u8 = 88;
883 pub const OBJECT_DIGEST_CANCELLED_BYTE_VAL: u8 = 77;
884
885 pub const OBJECT_DIGEST_DELETED: ObjectDigest =
887 Self::new([Self::OBJECT_DIGEST_DELETED_BYTE_VAL; 32]);
888
889 pub const OBJECT_DIGEST_WRAPPED: ObjectDigest =
891 Self::new([Self::OBJECT_DIGEST_WRAPPED_BYTE_VAL; 32]);
892
893 pub const OBJECT_DIGEST_CANCELLED: ObjectDigest =
894 Self::new([Self::OBJECT_DIGEST_CANCELLED_BYTE_VAL; 32]);
895
896 pub const fn new(digest: [u8; 32]) -> Self {
897 Self(Digest::new(digest))
898 }
899
900 pub fn generate<R: rand::RngCore + rand::CryptoRng>(rng: R) -> Self {
901 Self(Digest::generate(rng))
902 }
903
904 pub fn random() -> Self {
905 Self(Digest::random())
906 }
907
908 pub const fn inner(&self) -> &[u8; 32] {
909 self.0.inner()
910 }
911
912 pub const fn into_inner(self) -> [u8; 32] {
913 self.0.into_inner()
914 }
915
916 pub fn is_alive(&self) -> bool {
917 *self != Self::OBJECT_DIGEST_DELETED && *self != Self::OBJECT_DIGEST_WRAPPED
918 }
919
920 pub fn is_deleted(&self) -> bool {
921 *self == Self::OBJECT_DIGEST_DELETED
922 }
923
924 pub fn is_wrapped(&self) -> bool {
925 *self == Self::OBJECT_DIGEST_WRAPPED
926 }
927
928 pub fn base58_encode(&self) -> String {
929 Base58::encode(self.0)
930 }
931}
932
933impl AsRef<[u8]> for ObjectDigest {
934 fn as_ref(&self) -> &[u8] {
935 self.0.as_ref()
936 }
937}
938
939impl AsRef<[u8; 32]> for ObjectDigest {
940 fn as_ref(&self) -> &[u8; 32] {
941 self.0.as_ref()
942 }
943}
944
945impl From<ObjectDigest> for [u8; 32] {
946 fn from(digest: ObjectDigest) -> Self {
947 digest.into_inner()
948 }
949}
950
951impl From<[u8; 32]> for ObjectDigest {
952 fn from(digest: [u8; 32]) -> Self {
953 Self::new(digest)
954 }
955}
956
957impl fmt::Display for ObjectDigest {
958 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
959 fmt::Display::fmt(&self.0, f)
960 }
961}
962
963impl fmt::Debug for ObjectDigest {
964 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
965 write!(f, "o#{}", self.0)
966 }
967}
968
969impl fmt::LowerHex for ObjectDigest {
970 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
971 fmt::LowerHex::fmt(&self.0, f)
972 }
973}
974
975impl fmt::UpperHex for ObjectDigest {
976 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
977 fmt::UpperHex::fmt(&self.0, f)
978 }
979}
980
981impl TryFrom<&[u8]> for ObjectDigest {
982 type Error = crate::error::SuiError;
983
984 fn try_from(bytes: &[u8]) -> Result<Self, crate::error::SuiError> {
985 let arr: [u8; 32] = bytes
986 .try_into()
987 .map_err(|_| crate::error::SuiErrorKind::InvalidTransactionDigest)?;
988 Ok(Self::new(arr))
989 }
990}
991
992impl std::str::FromStr for ObjectDigest {
993 type Err = anyhow::Error;
994
995 fn from_str(s: &str) -> Result<Self, Self::Err> {
996 Ok(Self::new(digest_from_base58(s)?))
997 }
998}
999
1000#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1002pub struct ZKLoginInputsDigest(Digest);
1003
1004impl ZKLoginInputsDigest {
1005 pub const fn new(digest: [u8; 32]) -> Self {
1006 Self(Digest::new(digest))
1007 }
1008}
1009
1010#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)]
1011pub struct ConsensusCommitDigest(Digest);
1012
1013impl ConsensusCommitDigest {
1014 pub const ZERO: Self = Self(Digest::ZERO);
1015
1016 pub const fn new(digest: [u8; 32]) -> Self {
1017 Self(Digest::new(digest))
1018 }
1019
1020 pub const fn inner(&self) -> &[u8; 32] {
1021 self.0.inner()
1022 }
1023
1024 pub const fn into_inner(self) -> [u8; 32] {
1025 self.0.into_inner()
1026 }
1027
1028 pub fn random() -> Self {
1029 Self(Digest::random())
1030 }
1031}
1032
1033impl Default for ConsensusCommitDigest {
1034 fn default() -> Self {
1035 Self::ZERO
1036 }
1037}
1038
1039impl From<ConsensusCommitDigest> for [u8; 32] {
1040 fn from(digest: ConsensusCommitDigest) -> Self {
1041 digest.into_inner()
1042 }
1043}
1044
1045impl From<[u8; 32]> for ConsensusCommitDigest {
1046 fn from(digest: [u8; 32]) -> Self {
1047 Self::new(digest)
1048 }
1049}
1050
1051impl fmt::Display for ConsensusCommitDigest {
1052 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1053 fmt::Display::fmt(&self.0, f)
1054 }
1055}
1056
1057impl fmt::Debug for ConsensusCommitDigest {
1058 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1059 f.debug_tuple("ConsensusCommitDigest")
1060 .field(&self.0)
1061 .finish()
1062 }
1063}
1064
1065#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)]
1066pub struct AdditionalConsensusStateDigest(Digest);
1067
1068impl AdditionalConsensusStateDigest {
1069 pub const ZERO: Self = Self(Digest::ZERO);
1070 pub const fn new(digest: [u8; 32]) -> Self {
1071 Self(Digest::new(digest))
1072 }
1073}
1074
1075impl fmt::Display for AdditionalConsensusStateDigest {
1076 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1077 fmt::Display::fmt(&self.0, f)
1078 }
1079}
1080
1081impl fmt::Debug for AdditionalConsensusStateDigest {
1082 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1083 f.debug_tuple("AdditionalConsensusStateDigest")
1084 .field(&self.0)
1085 .finish()
1086 }
1087}
1088
1089#[derive(
1090 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
1091)]
1092pub struct CheckpointArtifactsDigest(Digest);
1093
1094impl CheckpointArtifactsDigest {
1095 pub const fn new(digest: [u8; 32]) -> Self {
1096 Self(Digest::new(digest))
1097 }
1098
1099 pub const fn inner(&self) -> &[u8; 32] {
1100 self.0.inner()
1101 }
1102
1103 pub const fn into_inner(self) -> [u8; 32] {
1104 self.0.into_inner()
1105 }
1106
1107 pub fn base58_encode(&self) -> String {
1108 Base58::encode(self.0)
1109 }
1110
1111 pub fn from_artifact_digests(digests: Vec<Digest>) -> SuiResult<Self> {
1112 let bytes =
1113 bcs::to_bytes(&digests).map_err(|e| SuiError::from(format!("BCS error: {}", e)))?;
1114 Ok(Self(Digest::new(Blake2b256::digest(&bytes).into())))
1115 }
1116}
1117
1118impl From<[u8; 32]> for CheckpointArtifactsDigest {
1119 fn from(digest: [u8; 32]) -> Self {
1120 Self(Digest::new(digest))
1121 }
1122}
1123
1124impl fmt::Display for CheckpointArtifactsDigest {
1125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1126 fmt::Display::fmt(&self.0, f)
1127 }
1128}
1129
1130fn digest_from_base58(s: &str) -> anyhow::Result<[u8; 32]> {
1131 let mut result = [0; 32];
1132 let buffer = Base58::decode(s).map_err(|e| anyhow::anyhow!(e))?;
1133 let len = buffer.len();
1134 if len < 32 {
1135 return Err(anyhow::anyhow!(
1136 "Invalid digest length. Expected base58 string that decodes into 32 bytes, but [{s}] decodes into {len} bytes"
1137 ));
1138 } else if len > 32 {
1139 let s = &s[0..32.min(s.len())];
1140 return Err(anyhow::anyhow!(
1141 "Invalid digest length. Expected base58 string that decodes into 32 bytes, but [{s}] (truncated) decodes into {len} bytes"
1142 ));
1143 }
1144 result.copy_from_slice(&buffer);
1145 Ok(result)
1146}
1147
1148#[cfg(test)]
1149mod test {
1150 use crate::digests::{ChainIdentifier, SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE, digest_from_base58};
1151
1152 fn has_env_override() -> bool {
1153 SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE.is_some()
1154 }
1155
1156 #[test]
1158 fn test_chain_id_mainnet() {
1159 if has_env_override() {
1160 return;
1161 }
1162 let chain_id = ChainIdentifier::from_chain_short_id(&String::from("35834a8a"));
1163 assert_eq!(
1164 chain_id.unwrap().chain(),
1165 sui_protocol_config::Chain::Mainnet
1166 );
1167 }
1168
1169 #[test]
1170 fn test_chain_id_testnet() {
1171 if has_env_override() {
1172 return;
1173 }
1174 let chain_id = ChainIdentifier::from_chain_short_id(&String::from("4c78adac"));
1175 assert_eq!(
1176 chain_id.unwrap().chain(),
1177 sui_protocol_config::Chain::Testnet
1178 );
1179 }
1180
1181 #[test]
1182 fn test_chain_id_unknown() {
1183 if has_env_override() {
1184 return;
1185 }
1186 let chain_id = ChainIdentifier::from_chain_short_id(&String::from("unknown"));
1187 assert_eq!(chain_id, None);
1188 }
1189
1190 #[test]
1191 fn test_digest_from_base58_eq_32() {
1192 assert_eq!(
1193 digest_from_base58("1".repeat(32).as_str()).unwrap(),
1194 [0; 32]
1195 );
1196 }
1197
1198 #[test]
1199 fn test_digest_from_base58_lt_32() {
1200 assert_eq!(
1201 digest_from_base58("1".repeat(31).as_str())
1202 .unwrap_err()
1203 .to_string(),
1204 "Invalid digest length. Expected base58 string that decodes into 32 bytes, but [1111111111111111111111111111111] decodes into 31 bytes"
1205 );
1206 }
1207
1208 #[test]
1209 fn test_digest_from_base58_gt_32() {
1210 assert_eq!(
1211 digest_from_base58("1".repeat(33).as_str())
1212 .unwrap_err()
1213 .to_string(),
1214 "Invalid digest length. Expected base58 string that decodes into 32 bytes, but [11111111111111111111111111111111] (truncated) decodes into 33 bytes"
1215 );
1216 }
1217}