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 buf = bs58::decode(base58.as_ref())
67 .into_array_const::<{ Self::LENGTH }>()
68 .map_err(|e| DigestParseError {
69 bs58_error: Some(e),
70 })?;
71
72 Ok(Self(buf))
73 }
74
75 pub const fn from_base58_unwrap(base58: &[u8]) -> Self {
79 let buf = bs58::decode(base58).into_array_const_unwrap::<{ Self::LENGTH }>();
80 Self(buf)
81 }
82
83 pub fn to_base58(&self) -> String {
85 self.to_string()
86 }
87
88 pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, DigestParseError> {
90 <[u8; Self::LENGTH]>::try_from(bytes.as_ref())
91 .map_err(|_| DigestParseError { bs58_error: None })
92 .map(Self)
93 }
94}
95
96impl std::str::FromStr for Digest {
97 type Err = DigestParseError;
98
99 fn from_str(s: &str) -> Result<Self, Self::Err> {
100 Self::from_base58(s)
101 }
102}
103
104impl AsRef<[u8]> for Digest {
105 fn as_ref(&self) -> &[u8] {
106 &self.0
107 }
108}
109
110impl AsRef<[u8; Self::LENGTH]> for Digest {
111 fn as_ref(&self) -> &[u8; Self::LENGTH] {
112 &self.0
113 }
114}
115
116impl From<Digest> for [u8; Digest::LENGTH] {
117 fn from(digest: Digest) -> Self {
118 digest.into_inner()
119 }
120}
121
122impl From<[u8; Self::LENGTH]> for Digest {
123 fn from(digest: [u8; Self::LENGTH]) -> Self {
124 Self::new(digest)
125 }
126}
127
128impl std::fmt::Display for Digest {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 let mut buf = [0; 45];
134
135 let len = bs58::encode(&self.0).onto(&mut buf[..]).unwrap();
136 let encoded = std::str::from_utf8(&buf[..len]).unwrap();
137
138 f.write_str(encoded)
139 }
140}
141
142impl From<Digest> for String {
143 fn from(value: Digest) -> Self {
144 value.to_string()
145 }
146}
147
148impl From<&Digest> for String {
149 fn from(value: &Digest) -> Self {
150 value.to_string()
151 }
152}
153
154impl std::fmt::Debug for Digest {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 f.debug_tuple("Digest")
157 .field(&format_args!("\"{self}\""))
158 .finish()
159 }
160}
161
162impl std::fmt::LowerHex for Digest {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 if f.alternate() {
165 write!(f, "0x")?;
166 }
167
168 for byte in self.0 {
169 write!(f, "{byte:02x}")?;
170 }
171
172 Ok(())
173 }
174}
175
176#[cfg(feature = "serde")]
179type DigestSerialization =
180 ::serde_with::As<::serde_with::IfIsHumanReadable<ReadableDigest, ::serde_with::Bytes>>;
181
182#[cfg(feature = "serde")]
183#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
184struct ReadableDigest;
185
186#[cfg(feature = "serde")]
187#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
188impl serde_with::SerializeAs<[u8; Digest::LENGTH]> for ReadableDigest {
189 fn serialize_as<S>(source: &[u8; Digest::LENGTH], serializer: S) -> Result<S::Ok, S::Error>
190 where
191 S: serde::Serializer,
192 {
193 let digest = Digest::new(*source);
194 serde_with::DisplayFromStr::serialize_as(&digest, serializer)
195 }
196}
197
198#[cfg(feature = "serde")]
199#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
200impl<'de> serde_with::DeserializeAs<'de, [u8; Digest::LENGTH]> for ReadableDigest {
201 fn deserialize_as<D>(deserializer: D) -> Result<[u8; Digest::LENGTH], D::Error>
202 where
203 D: serde::Deserializer<'de>,
204 {
205 let digest: Digest = serde_with::DisplayFromStr::deserialize_as(deserializer)?;
206 Ok(digest.into_inner())
207 }
208}
209
210#[derive(Clone, Copy, Debug, PartialEq, Eq)]
211pub struct DigestParseError {
212 bs58_error: Option<bs58::decode::Error>,
213}
214
215impl std::fmt::Display for DigestParseError {
216 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
217 write!(
218 f,
219 "Unable to parse Digest (must be Base58 string of length {})",
220 Digest::LENGTH
221 )
222 }
223}
224
225impl std::error::Error for DigestParseError {}
226
227pub type SigningDigest = [u8; Digest::LENGTH];
229
230#[cfg(test)]
231mod test {
232 use super::*;
233 use test_strategy::proptest;
234
235 #[cfg(target_arch = "wasm32")]
236 use wasm_bindgen_test::wasm_bindgen_test as test;
237
238 #[proptest]
239 fn roundtrip_display_fromstr(digest: Digest) {
240 let s = digest.to_string();
241 let d = s.parse::<Digest>().unwrap();
242 assert_eq!(digest, d);
243 }
244}