1use bc_crypto::hash::sha256;
2use dcbor::prelude::*;
3
4use crate::{Error, Result, digest_provider::DigestProvider, tags};
5
6#[derive(Clone, Copy, PartialEq, Eq, Hash)]
53pub struct Digest([u8; Self::DIGEST_SIZE]);
54
55impl Digest {
56 pub const DIGEST_SIZE: usize = 32;
57
58 pub fn from_data(data: [u8; Self::DIGEST_SIZE]) -> Self { Self(data) }
60
61 pub fn from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
65 let data = data.as_ref();
66 if data.len() != Self::DIGEST_SIZE {
67 return Err(Error::invalid_size(
68 "digest",
69 Self::DIGEST_SIZE,
70 data.len(),
71 ));
72 }
73 let mut arr = [0u8; Self::DIGEST_SIZE];
74 arr.copy_from_slice(data.as_ref());
75 Ok(Self::from_data(arr))
76 }
77
78 pub fn from_image(image: impl AsRef<[u8]>) -> Self {
82 Self::from_data(sha256(image.as_ref()))
83 }
84
85 pub fn from_image_parts(image_parts: &[&[u8]]) -> Self {
89 let mut buf = Vec::new();
90 for part in image_parts {
91 buf.extend_from_slice(part);
92 }
93 Self::from_image(&buf)
94 }
95
96 pub fn from_digests(digests: &[Digest]) -> Self {
100 let mut buf = Vec::new();
101 for digest in digests {
102 buf.extend_from_slice(digest.data());
103 }
104 Self::from_image(&buf)
105 }
106
107 pub fn data(&self) -> &[u8; Self::DIGEST_SIZE] { self.into() }
109
110 pub fn as_bytes(&self) -> &[u8] { self.as_ref() }
112
113 pub fn validate(&self, image: impl AsRef<[u8]>) -> bool {
118 self == &Self::from_image(image)
119 }
120
121 pub fn from_hex(hex: impl AsRef<str>) -> Self {
126 Self::from_data_ref(hex::decode(hex.as_ref()).unwrap()).unwrap()
127 }
128
129 pub fn hex(&self) -> String { hex::encode(self.0) }
131
132 pub fn short_description(&self) -> String { hex::encode(&self.0[0..4]) }
134
135 pub fn validate_opt(
141 image: impl AsRef<[u8]>,
142 digest: Option<Digest>,
143 ) -> bool {
144 match digest {
145 Some(digest) => digest.validate(image),
146 None => true,
147 }
148 }
149}
150
151impl<'a> From<&'a Digest> for &'a [u8; Digest::DIGEST_SIZE] {
153 fn from(value: &'a Digest) -> Self { &value.0 }
154}
155
156impl<'a> From<&'a Digest> for &'a [u8] {
158 fn from(value: &'a Digest) -> Self { &value.0 }
159}
160
161impl AsRef<[u8]> for Digest {
163 fn as_ref(&self) -> &[u8] { &self.0 }
164}
165
166impl std::cmp::PartialOrd for Digest {
168 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
169 Some(self.cmp(other))
170 }
171}
172
173impl std::cmp::Ord for Digest {
176 fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.0.cmp(&other.0) }
177}
178
179impl DigestProvider for Digest {
182 fn digest(&self) -> Digest { *self }
183}
184
185impl std::fmt::Debug for Digest {
187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188 write!(f, "Digest({})", self.hex())
189 }
190}
191
192impl std::fmt::Display for Digest {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 write!(f, "Digest({})", self.hex())
196 }
197}
198
199impl CBORTagged for Digest {
201 fn cbor_tags() -> Vec<Tag> { tags_for_values(&[tags::TAG_DIGEST]) }
202}
203
204impl From<Digest> for CBOR {
206 fn from(value: Digest) -> Self { value.tagged_cbor() }
207}
208
209impl CBORTaggedEncodable for Digest {
211 fn untagged_cbor(&self) -> CBOR { CBOR::to_byte_string(self.0) }
212}
213
214impl TryFrom<CBOR> for Digest {
216 type Error = dcbor::Error;
217
218 fn try_from(cbor: CBOR) -> std::result::Result<Self, Self::Error> {
219 Self::from_tagged_cbor(cbor)
220 }
221}
222
223impl CBORTaggedDecodable for Digest {
225 fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
226 let data = CBOR::try_into_byte_string(cbor)?;
227 Self::from_data_ref(data).map_err(|e| e.into())
228 }
229}
230
231impl From<Digest> for Vec<u8> {
233 fn from(digest: Digest) -> Self { digest.0.to_vec() }
234}
235
236#[cfg(test)]
237mod tests {
238 use bc_ur::prelude::*;
239 use hex_literal::hex;
240
241 use super::*;
242
243 #[test]
244 fn test_digest() {
245 let data = "hello world";
246 let digest = Digest::from_image(data.as_bytes());
247 assert_eq!(digest.data().len(), Digest::DIGEST_SIZE);
248 assert_eq!(*digest.data(), sha256(data.as_bytes()));
249 assert_eq!(
250 *digest.data(),
251 hex!(
252 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
253 )
254 );
255 }
256
257 #[test]
258 fn test_digest_from_hex() {
259 let digest = Digest::from_hex(
260 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
261 );
262 assert_eq!(digest.data().len(), Digest::DIGEST_SIZE);
263 assert_eq!(*digest.data(), sha256("hello world".as_bytes()));
264 assert_eq!(
265 *digest.data(),
266 hex!(
267 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
268 )
269 );
270 }
271
272 #[test]
273 fn test_ur() {
274 crate::register_tags();
275 let data = "hello world";
276 let digest = Digest::from_image(data.as_bytes());
277 let ur_string = digest.ur_string();
278 let expected_ur_string = "ur:digest/hdcxrhgtdirhmugtfmayondmgmtstnkipyzssslrwsvlkngulawymhloylpsvowssnwlamnlatrs";
279 assert_eq!(ur_string, expected_ur_string);
280 let digest2 = Digest::from_ur_string(&ur_string).unwrap();
281 assert_eq!(digest, digest2);
282 }
283
284 #[test]
285 fn test_digest_equality() {
286 let digest1 = Digest::from_hex(
287 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
288 );
289 let digest2 = Digest::from_hex(
290 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
291 );
292 assert_eq!(digest1, digest2);
293 }
294
295 #[test]
296 fn test_digest_inequality() {
297 let digest1 = Digest::from_hex(
298 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
299 );
300 let digest2 = Digest::from_hex(
301 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
302 );
303 assert_ne!(digest1, digest2);
304 }
305
306 #[test]
307 #[should_panic]
308 fn test_invalid_hex_string() {
309 let _ = Digest::from_hex("invalid_hex_string");
310 }
311
312 #[test]
313 #[should_panic]
314 fn test_new_from_invalid_ur_string() {
315 let invalid_ur = "ur:not_digest/invalid";
316 let _ = Digest::from_ur_string(invalid_ur).unwrap();
317 }
318}