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))]
20#[doc(alias = "CheckpointDigest")]
21#[doc(alias = "TransactionDigest")]
22pub struct Digest(
23 #[cfg_attr(feature = "serde", serde(with = "DigestSerialization"))] [u8; Self::LENGTH],
24);
25
26impl Digest {
27 pub const LENGTH: usize = 32;
29 pub const ZERO: Self = Self([0; Self::LENGTH]);
31
32 pub const fn new(digest: [u8; Self::LENGTH]) -> Self {
34 Self(digest)
35 }
36
37 #[cfg(feature = "rand")]
39 #[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
40 pub fn generate<R>(mut rng: R) -> Self
41 where
42 R: rand_core::RngCore + rand_core::CryptoRng,
43 {
44 let mut buf: [u8; Self::LENGTH] = [0; Self::LENGTH];
45 rng.fill_bytes(&mut buf);
46 Self::new(buf)
47 }
48
49 pub const fn inner(&self) -> &[u8; Self::LENGTH] {
51 &self.0
52 }
53
54 pub const fn into_inner(self) -> [u8; Self::LENGTH] {
56 self.0
57 }
58
59 pub const fn as_bytes(&self) -> &[u8] {
61 &self.0
62 }
63
64 pub fn from_base58<T: AsRef<[u8]>>(base58: T) -> Result<Self, DigestParseError> {
66 let mut buf = [0; Self::LENGTH];
67
68 bs58::decode(base58)
69 .onto(&mut buf)
70 .map_err(|_| DigestParseError)?;
72
73 Ok(Self(buf))
74 }
75
76 pub fn to_base58(&self) -> String {
78 self.to_string()
79 }
80
81 pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, DigestParseError> {
83 <[u8; Self::LENGTH]>::try_from(bytes.as_ref())
84 .map_err(|_| DigestParseError)
85 .map(Self)
86 }
87}
88
89impl std::str::FromStr for Digest {
90 type Err = DigestParseError;
91
92 fn from_str(s: &str) -> Result<Self, Self::Err> {
93 Self::from_base58(s)
94 }
95}
96
97impl AsRef<[u8]> for Digest {
98 fn as_ref(&self) -> &[u8] {
99 &self.0
100 }
101}
102
103impl AsRef<[u8; Self::LENGTH]> for Digest {
104 fn as_ref(&self) -> &[u8; Self::LENGTH] {
105 &self.0
106 }
107}
108
109impl From<Digest> for [u8; Digest::LENGTH] {
110 fn from(digest: Digest) -> Self {
111 digest.into_inner()
112 }
113}
114
115impl From<[u8; Self::LENGTH]> for Digest {
116 fn from(digest: [u8; Self::LENGTH]) -> Self {
117 Self::new(digest)
118 }
119}
120
121impl std::fmt::Display for Digest {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 let mut buf = [0; 45];
127
128 let len = bs58::encode(&self.0).onto(&mut buf[..]).unwrap();
129 let encoded = std::str::from_utf8(&buf[..len]).unwrap();
130
131 f.write_str(encoded)
132 }
133}
134
135impl std::fmt::Debug for Digest {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 f.debug_tuple("Digest")
138 .field(&format_args!("\"{self}\""))
139 .finish()
140 }
141}
142
143impl std::fmt::LowerHex for Digest {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 if f.alternate() {
146 write!(f, "0x")?;
147 }
148
149 for byte in self.0 {
150 write!(f, "{byte:02x}")?;
151 }
152
153 Ok(())
154 }
155}
156
157#[cfg(feature = "serde")]
160type DigestSerialization =
161 ::serde_with::As<::serde_with::IfIsHumanReadable<ReadableDigest, ::serde_with::Bytes>>;
162
163#[cfg(feature = "serde")]
164#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
165struct ReadableDigest;
166
167#[cfg(feature = "serde")]
168#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
169impl serde_with::SerializeAs<[u8; Digest::LENGTH]> for ReadableDigest {
170 fn serialize_as<S>(source: &[u8; Digest::LENGTH], serializer: S) -> Result<S::Ok, S::Error>
171 where
172 S: serde::Serializer,
173 {
174 let digest = Digest::new(*source);
175 serde_with::DisplayFromStr::serialize_as(&digest, serializer)
176 }
177}
178
179#[cfg(feature = "serde")]
180#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
181impl<'de> serde_with::DeserializeAs<'de, [u8; Digest::LENGTH]> for ReadableDigest {
182 fn deserialize_as<D>(deserializer: D) -> Result<[u8; Digest::LENGTH], D::Error>
183 where
184 D: serde::Deserializer<'de>,
185 {
186 let digest: Digest = serde_with::DisplayFromStr::deserialize_as(deserializer)?;
187 Ok(digest.into_inner())
188 }
189}
190
191#[derive(Clone, Copy, Debug, PartialEq, Eq)]
192pub struct DigestParseError;
193
194impl std::fmt::Display for DigestParseError {
195 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
196 write!(
197 f,
198 "Unable to parse Digest (must be Base58 string of length {})",
199 Digest::LENGTH
200 )
201 }
202}
203
204impl std::error::Error for DigestParseError {}
205
206pub type SigningDigest = [u8; Digest::LENGTH];
208
209#[cfg(test)]
210mod test {
211 use super::*;
212 use test_strategy::proptest;
213
214 #[cfg(target_arch = "wasm32")]
215 use wasm_bindgen_test::wasm_bindgen_test as test;
216
217 #[proptest]
218 fn roundtrip_display_fromstr(digest: Digest) {
219 let s = digest.to_string();
220 let d = s.parse::<Digest>().unwrap();
221 assert_eq!(digest, d);
222 }
223}