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