sui_sdk_types/
digest.rs

1/// A 32-byte Blake2b256 hash output.
2///
3/// # BCS
4///
5/// A `Digest`'s BCS serialized form is defined by the following:
6///
7/// ```text
8/// digest = %x20 32OCTET
9/// ```
10///
11/// Due to historical reasons, even though a `Digest` has a fixed-length of 32, Sui's binary
12/// representation of a `Digest` is prefixed with its length meaning its serialized binary form (in
13/// bcs) is 33 bytes long vs a more compact 32 bytes.
14#[derive(Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
15#[cfg_attr(
16    feature = "serde",
17    derive(serde_derive::Serialize, serde_derive::Deserialize)
18)]
19#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
20pub struct Digest(
21    #[cfg_attr(feature = "serde", serde(with = "DigestSerialization"))] [u8; Self::LENGTH],
22);
23
24impl Digest {
25    /// A constant representing the length of a digest in bytes.
26    pub const LENGTH: usize = 32;
27    /// A constant representing a zero digest.
28    pub const ZERO: Self = Self([0; Self::LENGTH]);
29
30    /// Generates a new digest from the provided 32 byte array containing [`u8`] values.
31    pub const fn new(digest: [u8; Self::LENGTH]) -> Self {
32        Self(digest)
33    }
34
35    /// Generates a new digest from the provided random number generator.
36    #[cfg(feature = "rand")]
37    #[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
38    pub fn generate<R>(mut rng: R) -> Self
39    where
40        R: rand_core::RngCore + rand_core::CryptoRng,
41    {
42        let mut buf: [u8; Self::LENGTH] = [0; Self::LENGTH];
43        rng.fill_bytes(&mut buf);
44        Self::new(buf)
45    }
46
47    /// Returns a slice to the inner array representation of this digest.
48    pub const fn inner(&self) -> &[u8; Self::LENGTH] {
49        &self.0
50    }
51
52    /// Returns the inner array representation of this digest.
53    pub const fn into_inner(self) -> [u8; Self::LENGTH] {
54        self.0
55    }
56
57    /// Returns a slice of bytes representing the digest.
58    pub const fn as_bytes(&self) -> &[u8] {
59        &self.0
60    }
61
62    /// Decodes a digest from a Base58 encoded string.
63    pub fn from_base58<T: AsRef<[u8]>>(base58: T) -> Result<Self, DigestParseError> {
64        let mut buf = [0; Self::LENGTH];
65
66        bs58::decode(base58)
67            .onto(&mut buf)
68            //TODO fix error to contain bs58 parse error
69            .map_err(|_| DigestParseError)?;
70
71        Ok(Self(buf))
72    }
73
74    /// Returns a Base58 encoded string representation of this digest.
75    pub fn to_base58(&self) -> String {
76        self.to_string()
77    }
78
79    /// Generates a digest from bytes.
80    pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, DigestParseError> {
81        <[u8; Self::LENGTH]>::try_from(bytes.as_ref())
82            .map_err(|_| DigestParseError)
83            .map(Self)
84    }
85}
86
87impl std::str::FromStr for Digest {
88    type Err = DigestParseError;
89
90    fn from_str(s: &str) -> Result<Self, Self::Err> {
91        Self::from_base58(s)
92    }
93}
94
95impl AsRef<[u8]> for Digest {
96    fn as_ref(&self) -> &[u8] {
97        &self.0
98    }
99}
100
101impl AsRef<[u8; Self::LENGTH]> for Digest {
102    fn as_ref(&self) -> &[u8; Self::LENGTH] {
103        &self.0
104    }
105}
106
107impl From<Digest> for [u8; Digest::LENGTH] {
108    fn from(digest: Digest) -> Self {
109        digest.into_inner()
110    }
111}
112
113impl From<[u8; Self::LENGTH]> for Digest {
114    fn from(digest: [u8; Self::LENGTH]) -> Self {
115        Self::new(digest)
116    }
117}
118
119impl std::fmt::Display for Digest {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        // output size is determined via the following formula:
122        //      N * log(256) / log(58) + 1 (round up)
123        // where N = 32 this results in a value of 45
124        let mut buf = [0; 45];
125
126        let len = bs58::encode(&self.0).onto(&mut buf[..]).unwrap();
127        let encoded = std::str::from_utf8(&buf[..len]).unwrap();
128
129        f.write_str(encoded)
130    }
131}
132
133impl std::fmt::Debug for Digest {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        f.debug_tuple("Digest")
136            .field(&format_args!("\"{}\"", self))
137            .finish()
138    }
139}
140
141impl std::fmt::LowerHex for Digest {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        if f.alternate() {
144            write!(f, "0x")?;
145        }
146
147        for byte in self.0 {
148            write!(f, "{:02x}", byte)?;
149        }
150
151        Ok(())
152    }
153}
154
155// Unfortunately sui's binary representation of digests is prefixed with its length meaning its
156// serialized binary form is 33 bytes long (in bcs) vs a more compact 32 bytes.
157#[cfg(feature = "serde")]
158type DigestSerialization =
159    ::serde_with::As<::serde_with::IfIsHumanReadable<ReadableDigest, ::serde_with::Bytes>>;
160
161#[cfg(feature = "serde")]
162#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
163struct ReadableDigest;
164
165#[cfg(feature = "serde")]
166#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
167impl serde_with::SerializeAs<[u8; Digest::LENGTH]> for ReadableDigest {
168    fn serialize_as<S>(source: &[u8; Digest::LENGTH], serializer: S) -> Result<S::Ok, S::Error>
169    where
170        S: serde::Serializer,
171    {
172        let digest = Digest::new(*source);
173        serde_with::DisplayFromStr::serialize_as(&digest, serializer)
174    }
175}
176
177#[cfg(feature = "serde")]
178#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
179impl<'de> serde_with::DeserializeAs<'de, [u8; Digest::LENGTH]> for ReadableDigest {
180    fn deserialize_as<D>(deserializer: D) -> Result<[u8; Digest::LENGTH], D::Error>
181    where
182        D: serde::Deserializer<'de>,
183    {
184        let digest: Digest = serde_with::DisplayFromStr::deserialize_as(deserializer)?;
185        Ok(digest.into_inner())
186    }
187}
188
189#[derive(Clone, Copy, Debug, PartialEq, Eq)]
190pub struct DigestParseError;
191
192impl std::fmt::Display for DigestParseError {
193    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
194        write!(
195            f,
196            "Unable to parse Digest (must be Base58 string of length {})",
197            Digest::LENGTH
198        )
199    }
200}
201
202impl std::error::Error for DigestParseError {}
203
204//
205// Implement Various Digest wrappers
206//
207
208macro_rules! impl_digest {
209    ($t:ident) => {
210        #[derive(Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
211        #[cfg_attr(
212            feature = "serde",
213            derive(serde_derive::Serialize, serde_derive::Deserialize)
214        )]
215        #[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
216        pub struct $t(Digest);
217
218        impl $t {
219            pub const LENGTH: usize = Digest::LENGTH;
220            pub const ZERO: Self = Self::new([0; Self::LENGTH]);
221
222            pub const fn new(digest: [u8; Self::LENGTH]) -> Self {
223                Self(Digest::new(digest))
224            }
225
226            #[cfg(feature = "rand")]
227            #[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
228            pub fn generate<R>(rng: R) -> Self
229            where
230                R: rand_core::RngCore + rand_core::CryptoRng,
231            {
232                Self(Digest::generate(rng))
233            }
234
235            pub const fn inner(&self) -> &[u8; Self::LENGTH] {
236                self.0.inner()
237            }
238
239            pub const fn into_inner(self) -> [u8; Self::LENGTH] {
240                self.0.into_inner()
241            }
242
243            pub const fn as_bytes(&self) -> &[u8] {
244                self.0.as_bytes()
245            }
246
247            pub fn from_base58<T: AsRef<[u8]>>(base58: T) -> Result<Self, DigestParseError> {
248                Digest::from_base58(base58).map(Self)
249            }
250
251            #[allow(clippy::wrong_self_convention)]
252            pub fn to_base58(&self) -> String {
253                self.to_string()
254            }
255
256            pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, DigestParseError> {
257                Digest::from_bytes(bytes).map(Self)
258            }
259        }
260
261        impl std::str::FromStr for $t {
262            type Err = DigestParseError;
263
264            fn from_str(s: &str) -> Result<Self, Self::Err> {
265                Self::from_base58(s)
266            }
267        }
268
269        impl AsRef<[u8]> for $t {
270            fn as_ref(&self) -> &[u8] {
271                self.0.as_ref()
272            }
273        }
274
275        impl AsRef<[u8; Self::LENGTH]> for $t {
276            fn as_ref(&self) -> &[u8; Self::LENGTH] {
277                self.0.as_ref()
278            }
279        }
280
281        impl From<$t> for [u8; $t::LENGTH] {
282            fn from(digest: $t) -> Self {
283                digest.into_inner()
284            }
285        }
286
287        impl From<[u8; Self::LENGTH]> for $t {
288            fn from(digest: [u8; Self::LENGTH]) -> Self {
289                Self::new(digest)
290            }
291        }
292
293        impl From<Digest> for $t {
294            fn from(digest: Digest) -> Self {
295                Self(digest)
296            }
297        }
298
299        impl From<$t> for Digest {
300            fn from(digest: $t) -> Self {
301                digest.0
302            }
303        }
304
305        impl std::fmt::Display for $t {
306            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
307                std::fmt::Display::fmt(&self.0, f)
308            }
309        }
310
311        impl std::fmt::Debug for $t {
312            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313                f.debug_tuple(stringify!($t))
314                    .field(&format_args!("\"{}\"", self))
315                    .finish()
316            }
317        }
318
319        impl std::fmt::LowerHex for $t {
320            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321                std::fmt::LowerHex::fmt(&self.0, f)
322            }
323        }
324    };
325}
326
327impl_digest!(CheckpointDigest);
328impl_digest!(CheckpointContentsDigest);
329impl_digest!(TransactionDigest);
330impl_digest!(TransactionEffectsDigest);
331impl_digest!(TransactionEventsDigest);
332impl_digest!(ObjectDigest);
333impl_digest!(ConsensusCommitDigest);
334impl_digest!(EffectsAuxiliaryDataDigest);
335
336// Don't implement like the other digest types since this isn't intended to be serialized
337pub type SigningDigest = [u8; Digest::LENGTH];
338
339#[cfg(test)]
340mod test {
341    use super::*;
342    use test_strategy::proptest;
343
344    #[cfg(target_arch = "wasm32")]
345    use wasm_bindgen_test::wasm_bindgen_test as test;
346
347    #[proptest]
348    fn roundtrip_display_fromstr(digest: Digest) {
349        let s = digest.to_string();
350        let d = s.parse::<Digest>().unwrap();
351        assert_eq!(digest, d);
352    }
353}