Skip to main content

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}