runx_contracts/
fingerprint.rs1use serde::{Deserialize, Serialize};
3use sha2::{Digest, Sha256};
4
5use crate::Reference;
6use crate::schema::{NonEmptyString, RunxSchema};
7
8#[must_use]
18pub fn hex_lower(bytes: &[u8]) -> String {
19 const HEX: &[u8; 16] = b"0123456789abcdef";
20 let mut encoded = String::with_capacity(bytes.len() * 2);
21 for byte in bytes {
22 encoded.push(HEX[(byte >> 4) as usize] as char);
23 encoded.push(HEX[(byte & 0x0f) as usize] as char);
24 }
25 encoded
26}
27
28#[must_use]
41pub fn sha256_hex(bytes: &[u8]) -> String {
42 hex_lower(&Sha256::digest(bytes))
43}
44
45#[must_use]
62pub fn sha256_prefixed(bytes: &[u8]) -> String {
63 format!("sha256:{}", sha256_hex(bytes))
64}
65
66#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
67#[serde(rename_all = "snake_case")]
68pub enum FingerprintAlgorithm {
69 Sha256,
70}
71
72#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
73#[serde(deny_unknown_fields)]
74pub struct Fingerprint {
75 pub algorithm: FingerprintAlgorithm,
76 pub canonicalization: NonEmptyString,
77 pub value: NonEmptyString,
78 pub derived_from: Vec<Reference>,
79}
80
81#[cfg(test)]
82mod tests {
83 use super::{hex_lower, sha256_hex, sha256_prefixed};
84
85 #[test]
86 fn hex_lower_encodes_bytes() {
87 assert_eq!(hex_lower(&[0xde, 0xad, 0xbe, 0xef]), "deadbeef");
88 }
89
90 #[test]
91 fn sha256_hex_hashes_bytes() {
92 assert_eq!(
93 sha256_hex(b"runx"),
94 "8186b7035bea2f66ebe27c1f5cf7de4e94ef935e259a2f3160352adffc752f28",
95 );
96 }
97
98 #[test]
99 fn sha256_prefixed_includes_algorithm_tag() {
100 assert_eq!(
101 sha256_prefixed(b"runx"),
102 "sha256:8186b7035bea2f66ebe27c1f5cf7de4e94ef935e259a2f3160352adffc752f28",
103 );
104 }
105}