Skip to main content

runx_contracts/
fingerprint.rs

1//! Fingerprint contracts: content hashing identifiers.
2use serde::{Deserialize, Serialize};
3use sha2::{Digest, Sha256};
4
5use crate::Reference;
6use crate::schema::{NonEmptyString, RunxSchema};
7
8/// Lowercase hex encoding of raw bytes.
9///
10/// # Examples
11///
12/// ```rust,no_run
13/// use runx_contracts::fingerprint::hex_lower;
14///
15/// assert_eq!(hex_lower(&[0xde, 0xad, 0xbe, 0xef]), "deadbeef");
16/// ```
17#[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/// SHA-256 of the input bytes as lowercase hex (no prefix).
29///
30/// # Examples
31///
32/// ```rust,no_run
33/// use runx_contracts::fingerprint::sha256_hex;
34///
35/// assert_eq!(
36///     sha256_hex(b"runx"),
37///     "8186b7035bea2f66ebe27c1f5cf7de4e94ef935e259a2f3160352adffc752f28",
38/// );
39/// ```
40#[must_use]
41pub fn sha256_hex(bytes: &[u8]) -> String {
42    hex_lower(&Sha256::digest(bytes))
43}
44
45/// SHA-256 of the input bytes, prefixed with the `sha256:` algorithm tag.
46///
47/// This is the content-addressed form used for runx identifiers.
48///
49/// # Examples
50///
51/// ```rust,no_run
52/// use runx_contracts::fingerprint::sha256_prefixed;
53///
54/// let id = sha256_prefixed(b"runx");
55/// assert!(id.starts_with("sha256:"));
56/// assert_eq!(
57///     id,
58///     "sha256:8186b7035bea2f66ebe27c1f5cf7de4e94ef935e259a2f3160352adffc752f28",
59/// );
60/// ```
61#[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}