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