|
| 1 | +//! Regression: a 64-byte-fingerprint hasher (`Sha512Hasher`, i.e. |
| 2 | +//! `Hasher<64>`) must flow through the principal data path. |
| 3 | +//! |
| 4 | +//! Foundation 0.5.1 pinned the entire resolver/pipeline tower to |
| 5 | +//! `Hasher<FP_MAX = 32>` — the `AxisTuple` blanket impl and every ψ-stage |
| 6 | +//! resolver trait required `FP_MAX = 32`, so `Sha512Hasher` |
| 7 | +//! (`Hasher<64>`) could not be selected as the substrate hasher at all; |
| 8 | +//! a downstream project pinned to prism 0.3.1 / foundation 0.5.1 was |
| 9 | +//! blocked. Foundation 0.5.2 generalized the tower: the `AxisTuple` |
| 10 | +//! blanket is `impl<INLINE_BYTES, FP_MAX, H: Hasher<FP_MAX>>`, the |
| 11 | +//! resolver traits take an unbounded `H`, and `run` / `run_route` / |
| 12 | +//! `Grounded` / `PrismModel` carry `FP_MAX` as a const parameter. |
| 13 | +//! |
| 14 | +//! This test grounds a unit through `run` with `Sha512Hasher` at |
| 15 | +//! `FP_MAX = 64`, asserts the certificate's fingerprint width is the |
| 16 | +//! full 64 bytes, and verifies the QS-05 replay round-trip — the |
| 17 | +//! conformance witness that a wide hasher flows end-to-end. |
| 18 | +
|
| 19 | +#![allow(clippy::unwrap_used, clippy::expect_used)] |
| 20 | + |
| 21 | +use prism::crypto::Sha512Hasher; |
| 22 | +use prism::operation::Term; |
| 23 | +use prism::pipeline::run; |
| 24 | +use prism::replay::certify_from_trace; |
| 25 | +use prism::seal::Validated; |
| 26 | +use prism::std_types::ConstrainedTypeInput; |
| 27 | +use prism::vocabulary::{CompileUnitBuilder, Hasher, HostBounds, VerificationDomain, WittLevel}; |
| 28 | + |
| 29 | +/// An application `HostBounds` whose `FINGERPRINT_MAX_BYTES` is 64, |
| 30 | +/// admitting a 64-byte (SHA-512-class) fingerprint. Per ADR-060 the |
| 31 | +/// foundation ships no default bounds; this is the test's declaration. |
| 32 | +/// Values match the canonical profile except the doubled fingerprint |
| 33 | +/// ceiling. |
| 34 | +struct Bounds64; |
| 35 | +impl HostBounds for Bounds64 { |
| 36 | + const FINGERPRINT_MIN_BYTES: usize = 32; |
| 37 | + const FINGERPRINT_MAX_BYTES: usize = 64; |
| 38 | + const TRACE_MAX_EVENTS: usize = 256; |
| 39 | + const WITT_LEVEL_MAX_BITS: u32 = 64; |
| 40 | + const FOLD_UNROLL_THRESHOLD: usize = 8; |
| 41 | + const BETTI_DIMENSION_MAX: usize = 8; |
| 42 | + const NERVE_CONSTRAINTS_MAX: usize = 8; |
| 43 | + const NERVE_SITES_MAX: usize = 8; |
| 44 | + const JACOBIAN_SITES_MAX: usize = 8; |
| 45 | + const RECURSION_TRACE_DEPTH_MAX: usize = 16; |
| 46 | + const OP_CHAIN_DEPTH_MAX: usize = 8; |
| 47 | + const AFFINE_COEFFS_MAX: usize = 8; |
| 48 | + const CONJUNCTION_TERMS_MAX: usize = 8; |
| 49 | + const UNFOLD_ITERATIONS_MAX: usize = 256; |
| 50 | +} |
| 51 | + |
| 52 | +// The inline carrier width derived from the 64-byte-fingerprint bounds, |
| 53 | +// and the matching `FP_MAX`. |
| 54 | +const CARRIER: usize = uor_foundation::pipeline::carrier_inline_bytes::<Bounds64>(); |
| 55 | +const FP_MAX: usize = 64; |
| 56 | + |
| 57 | +const ROOT_TERMS: &[Term<'static, CARRIER>] = &[Term::Literal { |
| 58 | + value: prism::operation::TermValue::from_u64_be(7, 1), |
| 59 | + level: WittLevel::W8, |
| 60 | +}]; |
| 61 | +static DOMAINS: &[VerificationDomain] = &[VerificationDomain::Enumerative]; |
| 62 | + |
| 63 | +#[test] |
| 64 | +fn sha512_hasher_flows_through_the_pipeline() { |
| 65 | + // `Sha512Hasher: Hasher<64>` — the case foundation 0.5.1 rejected. |
| 66 | + let unit: Validated<_> = CompileUnitBuilder::new() |
| 67 | + .root_term(ROOT_TERMS) |
| 68 | + .witt_level_ceiling(WittLevel::W32) |
| 69 | + .thermodynamic_budget(2048) |
| 70 | + .target_domains(DOMAINS) |
| 71 | + .result_type::<ConstrainedTypeInput>() |
| 72 | + .validate() |
| 73 | + .expect("unit well-formed"); |
| 74 | + |
| 75 | + let grounded = run::<ConstrainedTypeInput, _, Sha512Hasher, CARRIER, FP_MAX>(unit) |
| 76 | + .expect("pipeline admits a 64-byte-fingerprint hasher (0.5.2 fix)"); |
| 77 | + |
| 78 | + // The full SHA-512 width flows into the certificate fingerprint — |
| 79 | + // not truncated to the old FP_MAX = 32 ceiling. |
| 80 | + assert_eq!( |
| 81 | + usize::from(grounded.content_fingerprint().width_bytes()), |
| 82 | + <Sha512Hasher as Hasher<64>>::OUTPUT_BYTES, |
| 83 | + "fingerprint width must equal Sha512Hasher::OUTPUT_BYTES (64)", |
| 84 | + ); |
| 85 | + assert_eq!( |
| 86 | + usize::from(grounded.content_fingerprint().width_bytes()), |
| 87 | + 64 |
| 88 | + ); |
| 89 | + |
| 90 | + // QS-05 replay round-trip holds for the wide hasher. `replay`'s |
| 91 | + // `TR_MAX` is the trace-buffer bound (the bounds' `TRACE_MAX_EVENTS`); |
| 92 | + // its `FP_MAX = 64` flows from the `Derivation`, so |
| 93 | + // `certify_from_trace` re-derives a `ContentFingerprint<64>`. |
| 94 | + let trace = grounded |
| 95 | + .derivation() |
| 96 | + .replay::<{ <Bounds64 as HostBounds>::TRACE_MAX_EVENTS }>(); |
| 97 | + assert!(usize::from(trace.len()) <= <Bounds64 as HostBounds>::TRACE_MAX_EVENTS); |
| 98 | + let recertified = certify_from_trace(&trace).expect("trace well-formed"); |
| 99 | + assert_eq!( |
| 100 | + recertified.certificate().content_fingerprint(), |
| 101 | + grounded.content_fingerprint(), |
| 102 | + "QS-05: re-certified fingerprint must equal source for a 64-byte hasher", |
| 103 | + ); |
| 104 | +} |
0 commit comments