1#[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 pub const LENGTH: usize = 32;
27 pub const ZERO: Self = Self([0; Self::LENGTH]);
29
30 pub const fn new(digest: [u8; Self::LENGTH]) -> Self {
32 Self(digest)
33 }
34
35 #[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 pub const fn inner(&self) -> &[u8; Self::LENGTH] {
49 &self.0
50 }
51
52 pub const fn into_inner(self) -> [u8; Self::LENGTH] {
54 self.0
55 }
56
57 pub const fn as_bytes(&self) -> &[u8] {
59 &self.0
60 }
61
62 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 .map_err(|_| DigestParseError)?;
70
71 Ok(Self(buf))
72 }
73
74 pub fn to_base58(&self) -> String {
76 self.to_string()
77 }
78
79 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 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#[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
204macro_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
336pub 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}