Skip to main content

uor_addr/
hash.rs

1//! `crate::hash` — the pluggable σ-axis hash family (wiki ADR-007 /
2//! ADR-010: the substrate ships no hasher; the application selects one).
3//!
4//! UOR-ADDR's κ-label is `<algorithm>:<lowercase-hex-digest>`. The
5//! algorithm is the realization's selected σ-axis `H`; ψ₉ folds the
6//! canonical carrier through `H` and formats the label
7//! ([`crate::resolvers`]). [`AddrHash`] is the **fingerprint-width-erased**
8//! façade over a concrete prism [`Hasher`]: it carries the wire prefix
9//! (`"sha256"`, `"blake3"`, …), the digest width, and a `digest_carrier`
10//! method that folds the (streamed) carrier through the axis and returns
11//! the digest in a fixed [`MAX_DIGEST_BYTES`] buffer.
12//!
13//! Erasing the `Hasher<FP_MAX>` const-generic into the [`AddrHash`] method
14//! is what lets the *single* [`AddressResolverTuple`](crate::resolvers)
15//! carry both the 32-byte axes and the 64-byte `Sha512Hasher` without a
16//! free `FP_MAX` parameter (which would be unconstrained — E0207 — in the
17//! tuple's `Has*Resolver` impls). The model still binds the concrete
18//! `Hasher<FP_MAX>` as its σ-axis, so the foundation pipeline computes the
19//! full-width content fingerprint.
20//!
21//! ## Admissible axes
22//!
23//! foundation 0.5.2 generalized the resolver tower over `FP_MAX`, so every
24//! prism hasher is admissible:
25//!
26//! | axis | `LABEL_PREFIX` | `OUTPUT_BYTES` | `LABEL_BYTES` | authority |
27//! |------|----------------|----------------|---------------|-----------|
28//! | [`Sha256Hasher`]    | `sha256`    | 32 | 71  | FIPS 180-4 §6.2 |
29//! | [`Blake3Hasher`]    | `blake3`    | 32 | 71  | BLAKE3 §2 (the reference spec) |
30//! | [`Sha3_256Hasher`]  | `sha3-256`  | 32 | 73  | FIPS 202 §6.1 |
31//! | [`Keccak256Hasher`] | `keccak256` | 32 | 74  | Keccak SHA-3 submission (pre-FIPS padding) |
32//! | [`Sha512Hasher`]    | `sha512`    | 64 | 135 | FIPS 180-4 §6.4 |
33//!
34//! [`Hasher`]: prism::vocabulary::Hasher
35//! [`Sha256Hasher`]: prism::crypto::Sha256Hasher
36//! [`Blake3Hasher`]: prism::crypto::Blake3Hasher
37//! [`Sha3_256Hasher`]: prism::crypto::Sha3_256Hasher
38//! [`Keccak256Hasher`]: prism::crypto::Keccak256Hasher
39//! [`Sha512Hasher`]: prism::crypto::Sha512Hasher
40
41use prism::crypto::{Blake3Hasher, Keccak256Hasher, Sha256Hasher, Sha3_256Hasher, Sha512Hasher};
42use prism::operation::TermValue;
43use prism::vocabulary::Hasher;
44
45/// The widest admissible digest (`Sha512Hasher` = 64 bytes). Every
46/// [`AddrHash::digest_carrier`] returns a buffer of this width; the first
47/// `OUTPUT_BYTES` are significant.
48pub const MAX_DIGEST_BYTES: usize = 64;
49
50/// The κ-label ASCII byte width for a `<prefix>:<hex>` label over a
51/// `digest_bytes`-wide digest: `prefix.len() + 1 (':') + 2 × digest_bytes`.
52#[must_use]
53pub const fn label_bytes(prefix: &str, digest_bytes: usize) -> usize {
54    prefix.len() + 1 + 2 * digest_bytes
55}
56
57/// The widest admissible κ-label (`sha512:` + 128 hex = 135). The κ-label
58/// formatter ([`crate::resolvers`]) sizes its stack scratch to this and
59/// writes the active axis's `LABEL_BYTES` prefix.
60pub const MAX_LABEL_BYTES: usize = label_bytes("sha512", MAX_DIGEST_BYTES);
61
62/// A prism hasher usable as a UOR-ADDR σ-axis. Fingerprint-width-erased:
63/// the κ-label prefix + digest width are associated consts, and
64/// [`digest_carrier`](AddrHash::digest_carrier) folds the carrier through
65/// the concrete `Hasher<FP_MAX>` internally.
66pub trait AddrHash {
67    /// The lowercase algorithm token at the head of the κ-label.
68    const LABEL_PREFIX: &'static str;
69
70    /// The σ-axis digest width in bytes (`Hasher::OUTPUT_BYTES`).
71    const OUTPUT_BYTES: usize;
72
73    /// Total κ-label ASCII width = `LABEL_PREFIX.len() + 1 + 2 ×
74    /// OUTPUT_BYTES`. The realization's output shape declares exactly this
75    /// many `Site` constraints, and the entry point returns
76    /// [`KappaLabel`](crate::label::KappaLabel)`<{LABEL_BYTES}>`.
77    const LABEL_BYTES: usize = label_bytes(Self::LABEL_PREFIX, Self::OUTPUT_BYTES);
78
79    /// Fold the (streamed) canonical carrier through this σ-axis, returning
80    /// the digest in a [`MAX_DIGEST_BYTES`] buffer (first `OUTPUT_BYTES`
81    /// significant; the rest zero). Bounded resident memory — never
82    /// materializes the carrier.
83    fn digest_carrier<const N: usize>(input: &TermValue<'_, N>) -> [u8; MAX_DIGEST_BYTES];
84}
85
86/// Stream-fold a carrier through a concrete `Hasher<FP>` with bounded
87/// resident memory.
88fn stream<const N: usize, const FP: usize, H: Hasher<FP>>(input: &TermValue<'_, N>) -> [u8; FP] {
89    let mut h = H::initial();
90    input.for_each_chunk(&mut |chunk| {
91        let cur = core::mem::replace(&mut h, H::initial());
92        h = cur.fold_bytes(chunk);
93    });
94    h.finalize()
95}
96
97macro_rules! impl_addr_hash {
98    ($hasher:ty, $prefix:literal, $fp:literal) => {
99        impl AddrHash for $hasher {
100            const LABEL_PREFIX: &'static str = $prefix;
101            const OUTPUT_BYTES: usize = $fp;
102            fn digest_carrier<const N: usize>(input: &TermValue<'_, N>) -> [u8; MAX_DIGEST_BYTES] {
103                let digest = stream::<N, $fp, $hasher>(input);
104                let mut out = [0u8; MAX_DIGEST_BYTES];
105                out[..$fp].copy_from_slice(&digest);
106                out
107            }
108        }
109    };
110}
111
112impl_addr_hash!(Sha256Hasher, "sha256", 32);
113impl_addr_hash!(Blake3Hasher, "blake3", 32);
114impl_addr_hash!(Sha3_256Hasher, "sha3-256", 32);
115impl_addr_hash!(Keccak256Hasher, "keccak256", 32);
116impl_addr_hash!(Sha512Hasher, "sha512", 64);
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn label_widths_match_the_specification() {
124        assert_eq!(Sha256Hasher::LABEL_BYTES, 71);
125        assert_eq!(Blake3Hasher::LABEL_BYTES, 71);
126        assert_eq!(Sha3_256Hasher::LABEL_BYTES, 73);
127        assert_eq!(Keccak256Hasher::LABEL_BYTES, 74);
128        assert_eq!(Sha512Hasher::LABEL_BYTES, 135);
129        assert_eq!(MAX_LABEL_BYTES, 135);
130        assert_eq!(MAX_DIGEST_BYTES, 64);
131    }
132
133    #[test]
134    fn output_widths_match_the_axis() {
135        assert_eq!(<Sha256Hasher as AddrHash>::OUTPUT_BYTES, 32);
136        assert_eq!(<Sha512Hasher as AddrHash>::OUTPUT_BYTES, 64);
137    }
138}