h33_substrate_verifier/lib.rs
1//! # h33-substrate-verifier
2//!
3//! Reference implementation of the H33 substrate response attestation
4//! verifier.
5//!
6//! Every HTTP response from a H33 API carries four attestation headers:
7//!
8//! ```text
9//! X-H33-Substrate: <64 hex chars> SHA3-256 of the response body
10//! X-H33-Receipt: <84 hex chars> 42-byte CompactReceipt
11//! X-H33-Algorithms: ML-DSA-65,FALCON-512,SPHINCS+-SHA2-128f
12//! X-H33-Substrate-Ts: <ms since epoch> Substrate mint timestamp
13//! ```
14//!
15//! This crate verifies those headers against the response body and
16//! returns a structured [`VerificationResult`] that the calling code
17//! can inspect per family.
18//!
19//! ## Verification model
20//!
21//! The H33 substrate pipeline destroys the raw ephemeral Dilithium,
22//! FALCON, and SPHINCS+ signatures after they are verified on the
23//! signing host. What the customer receives is the 42-byte
24//! [`CompactReceipt`](receipt::CompactReceipt) — a cryptographic
25//! *verification certificate* — plus the 32-byte body hash.
26//!
27//! The verifier performs four independent integrity checks:
28//!
29//! 1. **Body binding.** Compute `SHA3-256(body_bytes)` locally and
30//! confirm it matches the `X-H33-Substrate` header. Proves the
31//! body has not been tampered with in transit.
32//! 2. **Receipt structure.** Parse the 42-byte `CompactReceipt` and
33//! confirm the version byte, size, and algorithm flags are valid.
34//! 3. **Algorithm agreement.** Confirm the algorithm names in the
35//! `X-H33-Algorithms` header match the algorithm flags inside the
36//! receipt. Detects header stripping or algorithm downgrade.
37//! 4. **Timestamp agreement.** Confirm the millisecond timestamp in
38//! `X-H33-Substrate-Ts` matches the timestamp embedded in the
39//! receipt. Detects timestamp stripping.
40//!
41//! All four checks are **local and fully offline** — no network, no
42//! async, no I/O.
43//!
44//! ### What this verifier does NOT do (yet)
45//!
46//! Full raw-signature re-verification requires the ephemeral Dilithium,
47//! FALCON, and SPHINCS+ signatures, which the H33 pipeline destroys
48//! after attestation. When scif-backend ships persistent signature
49//! storage (Tier 3.2 — permanent receipt storage on Arweave) and
50//! exposes the substrate nonce, this crate will grow a second
51//! verification path that recomputes each of the three PQ signatures
52//! locally. Until then, structural verification is the security
53//! boundary.
54//!
55//! ## Minimum viable example
56//!
57//! ```no_run
58//! use h33_substrate_verifier::{Verifier, Headers};
59//!
60//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
61//! let body_bytes = b"{\"tenant_id\":\"t_abc\",\"plan\":\"premium\"}";
62//!
63//! // Parse the four X-H33-* headers the server sent back.
64//! let headers = Headers::from_strs(
65//! "f3a8b2c1deadbeef...64chars...",
66//! "012e891fa4cafebabedeadbeef...84chars...",
67//! "ML-DSA-65,FALCON-512,SPHINCS+-SHA2-128f",
68//! 1_733_942_731_234,
69//! );
70//!
71//! // Verify.
72//! let verifier = Verifier::new();
73//! let result = verifier.verify(body_bytes, &headers)?;
74//!
75//! assert!(result.is_valid());
76//! assert!(result.body_hash_matches);
77//! assert!(result.receipt_well_formed);
78//! assert!(result.algorithms_match_flags);
79//! assert!(result.timestamps_agree);
80//! # Ok(())
81//! # }
82//! ```
83//!
84//! ## Feature flags
85//!
86//! | Feature | Default | What it does |
87//! |---|---|---|
88//! | `std` | yes | Use `std::error::Error` on the error type and enable the `reqwest-support` convenience helpers |
89//! | `dilithium` | yes | Enable the Dilithium signature algorithm identifier mapping (no raw verification until Tier 3) |
90//! | `falcon` | yes | Enable the FALCON-512 algorithm identifier mapping |
91//! | `sphincs` | yes | Enable the SPHINCS+-SHA2-128f algorithm identifier mapping |
92//! | `reqwest-support` | no | Pull in reqwest and expose `Headers::from_reqwest` for one-line extraction from an HTTP response |
93//!
94//! A WASM build can disable every family feature flag and still run the
95//! four structural checks — SHA3-256, hex decoding, and byte comparisons
96//! are all pure Rust and compile to `wasm32-unknown-unknown` without any
97//! platform-specific dependency.
98//!
99//! ## Security
100//!
101//! - `#![forbid(unsafe_code)]` — the crate contains zero `unsafe` blocks.
102//! - `#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]`
103//! — library code never panics on malformed input; every fallible
104//! operation returns a [`VerifierError`].
105//! - Property-based tests with `proptest` exercise random header inputs
106//! and confirm the verifier never panics.
107//! - Criterion benchmarks validate the verification path runs in
108//! sub-millisecond time on commodity hardware.
109//!
110//! ## License
111//!
112//! Proprietary. Commercial use requires a license from H33.ai, Inc.
113//! Source is open for research, audit, and reference-implementation
114//! purposes. Patent pending — H33 substrate Claims 124-125.
115
116#![forbid(unsafe_code)]
117#![deny(
118 missing_docs,
119 rust_2018_idioms,
120 clippy::unwrap_used,
121 clippy::expect_used,
122 clippy::panic,
123 clippy::indexing_slicing,
124 clippy::float_cmp
125)]
126#![warn(
127 unreachable_pub,
128 clippy::pedantic,
129 clippy::nursery,
130 clippy::integer_division
131)]
132#![allow(clippy::module_name_repetitions, clippy::missing_errors_doc)]
133#![cfg_attr(not(feature = "std"), no_std)]
134// Tests legitimately unwrap/expect/panic — a test panic IS the failure mode.
135// Relax the strictest lints inside test code so every assert! doesn't have to
136// be rewritten as an if-let-ok-else-return-Err dance.
137#![cfg_attr(
138 test,
139 allow(
140 clippy::unwrap_used,
141 clippy::expect_used,
142 clippy::panic,
143 clippy::indexing_slicing,
144 clippy::too_many_lines
145 )
146)]
147
148extern crate alloc;
149
150pub mod error;
151pub mod headers;
152pub mod public_keys;
153pub mod receipt;
154pub mod substrate_layout;
155pub mod verify;
156
157pub use error::VerifierError;
158pub use headers::Headers;
159pub use public_keys::{PublicKeyBundle, PublicKeyEntry, PublicKeysResponse};
160pub use receipt::{CompactReceipt, AlgorithmFlags, RECEIPT_SIZE, RECEIPT_VERSION};
161pub use substrate_layout::{
162 ComputationType, SUBSTRATE_SIZE, SUBSTRATE_VERSION,
163};
164pub use verify::{verify_structural, VerificationResult};
165
166/// The canonical `Verifier`. A thin wrapper around [`verify_structural`]
167/// kept as a struct so future Tier 3 work can attach a cached
168/// [`PublicKeysResponse`] without changing the public API shape.
169///
170/// # Examples
171///
172/// ```
173/// use h33_substrate_verifier::Verifier;
174///
175/// let verifier = Verifier::new();
176/// // Subsequent calls to verifier.verify(...) use the structural path.
177/// ```
178#[derive(Debug, Default, Clone)]
179pub struct Verifier {
180 _private: (),
181}
182
183impl Verifier {
184 /// Construct a new `Verifier`.
185 #[must_use]
186 pub const fn new() -> Self {
187 Self { _private: () }
188 }
189
190 /// Verify an attested response.
191 ///
192 /// Returns `Ok(VerificationResult)` with per-check verdicts whenever
193 /// the inputs are structurally parseable — even if the verdicts
194 /// report failure. Returns `Err(VerifierError)` only when the inputs
195 /// themselves are malformed (bad hex, wrong length, etc.).
196 ///
197 /// # Examples
198 ///
199 /// ```no_run
200 /// use h33_substrate_verifier::{Headers, Verifier};
201 ///
202 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
203 /// let body = b"{\"ok\":true}";
204 /// let headers = Headers::from_strs(
205 /// "f3a8b2c1deadbeef...",
206 /// "012e891fa4cafebabedeadbeef...",
207 /// "ML-DSA-65,FALCON-512,SPHINCS+-SHA2-128f",
208 /// 1_733_942_731_234,
209 /// );
210 /// let verifier = Verifier::new();
211 /// let result = verifier.verify(body, &headers)?;
212 /// if result.is_valid() {
213 /// println!("Response was legitimately signed by H33");
214 /// }
215 /// # Ok(())
216 /// # }
217 /// ```
218 pub fn verify(
219 &self,
220 body: &[u8],
221 headers: &Headers<'_>,
222 ) -> Result<VerificationResult, VerifierError> {
223 verify_structural(body, headers)
224 }
225}