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