1use std::borrow::Cow;
2
3use anyhow::{Result, bail};
4use bc_crypto::hash::sha256;
5use dcbor::prelude::*;
6
7use crate::{digest_provider::DigestProvider, tags};
8
9#[derive(Clone, PartialEq, Eq, Hash)]
56pub struct Digest([u8; Self::DIGEST_SIZE]);
57
58impl Digest {
59 pub const DIGEST_SIZE: usize = 32;
60
61 pub fn from_data(data: [u8; Self::DIGEST_SIZE]) -> Self { Self(data) }
63
64 pub fn from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
68 let data = data.as_ref();
69 if data.len() != Self::DIGEST_SIZE {
70 bail!("Invalid digest size");
71 }
72 let mut arr = [0u8; Self::DIGEST_SIZE];
73 arr.copy_from_slice(data.as_ref());
74 Ok(Self::from_data(arr))
75 }
76
77 pub fn from_image(image: impl AsRef<[u8]>) -> Self {
81 Self::from_data(sha256(image.as_ref()))
82 }
83
84 pub fn from_image_parts(image_parts: &[&[u8]]) -> Self {
88 let mut buf = Vec::new();
89 for part in image_parts {
90 buf.extend_from_slice(part);
91 }
92 Self::from_image(&buf)
93 }
94
95 pub fn from_digests(digests: &[Digest]) -> Self {
99 let mut buf = Vec::new();
100 for digest in digests {
101 buf.extend_from_slice(digest.data());
102 }
103 Self::from_image(&buf)
104 }
105
106 pub fn data(&self) -> &[u8; Self::DIGEST_SIZE] { self.into() }
108
109 pub fn as_bytes(&self) -> &[u8] { self.as_ref() }
111
112 pub fn validate(&self, image: impl AsRef<[u8]>) -> bool {
117 self == &Self::from_image(image)
118 }
119
120 pub fn from_hex(hex: impl AsRef<str>) -> Self {
125 Self::from_data_ref(hex::decode(hex.as_ref()).unwrap()).unwrap()
126 }
127
128 pub fn hex(&self) -> String { hex::encode(self.0) }
130
131 pub fn short_description(&self) -> String { hex::encode(&self.0[0..4]) }
133
134 pub fn validate_opt(
140 image: impl AsRef<[u8]>,
141 digest: Option<&Digest>,
142 ) -> bool {
143 match digest {
144 Some(digest) => digest.validate(image),
145 None => true,
146 }
147 }
148}
149
150impl<'a> From<&'a Digest> for &'a [u8; Digest::DIGEST_SIZE] {
152 fn from(value: &'a Digest) -> Self { &value.0 }
153}
154
155impl<'a> From<&'a Digest> for &'a [u8] {
157 fn from(value: &'a Digest) -> Self { &value.0 }
158}
159
160impl AsRef<[u8]> for Digest {
162 fn as_ref(&self) -> &[u8] { &self.0 }
163}
164
165impl AsRef<Digest> for Digest {
167 fn as_ref(&self) -> &Digest { self }
168}
169
170impl std::cmp::PartialOrd for Digest {
172 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
173 Some(self.0.cmp(&other.0))
174 }
175}
176
177impl std::cmp::Ord for Digest {
180 fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.0.cmp(&other.0) }
181}
182
183impl DigestProvider for Digest {
186 fn digest(&self) -> Cow<'_, Digest> { Cow::Borrowed(self) }
187}
188
189impl std::fmt::Debug for Digest {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 write!(f, "Digest({})", self.hex())
193 }
194}
195
196impl std::fmt::Display for Digest {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 write!(f, "Digest({})", self.hex())
200 }
201}
202
203impl CBORTagged for Digest {
205 fn cbor_tags() -> Vec<Tag> { tags_for_values(&[tags::TAG_DIGEST]) }
206}
207
208impl From<Digest> for CBOR {
210 fn from(value: Digest) -> Self { value.tagged_cbor() }
211}
212
213impl CBORTaggedEncodable for Digest {
215 fn untagged_cbor(&self) -> CBOR { CBOR::to_byte_string(self.0) }
216}
217
218impl TryFrom<CBOR> for Digest {
220 type Error = dcbor::Error;
221
222 fn try_from(cbor: CBOR) -> Result<Self, Self::Error> {
223 Self::from_tagged_cbor(cbor)
224 }
225}
226
227impl CBORTaggedDecodable for Digest {
229 fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
230 let data = CBOR::try_into_byte_string(cbor)?;
231 Ok(Self::from_data_ref(data)?)
232 }
233}
234
235impl From<&Digest> for Digest {
237 fn from(digest: &Digest) -> Self { digest.clone() }
238}
239
240impl From<Digest> for Vec<u8> {
242 fn from(digest: Digest) -> Self { digest.0.to_vec() }
243}
244
245impl From<&Digest> for Vec<u8> {
247 fn from(digest: &Digest) -> Self { digest.0.to_vec() }
248}
249
250#[cfg(test)]
251mod tests {
252 use bc_ur::prelude::*;
253 use hex_literal::hex;
254
255 use super::*;
256
257 #[test]
258 fn test_digest() {
259 let data = "hello world";
260 let digest = Digest::from_image(data.as_bytes());
261 assert_eq!(digest.data().len(), Digest::DIGEST_SIZE);
262 assert_eq!(*digest.data(), sha256(data.as_bytes()));
263 assert_eq!(
264 *digest.data(),
265 hex!(
266 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
267 )
268 );
269 }
270
271 #[test]
272 fn test_digest_from_hex() {
273 let digest = Digest::from_hex(
274 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
275 );
276 assert_eq!(digest.data().len(), Digest::DIGEST_SIZE);
277 assert_eq!(*digest.data(), sha256("hello world".as_bytes()));
278 assert_eq!(
279 *digest.data(),
280 hex!(
281 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
282 )
283 );
284 }
285
286 #[test]
287 fn test_ur() {
288 crate::register_tags();
289 let data = "hello world";
290 let digest = Digest::from_image(data.as_bytes());
291 let ur_string = digest.ur_string();
292 let expected_ur_string = "ur:digest/hdcxrhgtdirhmugtfmayondmgmtstnkipyzssslrwsvlkngulawymhloylpsvowssnwlamnlatrs";
293 assert_eq!(ur_string, expected_ur_string);
294 let digest2 = Digest::from_ur_string(&ur_string).unwrap();
295 assert_eq!(digest, digest2);
296 }
297
298 #[test]
299 fn test_digest_equality() {
300 let digest1 = Digest::from_hex(
301 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
302 );
303 let digest2 = Digest::from_hex(
304 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
305 );
306 assert_eq!(digest1, digest2);
307 }
308
309 #[test]
310 fn test_digest_inequality() {
311 let digest1 = Digest::from_hex(
312 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
313 );
314 let digest2 = Digest::from_hex(
315 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
316 );
317 assert_ne!(digest1, digest2);
318 }
319
320 #[test]
321 #[should_panic]
322 fn test_invalid_hex_string() {
323 let _ = Digest::from_hex("invalid_hex_string");
324 }
325
326 #[test]
327 #[should_panic]
328 fn test_new_from_invalid_ur_string() {
329 let invalid_ur = "ur:not_digest/invalid";
330 let _ = Digest::from_ur_string(invalid_ur).unwrap();
331 }
332}