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