//! Consume the published `proof.receipt/1` conformance pack. //! //! `gen-vectors` writes `proof/conformance/v1/` and nothing read it back — the //! crate's `soundness.rs` does no file IO, so the published receipts were never //! checked against the implementation that claims to produce them. //! //! Three things are checked here, deliberately separated: //! //! 1. **Drift** — the generator still emits exactly the published files, run into a //! temporary directory so the pack itself is never touched. //! 2. **Signatures, independently** — each envelope is verified from the pack and //! RFC 9052 alone, with a hand-built CBOR encoder rather than the crate's own //! COSE code, which is what a third-party verifier would have to do. //! 3. **The cascade's accounting** — the economics the receipts record, recomputed //! across tiers rather than read back one at a time. use ed25519_dalek::{Signature, Verifier, VerifyingKey}; use serde_json::Value; use std::path::{Path, PathBuf}; fn pack_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../../../conformance/v1") .canonicalize() .expect("the conformance pack must exist next to the spec") } fn receipts_in(dir: &Path) -> Vec { let raw = std::fs::read_to_string(dir.join("receipts.jsonl")).expect("read receipts.jsonl"); raw.lines() .filter(|l| !l.trim().is_empty()) .map(|l| serde_json::from_str(l).expect("each line must parse")) .collect() } fn meta() -> Value { serde_json::from_slice(&std::fs::read(pack_dir().join("meta.json")).expect("read meta.json")) .expect("meta.json must parse") } // --- minimal CBOR (RFC 8949), independent of the crate's own COSE code ---------- fn head(major: u8, len: u64) -> Vec { let m = major << 5; match len { 0..=23 => vec![m | len as u8], 24..=0xff => vec![m | 24, len as u8], 0x100..=0xffff => { let mut v = vec![m | 25]; v.extend_from_slice(&(len as u16).to_be_bytes()); v } _ => { let mut v = vec![m | 26]; v.extend_from_slice(&(len as u32).to_be_bytes()); v } } } fn bstr(b: &[u8]) -> Vec { let mut v = head(2, b.len() as u64); v.extend_from_slice(b); v } fn tstr(s: &str) -> Vec { let mut v = head(3, s.len() as u64); v.extend_from_slice(s.as_bytes()); v } /// `{1: -8, 4: }` — EdDSA with the steward as `kid`. fn protected_header(kid: &str) -> Vec { let mut v = head(5, 2); v.push(0x01); v.push(0x27); // -8 v.push(0x04); v.extend(bstr(kid.as_bytes())); v } /// RFC 9052 §4.4. fn sig_structure(protected: &[u8], payload: &[u8]) -> Vec { let mut v = head(4, 4); v.extend(tstr("Signature1")); v.extend(bstr(protected)); v.extend(bstr(&[])); v.extend(bstr(payload)); v } /// The generator still emits exactly the published pack. /// /// Run into a temp directory — `gen-vectors` takes an output path, so the /// comparison never risks the published files. #[test] fn the_generator_still_emits_the_published_pack() { let tmp = std::env::temp_dir().join(format!("proof-pack-{}", std::process::id())); let _ = std::fs::remove_dir_all(&tmp); std::fs::create_dir_all(&tmp).expect("create temp dir"); let out = std::process::Command::new(env!("CARGO_BIN_EXE_gen-vectors")) .arg(&tmp) .output() .expect("gen-vectors must run"); assert!(out.status.success(), "gen-vectors failed: {}", String::from_utf8_lossy(&out.stderr)); for f in ["meta.json", "receipts.jsonl"] { assert_eq!( std::fs::read(tmp.join(f)).unwrap(), std::fs::read(pack_dir().join(f)).unwrap(), "{f} has drifted from what the implementation emits" ); } let _ = std::fs::remove_dir_all(&tmp); } /// Every published envelope verifies under the published key, checked from the pack /// and RFC 9052 alone. #[test] fn every_published_envelope_verifies_under_the_published_key() { let m = meta(); let steward = m["steward"].as_str().expect("meta names the steward"); let pubkey: [u8; 32] = hex::decode(m["pubkey_hex"].as_str().unwrap()) .unwrap() .try_into() .expect("a 32-byte Ed25519 key"); let vk = VerifyingKey::from_bytes(&pubkey).unwrap(); let protected = protected_header(steward); let receipts = receipts_in(&pack_dir()); assert_eq!(receipts.len(), 4, "the pack records one receipt per cascade tier"); for r in &receipts { let tier = r["closed_by"].as_str().unwrap(); let core = hex::decode(r["signed_core_hex"].as_str().unwrap()).unwrap(); let envelope = hex::decode(r["envelope_hex"].as_str().unwrap()).unwrap(); let sig = Signature::from_slice(&envelope[envelope.len() - 64..]).unwrap(); vk.verify(&sig_structure(&protected, &core), &sig) .unwrap_or_else(|e| panic!("{tier}: published envelope does not verify: {e}")); // the envelope really does carry the published core, not some other payload assert!( envelope.windows(core.len()).any(|w| w == core), "{tier}: the envelope does not contain its published signed core" ); assert_eq!(r["wire"]["steward"], m["steward"], "{tier}: wire steward disagrees with meta"); assert_eq!(r["wire"]["closed_by"], r["closed_by"], "{tier}: wire tier disagrees"); let goal_id = r["wire"]["goal_id"].as_str().expect("wire carries a goal id"); assert_eq!(goal_id.len(), 64, "{tier}: goal id is not a 32-byte hash"); assert!(goal_id.bytes().all(|b| b.is_ascii_hexdigit()), "{tier}: goal id is not hex"); } // Distinct goals must address distinctly, or the receipts would be // interchangeable and the pack would prove nothing about which goal closed. let goals: Vec<&str> = receipts.iter().map(|r| r["goal"].as_str().unwrap()).collect(); let ids: Vec<&str> = receipts.iter().map(|r| r["wire"]["goal_id"].as_str().unwrap()).collect(); for i in 0..receipts.len() { for j in i + 1..receipts.len() { assert_eq!( goals[i] == goals[j], ids[i] == ids[j], "goal text and goal id disagree about whether {i} and {j} are the same goal" ); } } } /// The verification above is not vacuous. #[test] fn a_tampered_core_or_a_wrong_key_does_not_verify() { let m = meta(); let steward = m["steward"].as_str().unwrap(); let pubkey: [u8; 32] = hex::decode(m["pubkey_hex"].as_str().unwrap()).unwrap().try_into().unwrap(); let protected = protected_header(steward); let r = &receipts_in(&pack_dir())[0]; let envelope = hex::decode(r["envelope_hex"].as_str().unwrap()).unwrap(); let sig = Signature::from_slice(&envelope[envelope.len() - 64..]).unwrap(); let mut core = hex::decode(r["signed_core_hex"].as_str().unwrap()).unwrap(); let mid = core.len() / 2; core[mid] ^= 0x01; assert!( VerifyingKey::from_bytes(&pubkey) .unwrap() .verify(&sig_structure(&protected, &core), &sig) .is_err(), "a tampered signed core verified" ); let core = hex::decode(r["signed_core_hex"].as_str().unwrap()).unwrap(); let stranger = ed25519_dalek::SigningKey::from_bytes(&[0x42; 32]).verifying_key(); assert!( stranger.verify(&sig_structure(&protected, &core), &sig).is_err(), "the envelope verified under a key that did not seal it" ); } /// The cascade's accounting, recomputed across the whole pack. /// /// Two properties the receipts assert together and neither states alone: the waste /// recorded at a tier is exactly what the tiers before it consumed, and escalating /// all the way costs **more** than the baseline it is measured against. The cascade /// saves energy by closing early, not by existing. #[test] fn the_cascade_accounting_holds_across_the_tiers() { let receipts = receipts_in(&pack_dir()); let tiers: Vec<&str> = receipts.iter().map(|r| r["closed_by"].as_str().unwrap()).collect(); assert_eq!(tiers, ["T0", "T1", "T2", "T3"], "the pack must cover every cascade tier"); let total = |r: &Value| r["wire"]["total_uj"].as_u64().unwrap(); let wasted = |r: &Value| r["wire"]["wasted_uj"].as_u64().unwrap(); let baseline = receipts[0]["wire"]["baseline_uj"].as_u64().unwrap(); assert_eq!(wasted(&receipts[0]), 0, "closing at T0 wastes nothing"); for i in 1..receipts.len() { assert_eq!( wasted(&receipts[i]), total(&receipts[i - 1]), "{}: waste must be exactly what the earlier tiers consumed", tiers[i] ); assert!(total(&receipts[i]) > total(&receipts[i - 1]), "{}: cost must rise", tiers[i]); } for r in &receipts[..3] { assert!(total(r) < baseline, "closing early must beat the baseline"); } assert!( total(&receipts[3]) > baseline, "escalating to T3 costs more than the baseline — if this ever passes, the \ honest limit of the cascade has been quietly re-described" ); // Every verdict here was closed by a stand-in, and the pack says so rather than // hiding it. That marking is the property worth holding onto. for (r, tier) in receipts.iter().zip(&tiers) { assert_eq!(r["wire"]["verdict"], "proved", "{tier}"); assert_eq!(r["wire"]["closed_by_stub"], 1, "{tier}: a stub close must be recorded as one"); } }