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