sui_types/
digests.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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/// A representation of a 32 byte digest
20#[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        // TODO avoid the allocation
109        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/// Representation of a network's identifier by the genesis checkpoint's digest
148#[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
170/// For testing purposes or bootstrapping regenesis chain configuration, you can set
171/// this environment variable to force protocol config to use a specific Chain.
172pub 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    /// take a short 4 byte identifier and convert it into a ChainIdentifier
191    /// short ids come from the JSON RPC getChainIdentifier and are encoded in hex
192    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/// Representation of a Checkpoint's digest
279#[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/// A digest of a certificate, which commits to the signatures as well as the tx.
490#[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/// A digest of a SenderSignedData, which commits to the signatures as well as the tx.
510#[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/// A transaction will have a (unique) digest.
528#[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    /// A digest we use to signify the parent transaction was the genesis,
549    /// ie. for an object there is no parent digest.
550    /// Note that this is not the same as the digest of the genesis transaction,
551    /// which cannot be known ahead of time.
552    // TODO(https://github.com/MystenLabs/sui/issues/65): we can pick anything here
553    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// Each object has a unique digest
911#[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    /// A marker that signifies the object is deleted.
922    pub const OBJECT_DIGEST_DELETED: ObjectDigest =
923        Self::new([Self::OBJECT_DIGEST_DELETED_BYTE_VAL; 32]);
924
925    /// A marker that signifies the object is wrapped into another object.
926    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/// A digest of a ZkLoginInputs, which commits to the signatures as well as the tx.
1043#[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    // check that the chain id returns mainnet
1181    #[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}