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}