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 let mut result = [0; 32];
375 let buffer = Base58::decode(s).map_err(|e| anyhow::anyhow!(e))?;
376 if buffer.len() != 32 {
377 return Err(anyhow::anyhow!("Invalid digest length. Expected 32 bytes"));
378 }
379 result.copy_from_slice(&buffer);
380 Ok(CheckpointDigest::new(result))
381 }
382}
383
384#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)]
385pub struct CheckpointContentsDigest(Digest);
386
387impl CheckpointContentsDigest {
388 pub const fn new(digest: [u8; 32]) -> Self {
389 Self(Digest::new(digest))
390 }
391
392 pub fn generate<R: rand::RngCore + rand::CryptoRng>(rng: R) -> Self {
393 Self(Digest::generate(rng))
394 }
395
396 pub fn random() -> Self {
397 Self(Digest::random())
398 }
399
400 pub const fn inner(&self) -> &[u8; 32] {
401 self.0.inner()
402 }
403
404 pub const fn into_inner(self) -> [u8; 32] {
405 self.0.into_inner()
406 }
407
408 pub fn base58_encode(&self) -> String {
409 Base58::encode(self.0)
410 }
411
412 pub fn next_lexicographical(&self) -> Option<Self> {
413 self.0.next_lexicographical().map(Self)
414 }
415}
416
417impl AsRef<[u8]> for CheckpointContentsDigest {
418 fn as_ref(&self) -> &[u8] {
419 self.0.as_ref()
420 }
421}
422
423impl AsRef<[u8; 32]> for CheckpointContentsDigest {
424 fn as_ref(&self) -> &[u8; 32] {
425 self.0.as_ref()
426 }
427}
428
429impl TryFrom<Vec<u8>> for CheckpointContentsDigest {
430 type Error = SuiError;
431
432 fn try_from(bytes: Vec<u8>) -> Result<Self, SuiError> {
433 Digest::try_from(bytes).map(CheckpointContentsDigest)
434 }
435}
436
437impl From<CheckpointContentsDigest> for [u8; 32] {
438 fn from(digest: CheckpointContentsDigest) -> Self {
439 digest.into_inner()
440 }
441}
442
443impl From<[u8; 32]> for CheckpointContentsDigest {
444 fn from(digest: [u8; 32]) -> Self {
445 Self::new(digest)
446 }
447}
448
449impl fmt::Display for CheckpointContentsDigest {
450 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451 fmt::Display::fmt(&self.0, f)
452 }
453}
454
455impl fmt::Debug for CheckpointContentsDigest {
456 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457 f.debug_tuple("CheckpointContentsDigest")
458 .field(&self.0)
459 .finish()
460 }
461}
462
463impl std::str::FromStr for CheckpointContentsDigest {
464 type Err = anyhow::Error;
465
466 fn from_str(s: &str) -> Result<Self, Self::Err> {
467 let mut result = [0; 32];
468 let buffer = Base58::decode(s).map_err(|e| anyhow::anyhow!(e))?;
469 if buffer.len() != 32 {
470 return Err(anyhow::anyhow!("Invalid digest length. Expected 32 bytes"));
471 }
472 result.copy_from_slice(&buffer);
473 Ok(CheckpointContentsDigest::new(result))
474 }
475}
476
477impl fmt::LowerHex for CheckpointContentsDigest {
478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479 fmt::LowerHex::fmt(&self.0, f)
480 }
481}
482
483impl fmt::UpperHex for CheckpointContentsDigest {
484 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
485 fmt::UpperHex::fmt(&self.0, f)
486 }
487}
488
489#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
491pub struct CertificateDigest(Digest);
492
493impl CertificateDigest {
494 pub const fn new(digest: [u8; 32]) -> Self {
495 Self(Digest::new(digest))
496 }
497
498 pub fn random() -> Self {
499 Self(Digest::random())
500 }
501}
502
503impl fmt::Debug for CertificateDigest {
504 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505 f.debug_tuple("CertificateDigest").field(&self.0).finish()
506 }
507}
508
509#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
511pub struct SenderSignedDataDigest(Digest);
512
513impl SenderSignedDataDigest {
514 pub const fn new(digest: [u8; 32]) -> Self {
515 Self(Digest::new(digest))
516 }
517}
518
519impl fmt::Debug for SenderSignedDataDigest {
520 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
521 f.debug_tuple("SenderSignedDataDigest")
522 .field(&self.0)
523 .finish()
524 }
525}
526
527#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)]
529pub struct TransactionDigest(Digest);
530
531impl Default for TransactionDigest {
532 fn default() -> Self {
533 Self::ZERO
534 }
535}
536
537impl TransactionDigest {
538 pub const ZERO: Self = Self(Digest::ZERO);
539
540 pub const fn new(digest: [u8; 32]) -> Self {
541 Self(Digest::new(digest))
542 }
543
544 pub const fn from_digest(digest: Digest) -> Self {
545 Self(digest)
546 }
547
548 pub const fn genesis_marker() -> Self {
554 Self::ZERO
555 }
556
557 pub fn generate<R: rand::RngCore + rand::CryptoRng>(rng: R) -> Self {
558 Self(Digest::generate(rng))
559 }
560
561 pub fn random() -> Self {
562 Self(Digest::random())
563 }
564
565 pub fn inner(&self) -> &[u8; 32] {
566 self.0.inner()
567 }
568
569 pub fn into_inner(self) -> [u8; 32] {
570 self.0.into_inner()
571 }
572
573 pub fn base58_encode(&self) -> String {
574 Base58::encode(self.0)
575 }
576
577 pub fn next_lexicographical(&self) -> Option<Self> {
578 self.0.next_lexicographical().map(Self)
579 }
580}
581
582impl AsRef<[u8]> for TransactionDigest {
583 fn as_ref(&self) -> &[u8] {
584 self.0.as_ref()
585 }
586}
587
588impl AsRef<[u8; 32]> for TransactionDigest {
589 fn as_ref(&self) -> &[u8; 32] {
590 self.0.as_ref()
591 }
592}
593
594impl From<TransactionDigest> for [u8; 32] {
595 fn from(digest: TransactionDigest) -> Self {
596 digest.into_inner()
597 }
598}
599
600impl From<[u8; 32]> for TransactionDigest {
601 fn from(digest: [u8; 32]) -> Self {
602 Self::new(digest)
603 }
604}
605
606impl fmt::Display for TransactionDigest {
607 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608 fmt::Display::fmt(&self.0, f)
609 }
610}
611
612impl fmt::Debug for TransactionDigest {
613 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
614 f.debug_tuple("TransactionDigest").field(&self.0).finish()
615 }
616}
617
618impl fmt::LowerHex for TransactionDigest {
619 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
620 fmt::LowerHex::fmt(&self.0, f)
621 }
622}
623
624impl fmt::UpperHex for TransactionDigest {
625 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
626 fmt::UpperHex::fmt(&self.0, f)
627 }
628}
629
630impl TryFrom<&[u8]> for TransactionDigest {
631 type Error = crate::error::SuiError;
632
633 fn try_from(bytes: &[u8]) -> Result<Self, crate::error::SuiError> {
634 let arr: [u8; 32] = bytes
635 .try_into()
636 .map_err(|_| crate::error::SuiErrorKind::InvalidTransactionDigest)?;
637 Ok(Self::new(arr))
638 }
639}
640
641impl TryFrom<Vec<u8>> for TransactionDigest {
642 type Error = crate::error::SuiError;
643
644 fn try_from(bytes: Vec<u8>) -> Result<Self, SuiError> {
645 Digest::try_from(bytes).map(TransactionDigest)
646 }
647}
648
649impl std::str::FromStr for TransactionDigest {
650 type Err = anyhow::Error;
651
652 fn from_str(s: &str) -> Result<Self, Self::Err> {
653 let mut result = [0; 32];
654 let buffer = Base58::decode(s).map_err(|e| anyhow::anyhow!(e))?;
655 if buffer.len() != 32 {
656 return Err(anyhow::anyhow!("Invalid digest length. Expected 32 bytes"));
657 }
658 result.copy_from_slice(&buffer);
659 Ok(TransactionDigest::new(result))
660 }
661}
662
663#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)]
664pub struct TransactionEffectsDigest(Digest);
665
666impl TransactionEffectsDigest {
667 pub const ZERO: Self = Self(Digest::ZERO);
668
669 pub const fn new(digest: [u8; 32]) -> Self {
670 Self(Digest::new(digest))
671 }
672
673 pub fn generate<R: rand::RngCore + rand::CryptoRng>(rng: R) -> Self {
674 Self(Digest::generate(rng))
675 }
676
677 pub fn random() -> Self {
678 Self(Digest::random())
679 }
680
681 pub const fn inner(&self) -> &[u8; 32] {
682 self.0.inner()
683 }
684
685 pub const fn into_inner(self) -> [u8; 32] {
686 self.0.into_inner()
687 }
688
689 pub fn base58_encode(&self) -> String {
690 Base58::encode(self.0)
691 }
692
693 pub fn next_lexicographical(&self) -> Option<Self> {
694 self.0.next_lexicographical().map(Self)
695 }
696}
697
698impl AsRef<[u8]> for TransactionEffectsDigest {
699 fn as_ref(&self) -> &[u8] {
700 self.0.as_ref()
701 }
702}
703
704impl AsRef<[u8; 32]> for TransactionEffectsDigest {
705 fn as_ref(&self) -> &[u8; 32] {
706 self.0.as_ref()
707 }
708}
709
710impl TryFrom<Vec<u8>> for TransactionEffectsDigest {
711 type Error = SuiError;
712
713 fn try_from(bytes: Vec<u8>) -> Result<Self, SuiError> {
714 Digest::try_from(bytes).map(TransactionEffectsDigest)
715 }
716}
717
718impl From<TransactionEffectsDigest> for [u8; 32] {
719 fn from(digest: TransactionEffectsDigest) -> Self {
720 digest.into_inner()
721 }
722}
723
724impl From<[u8; 32]> for TransactionEffectsDigest {
725 fn from(digest: [u8; 32]) -> Self {
726 Self::new(digest)
727 }
728}
729
730impl fmt::Display for TransactionEffectsDigest {
731 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
732 fmt::Display::fmt(&self.0, f)
733 }
734}
735
736impl fmt::Debug for TransactionEffectsDigest {
737 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
738 f.debug_tuple("TransactionEffectsDigest")
739 .field(&self.0)
740 .finish()
741 }
742}
743
744impl fmt::LowerHex for TransactionEffectsDigest {
745 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
746 fmt::LowerHex::fmt(&self.0, f)
747 }
748}
749
750impl fmt::UpperHex for TransactionEffectsDigest {
751 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752 fmt::UpperHex::fmt(&self.0, f)
753 }
754}
755
756impl std::str::FromStr for TransactionEffectsDigest {
757 type Err = anyhow::Error;
758
759 fn from_str(s: &str) -> Result<Self, Self::Err> {
760 let mut result = [0; 32];
761 let buffer = Base58::decode(s).map_err(|e| anyhow::anyhow!(e))?;
762 if buffer.len() != 32 {
763 return Err(anyhow::anyhow!("Invalid digest length. Expected 32 bytes"));
764 }
765 result.copy_from_slice(&buffer);
766 Ok(TransactionEffectsDigest::new(result))
767 }
768}
769
770#[serde_as]
771#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)]
772pub struct TransactionEventsDigest(Digest);
773
774impl TransactionEventsDigest {
775 pub const ZERO: Self = Self(Digest::ZERO);
776
777 pub const fn new(digest: [u8; 32]) -> Self {
778 Self(Digest::new(digest))
779 }
780
781 pub fn random() -> Self {
782 Self(Digest::random())
783 }
784
785 pub fn next_lexicographical(&self) -> Option<Self> {
786 self.0.next_lexicographical().map(Self)
787 }
788
789 pub fn into_inner(self) -> [u8; 32] {
790 self.0.into_inner()
791 }
792
793 pub fn base58_encode(&self) -> String {
794 Base58::encode(self.0)
795 }
796}
797
798impl fmt::Display for TransactionEventsDigest {
799 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
800 fmt::Display::fmt(&self.0, f)
801 }
802}
803
804impl fmt::Debug for TransactionEventsDigest {
805 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
806 f.debug_tuple("TransactionEventsDigest")
807 .field(&self.0)
808 .finish()
809 }
810}
811
812impl AsRef<[u8]> for TransactionEventsDigest {
813 fn as_ref(&self) -> &[u8] {
814 self.0.as_ref()
815 }
816}
817
818impl AsRef<[u8; 32]> for TransactionEventsDigest {
819 fn as_ref(&self) -> &[u8; 32] {
820 self.0.as_ref()
821 }
822}
823
824impl TryFrom<Vec<u8>> for TransactionEventsDigest {
825 type Error = SuiError;
826
827 fn try_from(bytes: Vec<u8>) -> Result<Self, SuiError> {
828 Digest::try_from(bytes).map(TransactionEventsDigest)
829 }
830}
831
832impl std::str::FromStr for TransactionEventsDigest {
833 type Err = anyhow::Error;
834
835 fn from_str(s: &str) -> Result<Self, Self::Err> {
836 let mut result = [0; 32];
837 let buffer = Base58::decode(s).map_err(|e| anyhow::anyhow!(e))?;
838 if buffer.len() != 32 {
839 return Err(anyhow::anyhow!("Invalid digest length. Expected 32 bytes"));
840 }
841 result.copy_from_slice(&buffer);
842 Ok(Self::new(result))
843 }
844}
845
846#[serde_as]
847#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)]
848pub struct EffectsAuxDataDigest(Digest);
849
850impl EffectsAuxDataDigest {
851 pub const ZERO: Self = Self(Digest::ZERO);
852
853 pub const fn new(digest: [u8; 32]) -> Self {
854 Self(Digest::new(digest))
855 }
856
857 pub fn random() -> Self {
858 Self(Digest::random())
859 }
860
861 pub fn next_lexicographical(&self) -> Option<Self> {
862 self.0.next_lexicographical().map(Self)
863 }
864
865 pub fn into_inner(self) -> [u8; 32] {
866 self.0.into_inner()
867 }
868}
869
870impl fmt::Display for EffectsAuxDataDigest {
871 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
872 fmt::Display::fmt(&self.0, f)
873 }
874}
875
876impl fmt::Debug for EffectsAuxDataDigest {
877 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
878 f.debug_tuple("EffectsAuxDataDigest")
879 .field(&self.0)
880 .finish()
881 }
882}
883
884impl AsRef<[u8]> for EffectsAuxDataDigest {
885 fn as_ref(&self) -> &[u8] {
886 self.0.as_ref()
887 }
888}
889
890impl AsRef<[u8; 32]> for EffectsAuxDataDigest {
891 fn as_ref(&self) -> &[u8; 32] {
892 self.0.as_ref()
893 }
894}
895
896impl std::str::FromStr for EffectsAuxDataDigest {
897 type Err = anyhow::Error;
898
899 fn from_str(s: &str) -> Result<Self, Self::Err> {
900 let mut result = [0; 32];
901 let buffer = Base58::decode(s).map_err(|e| anyhow::anyhow!(e))?;
902 if buffer.len() != 32 {
903 return Err(anyhow::anyhow!("Invalid digest length. Expected 32 bytes"));
904 }
905 result.copy_from_slice(&buffer);
906 Ok(Self::new(result))
907 }
908}
909
910#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)]
912pub struct ObjectDigest(Digest);
913
914impl ObjectDigest {
915 pub const MIN: ObjectDigest = Self::new([u8::MIN; 32]);
916 pub const MAX: ObjectDigest = Self::new([u8::MAX; 32]);
917 pub const OBJECT_DIGEST_DELETED_BYTE_VAL: u8 = 99;
918 pub const OBJECT_DIGEST_WRAPPED_BYTE_VAL: u8 = 88;
919 pub const OBJECT_DIGEST_CANCELLED_BYTE_VAL: u8 = 77;
920
921 pub const OBJECT_DIGEST_DELETED: ObjectDigest =
923 Self::new([Self::OBJECT_DIGEST_DELETED_BYTE_VAL; 32]);
924
925 pub const OBJECT_DIGEST_WRAPPED: ObjectDigest =
927 Self::new([Self::OBJECT_DIGEST_WRAPPED_BYTE_VAL; 32]);
928
929 pub const OBJECT_DIGEST_CANCELLED: ObjectDigest =
930 Self::new([Self::OBJECT_DIGEST_CANCELLED_BYTE_VAL; 32]);
931
932 pub const fn new(digest: [u8; 32]) -> Self {
933 Self(Digest::new(digest))
934 }
935
936 pub fn generate<R: rand::RngCore + rand::CryptoRng>(rng: R) -> Self {
937 Self(Digest::generate(rng))
938 }
939
940 pub fn random() -> Self {
941 Self(Digest::random())
942 }
943
944 pub const fn inner(&self) -> &[u8; 32] {
945 self.0.inner()
946 }
947
948 pub const fn into_inner(self) -> [u8; 32] {
949 self.0.into_inner()
950 }
951
952 pub fn is_alive(&self) -> bool {
953 *self != Self::OBJECT_DIGEST_DELETED && *self != Self::OBJECT_DIGEST_WRAPPED
954 }
955
956 pub fn is_deleted(&self) -> bool {
957 *self == Self::OBJECT_DIGEST_DELETED
958 }
959
960 pub fn is_wrapped(&self) -> bool {
961 *self == Self::OBJECT_DIGEST_WRAPPED
962 }
963
964 pub fn base58_encode(&self) -> String {
965 Base58::encode(self.0)
966 }
967}
968
969impl AsRef<[u8]> for ObjectDigest {
970 fn as_ref(&self) -> &[u8] {
971 self.0.as_ref()
972 }
973}
974
975impl AsRef<[u8; 32]> for ObjectDigest {
976 fn as_ref(&self) -> &[u8; 32] {
977 self.0.as_ref()
978 }
979}
980
981impl From<ObjectDigest> for [u8; 32] {
982 fn from(digest: ObjectDigest) -> Self {
983 digest.into_inner()
984 }
985}
986
987impl From<[u8; 32]> for ObjectDigest {
988 fn from(digest: [u8; 32]) -> Self {
989 Self::new(digest)
990 }
991}
992
993impl fmt::Display for ObjectDigest {
994 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
995 fmt::Display::fmt(&self.0, f)
996 }
997}
998
999impl fmt::Debug for ObjectDigest {
1000 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1001 write!(f, "o#{}", self.0)
1002 }
1003}
1004
1005impl fmt::LowerHex for ObjectDigest {
1006 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1007 fmt::LowerHex::fmt(&self.0, f)
1008 }
1009}
1010
1011impl fmt::UpperHex for ObjectDigest {
1012 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1013 fmt::UpperHex::fmt(&self.0, f)
1014 }
1015}
1016
1017impl TryFrom<&[u8]> for ObjectDigest {
1018 type Error = crate::error::SuiError;
1019
1020 fn try_from(bytes: &[u8]) -> Result<Self, crate::error::SuiError> {
1021 let arr: [u8; 32] = bytes
1022 .try_into()
1023 .map_err(|_| crate::error::SuiErrorKind::InvalidTransactionDigest)?;
1024 Ok(Self::new(arr))
1025 }
1026}
1027
1028impl std::str::FromStr for ObjectDigest {
1029 type Err = anyhow::Error;
1030
1031 fn from_str(s: &str) -> Result<Self, Self::Err> {
1032 let mut result = [0; 32];
1033 let buffer = Base58::decode(s).map_err(|e| anyhow::anyhow!(e))?;
1034 if buffer.len() != 32 {
1035 return Err(anyhow::anyhow!("Invalid digest length. Expected 32 bytes"));
1036 }
1037 result.copy_from_slice(&buffer);
1038 Ok(ObjectDigest::new(result))
1039 }
1040}
1041
1042#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1044pub struct ZKLoginInputsDigest(Digest);
1045
1046impl ZKLoginInputsDigest {
1047 pub const fn new(digest: [u8; 32]) -> Self {
1048 Self(Digest::new(digest))
1049 }
1050}
1051
1052#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)]
1053pub struct ConsensusCommitDigest(Digest);
1054
1055impl ConsensusCommitDigest {
1056 pub const ZERO: Self = Self(Digest::ZERO);
1057
1058 pub const fn new(digest: [u8; 32]) -> Self {
1059 Self(Digest::new(digest))
1060 }
1061
1062 pub const fn inner(&self) -> &[u8; 32] {
1063 self.0.inner()
1064 }
1065
1066 pub const fn into_inner(self) -> [u8; 32] {
1067 self.0.into_inner()
1068 }
1069
1070 pub fn random() -> Self {
1071 Self(Digest::random())
1072 }
1073}
1074
1075impl Default for ConsensusCommitDigest {
1076 fn default() -> Self {
1077 Self::ZERO
1078 }
1079}
1080
1081impl From<ConsensusCommitDigest> for [u8; 32] {
1082 fn from(digest: ConsensusCommitDigest) -> Self {
1083 digest.into_inner()
1084 }
1085}
1086
1087impl From<[u8; 32]> for ConsensusCommitDigest {
1088 fn from(digest: [u8; 32]) -> Self {
1089 Self::new(digest)
1090 }
1091}
1092
1093impl fmt::Display for ConsensusCommitDigest {
1094 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1095 fmt::Display::fmt(&self.0, f)
1096 }
1097}
1098
1099impl fmt::Debug for ConsensusCommitDigest {
1100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1101 f.debug_tuple("ConsensusCommitDigest")
1102 .field(&self.0)
1103 .finish()
1104 }
1105}
1106
1107#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)]
1108pub struct AdditionalConsensusStateDigest(Digest);
1109
1110impl AdditionalConsensusStateDigest {
1111 pub const ZERO: Self = Self(Digest::ZERO);
1112 pub const fn new(digest: [u8; 32]) -> Self {
1113 Self(Digest::new(digest))
1114 }
1115}
1116
1117impl fmt::Display for AdditionalConsensusStateDigest {
1118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1119 fmt::Display::fmt(&self.0, f)
1120 }
1121}
1122
1123impl fmt::Debug for AdditionalConsensusStateDigest {
1124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1125 f.debug_tuple("AdditionalConsensusStateDigest")
1126 .field(&self.0)
1127 .finish()
1128 }
1129}
1130
1131#[derive(
1132 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
1133)]
1134pub struct CheckpointArtifactsDigest(Digest);
1135
1136impl CheckpointArtifactsDigest {
1137 pub const fn new(digest: [u8; 32]) -> Self {
1138 Self(Digest::new(digest))
1139 }
1140
1141 pub const fn inner(&self) -> &[u8; 32] {
1142 self.0.inner()
1143 }
1144
1145 pub const fn into_inner(self) -> [u8; 32] {
1146 self.0.into_inner()
1147 }
1148
1149 pub fn base58_encode(&self) -> String {
1150 Base58::encode(self.0)
1151 }
1152
1153 pub fn from_artifact_digests(digests: Vec<Digest>) -> SuiResult<Self> {
1154 let bytes =
1155 bcs::to_bytes(&digests).map_err(|e| SuiError::from(format!("BCS error: {}", e)))?;
1156 Ok(Self(Digest::new(Blake2b256::digest(&bytes).into())))
1157 }
1158}
1159
1160impl From<[u8; 32]> for CheckpointArtifactsDigest {
1161 fn from(digest: [u8; 32]) -> Self {
1162 Self(Digest::new(digest))
1163 }
1164}
1165
1166impl fmt::Display for CheckpointArtifactsDigest {
1167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1168 fmt::Display::fmt(&self.0, f)
1169 }
1170}
1171
1172#[cfg(test)]
1173mod test {
1174 use crate::digests::{ChainIdentifier, SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE};
1175
1176 fn has_env_override() -> bool {
1177 SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE.is_some()
1178 }
1179
1180 #[test]
1182 fn test_chain_id_mainnet() {
1183 if has_env_override() {
1184 return;
1185 }
1186 let chain_id = ChainIdentifier::from_chain_short_id(&String::from("35834a8a"));
1187 assert_eq!(
1188 chain_id.unwrap().chain(),
1189 sui_protocol_config::Chain::Mainnet
1190 );
1191 }
1192
1193 #[test]
1194 fn test_chain_id_testnet() {
1195 if has_env_override() {
1196 return;
1197 }
1198 let chain_id = ChainIdentifier::from_chain_short_id(&String::from("4c78adac"));
1199 assert_eq!(
1200 chain_id.unwrap().chain(),
1201 sui_protocol_config::Chain::Testnet
1202 );
1203 }
1204
1205 #[test]
1206 fn test_chain_id_unknown() {
1207 if has_env_override() {
1208 return;
1209 }
1210 let chain_id = ChainIdentifier::from_chain_short_id(&String::from("unknown"));
1211 assert_eq!(chain_id, None);
1212 }
1213}