proof-aware-crypto-tooling-.../dogfood/quorum/verify-slhdsa/expose-mono.patch
mrwulf 16040b79f5 quorum: pacta-verify-slhdsa — the SLH-DSA head-checker built from the proven source
Fifth quorum member, first post-quantum one: verifies an SLH-DSA-SHA2-128s
signature by calling slh_verify_128s, the extraction root the eleven fips205
certificates cover (apex fips205.slh_verify_128s_accepts_iff). Verify-only
like the other four: quorum members judge, they never sign.

Build discipline, because "built from the proven source" is a claim that has
to survive a hostile reader: build-verify-slhdsa.sh REFUSES to build if the
pinned checkout is dirty or at any commit other than a3ce8e8, exports the
pinned commit via git archive (never a working copy), applies
expose-mono.patch to that scratch copy, and then DIFFS the patched
verify_mono.rs against the pinned one, aborting if any existing line changed
rather than being appended. The patch is a visibility keyword plus its doc
comment (the crate denies missing_docs, so pub mod alone does not compile)
and one appended argument-assembly function whose body is the crate's own
test helper. The extraction root is provably untouched. A provenance sidecar
lands beside the binary: source commit, patch hash, main.rs hash, rustc, and
a not_covered field naming what no certificate reaches — M-prime assembly
(including the pure/prehash domain-separator byte), hex/file IO, the
compiler; signing and keygen out of scope entirely.

Demonstrated against OpenSSL 3.5.5 on a throwaway key: valid signature OK
both ways, wrong message INVALID, corrupted signature INVALID. The agreement
is itself a finding — this binary assembles M' = 0x00 || 0x00 || payload
(pure variant, empty context) and OpenSSL evidently does the same.

Convention matches the other members: template + main.rs + patch + build
script tracked; rendered Cargo.toml, lock, target/ and the .build-slhdsa
scratch tree ignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 21:52:56 +02:00

49 lines
2.5 KiB
Diff

Expose the proven verify root so a quorum binary can call it.
APPLIED TO A COPY of fips205-source at the pinned commit, never to the pinned
checkout itself. Two hunks, and the reason each is the smallest possible:
1. `mod verify_mono;` -> `pub mod verify_mono;`
A visibility keyword. Rust's `src/bin/` and `examples/` targets are
SEPARATE crates, so neither can reach a `pub(crate)` item; the module has
to be public for any binary to call into it at all.
2. A new `verify_mono_bytes` function, appended.
It only assembles arguments: split the 32-byte public key into pk_seed and
pk_root, deserialize the 7856-byte signature, call `slh_verify_128s`. The
body is copied from the crate's OWN test helper `internal_inputs`, so the
conversion is the one the crate already trusts rather than one invented
here. Exposing the argument types and their fields instead would have
meant four more visibility changes across two files.
WHAT THIS DOES NOT CHANGE. No existing line's semantics. The extraction root
`slh_verify_128s` is untouched -- same body, same callees. Module visibility and
an added sibling function do not alter the MIR of an existing function, so the
code the certificates cover compiles to what it compiled to before. What IS
true and must be said: the binary is built from `pinned commit + this patch`,
not from the pinned commit alone, and the diff below is the whole of the
difference.
--- a/src/lib.rs
+++ b/src/lib.rs
@@
-mod verify_mono; // Aeneas-compat monomorphic verify path (formal-verification campaign; additive)
+pub mod verify_mono; // Aeneas-compat monomorphic verify path (formal-verification campaign; additive)
--- a/src/verify_mono.rs
+++ b/src/verify_mono.rs
@@ (appended after slh_verify_128s)
+/// Byte-level entry to the PROVEN root, for out-of-crate callers.
+///
+/// Assembles arguments only. `mprime` is FIPS 205's M' and is built by the
+/// CALLER -- its construction is outside every certificate (TRUSTED-BASE item
+/// 10), which is exactly why it is a parameter here and not computed inside.
+pub fn verify_mono_bytes(mprime: &[u8], sig_bytes: &[u8; 7856], pk_bytes: &[u8; 32]) -> bool {
+ let mut pk_seed = [0u8; 16];
+ let mut pk_root = [0u8; 16];
+ pk_seed.copy_from_slice(&pk_bytes[0..16]);
+ pk_root.copy_from_slice(&pk_bytes[16..32]);
+ let pk = SlhPublicKey { pk_seed, pk_root };
+ let sig = SlhDsaSig::<12, 7, 9, 14, 35, 16>::deserialize(sig_bytes);
+ slh_verify_128s(mprime, &sig, &pk)
+}