//! Consume the published `jcp` conformance pack — the files nothing read. //! //! jcp was already the best-covered standard here: `jcp-action-receipt`'s //! `tests/conformance.rs` genuinely reads `action_receipt.json` and //! `action_receipt_meta.json` from disk. But three of the pack's files had no //! reader at all: //! //! - **`legacy_jcp1.json`** — receipts sealed under the superseded `jcp/1` //! profile, published, in the pack's own words, "so a verifier can prove the //! jcp/2 migration path: these MUST still verify". //! //! The guarantee is *not* untested: `jcp-receipt`'s own //! `published_v1_receipts_still_verify` checks it. But it checks **one inlined //! hex constant**, which is a copy of this pack's *allow* receipt. The pack also //! publishes a **legacy deny** (`untrusted-control`) that no inlined constant //! covers, and the file itself is read by nothing — so the published bytes, and //! the legacy refusal path, were unverified. (`jcp-receipt`'s doc comment titled //! *The migration guarantee* is also misplaced: it sits ~80 lines above, over an //! unrelated test about environment bindings.) //! //! The mechanism is simpler than that comment suggests, too. There is no version //! dispatch in `jcr1.rs`; `payload_hash` is merely **optional**, so its absence //! leaves a legacy preimage byte-identical — which is what the neighbouring //! comment ("Absent, the field changes nothing") actually says. //! - **`dual_ceilings.json`** — §4.8's money-beside-joules ceiling, including the //! two refusals that make it real (`funds-exceeded`, `currency-mismatch`). Those //! are negative vectors, and nothing executed them. //! - **`standing.json`** — the standing-grant lineage, and (added with §4.7's //! remainder rule) the spend-bearing checkpoint whose arithmetic a third //! party can redo. //! //! Note `legacy_jcp1.json` is deliberately absent from the drift check below: it is //! a frozen historical artifact ("not minted any more"), so regenerating it would //! destroy the very thing it exists to prove. use jcp_receipt::{extract, replay_spend}; use serde_json::Value; use std::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 doc(name: &str) -> Value { serde_json::from_slice(&std::fs::read(pack_dir().join(name)).expect("read pack file")) .unwrap_or_else(|e| panic!("{name} must parse: {e}")) } /// The steward identity and key the pack publishes. fn steward_and_key() -> (String, [u8; 32]) { let m = doc("meta.json"); let steward = m["steward"].as_str().expect("meta names the steward").to_string(); let key: [u8; 32] = hex::decode(m["pubkey_hex"].as_str().expect("meta publishes a key")) .expect("hex") .try_into() .expect("a 32-byte Ed25519 key"); (steward, key) } fn envelopes(d: &Value) -> Vec> { d["receipts"] .as_array() .expect("the pack lists receipts") .iter() .map(|r| hex::decode(r["envelope_hex"].as_str().expect("receipt carries an envelope")).unwrap()) .collect() } /// The migration guarantee, over the **published** bytes rather than an inlined copy. /// /// Every receipt sealed under the superseded `jcp/1` profile must still verify under /// today's implementation, or the `payload_hash` bump silently invalidated every /// receipt in the wild. `jcp-receipt` already checks the allow receipt from an /// inlined constant; what was missing is the file itself and the legacy **deny**, /// which no inlined constant covers. Both verdicts are required below so the /// refusal cannot quietly disappear from the pack. #[test] fn receipts_sealed_under_the_superseded_profile_still_verify() { let d = doc("legacy_jcp1.json"); let (steward, key) = steward_and_key(); assert_eq!(d["profile"], "jcp/1", "this pack is the superseded profile's evidence"); let receipts = d["receipts"].as_array().expect("the pack lists legacy receipts"); assert!(!receipts.is_empty(), "a wrong path must not look like a pass"); let verdicts: Vec<&str> = receipts.iter().map(|r| r["verdict"].as_str().unwrap()).collect(); assert!(verdicts.contains(&"allow"), "the legacy pack must keep an allow: {verdicts:?}"); assert!( verdicts.contains(&"deny"), "the legacy pack must keep a deny — the refusal path is the half no inlined \ constant covers: {verdicts:?}" ); for (i, r) in receipts.iter().enumerate() { let env = hex::decode(r["envelope_hex"].as_str().unwrap()).unwrap(); let core = extract(&env, &steward, &key).unwrap_or_else(|e| { panic!("legacy receipt {i} no longer verifies ({e:?}) — the profile bump broke migration") }); assert_eq!( core.verdict, r["verdict"].as_str().unwrap(), "legacy receipt {i}: verdict differs from the published one" ); assert_eq!(core.steward, steward, "legacy receipt {i}: steward differs"); } } /// The migration check is not vacuous: tampering with a legacy envelope, or /// verifying it under another key, must both fail. #[test] fn a_tampered_legacy_receipt_does_not_verify() { let d = doc("legacy_jcp1.json"); let (steward, key) = steward_and_key(); let mut env = hex::decode(d["receipts"][0]["envelope_hex"].as_str().unwrap()).unwrap(); let stranger = ed25519_dalek::SigningKey::from_bytes(&[0x42; 32]).verifying_key().to_bytes(); assert!( extract(&env, &steward, &stranger).is_err(), "a legacy receipt verified under a key that did not seal it" ); assert!( extract(&env, "did:example:not-this-steward", &key).is_err(), "a legacy receipt verified against the wrong steward" ); let mid = env.len() / 2; env[mid] ^= 0x01; assert!(extract(&env, &steward, &key).is_err(), "a tampered legacy receipt verified"); } /// §4.8's dual ceilings, with the refusals actually executed. /// /// The pack's value is in its two denials: a spend that exceeds the money ceiling, /// and a spend in the wrong currency. Both are compared as the whole recorded /// verdict — verdict *and* reason — because "it denied" would pass even if it /// denied for the wrong reason. #[test] fn the_dual_ceiling_refusals_replay_with_their_recorded_reasons() { let d = doc("dual_ceilings.json"); let (steward, key) = steward_and_key(); let receipts = d["receipts"].as_array().expect("the pack lists receipts"); assert_eq!(receipts.len(), 3, "the scenario is one allow and two refusals"); let mut reasons = Vec::new(); for (i, r) in receipts.iter().enumerate() { let env = hex::decode(r["envelope_hex"].as_str().unwrap()).unwrap(); let core = extract(&env, &steward, &key) .unwrap_or_else(|e| panic!("dual-ceiling receipt {i} does not verify: {e:?}")); assert_eq!(core.verdict, r["verdict"].as_str().unwrap(), "receipt {i}: verdict differs"); let want_reason = r["wire"]["deny_reason"].as_str(); assert_eq!( core.deny_reason.as_deref(), want_reason, "receipt {i}: denied for a different reason than the pack records" ); if let Some(reason) = want_reason { reasons.push(reason.to_string()); } } // The two rules §4.8 exists to enforce: a money ceiling, and no invented FX. assert!(reasons.iter().any(|r| r == "funds-exceeded"), "no funds-exceeded refusal: {reasons:?}"); assert!( reasons.iter().any(|r| r == "currency-mismatch"), "no currency-mismatch refusal: {reasons:?}" ); // Replaying the spend must land on the recorded total: only the allow debits. let envs = envelopes(&d); let spent: u64 = replay_spend(envs.iter().map(|e| e.as_slice()), &steward, &key) .into_iter() .map(|(_grant, uj)| uj) .sum(); assert_eq!( spent, d["spent_after"]["uj"].as_u64().expect("the pack records the joules spent"), "replaying the receipts does not reproduce the recorded joule spend" ); assert!( spent <= d["grant"]["budget_uj"].as_u64().unwrap(), "the replayed spend exceeds the grant's own joule ceiling" ); } /// The standing-grant lineage, as far as the pack makes it checkable. /// /// One link closes: `minted_child` names `standing_grant` as its parent, and its /// budget is within the amended ceiling. One does not: `continuation_grant` names a /// `parent_hex` that is **not among the ids the pack publishes**. Its budget matches /// the amendment's `new_budget_uj`, so the parent is plausibly the post-amendment /// grant — but that grant is not in the pack, so a third party cannot close the /// link from the published files alone. Asserted as the current state, with a /// message to replace it once the parent is published. #[test] fn the_standing_lineage_links_as_far_as_the_pack_allows() { let d = doc("standing.json"); let id = |k: &str| d[k]["id_hex"].as_str().unwrap_or_default().to_string(); for k in ["standing_grant", "mint_policy", "minted_child", "amendment", "checkpoint", "continuation_grant"] { let i = id(k); assert_eq!(i.len(), 64, "{k} has no 32-byte id"); assert!(i.bytes().all(|b| b.is_ascii_hexdigit()), "{k} id is not hex"); } assert_eq!( d["minted_child"]["parent_hex"].as_str().unwrap(), id("standing_grant"), "the minted child does not descend from the standing grant" ); let amended = d["amendment"]["new_budget_uj"].as_u64().expect("the amendment sets a budget"); assert_eq!( d["continuation_grant"]["budget_uj"].as_u64().unwrap(), amended, "the continuation does not carry the amended budget" ); assert!( d["minted_child"]["budget_uj"].as_u64().unwrap() <= amended, "a minted child may not exceed the amended ceiling" ); // The spend-bearing checkpoint (§4.7's remainder rule). Two things are // checkable from the published files alone: the remainder is arithmetic a // third party can redo, and the entry's id differs from `checkpoint`'s — // which is what makes the additivity claim non-vacuous. `checkpoint` was // generated with no spend and keeps the id it had before the field existed; // this one carries the field and therefore cannot. let sp = &d["checkpoint_with_spend"]; let issued = sp["grant_budget_uj"].as_u64().expect("the entry publishes the issued budget"); let spent = sp["spent_uj"].as_u64().expect("the entry publishes the spend"); let remaining = sp["remaining_uj"].as_u64().expect("the entry publishes the remainder"); assert_eq!( issued.saturating_sub(spent), remaining, "the published remainder is not the issued budget minus the spend" ); assert!(remaining < issued, "a vector for the remainder rule must actually have spent something"); assert_ne!( id("checkpoint_with_spend"), id("checkpoint"), "a declared spend must change the checkpoint's id — it is signed, not advisory" ); let published: Vec = ["standing_grant", "mint_policy", "minted_child", "amendment", "checkpoint", "continuation_grant"] .iter() .map(|k| id(k)) .collect(); let cont_parent = d["continuation_grant"]["parent_hex"].as_str().unwrap(); assert!( !published.contains(&cont_parent.to_string()), "the continuation's parent is now published — replace this with a real lineage \ assertion linking it to that grant" ); } /// The generator still emits the files it owns. /// /// `legacy_jcp1.json` is excluded on purpose: it is frozen pre-`payload_hash` /// evidence, so regenerating it would erase what it exists to prove. #[test] fn the_generator_still_emits_the_files_it_owns() { let tmp = std::env::temp_dir().join(format!("jcp-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)); let mut checked = 0; for f in ["meta.json", "vectors.json", "dual_ceilings.json", "standing.json", "replay.json"] { let regenerated = tmp.join(f); if !regenerated.exists() { continue; // the generator does not own this one } assert_eq!( std::fs::read(®enerated).unwrap(), std::fs::read(pack_dir().join(f)).unwrap(), "{f} has drifted from what the generator emits" ); checked += 1; } assert!(checked >= 3, "expected the generator to own at least three files, saw {checked}"); assert!( !tmp.join("legacy_jcp1.json").exists(), "the generator now rewrites the frozen legacy pack — that would erase the \ pre-payload_hash evidence the migration guarantee rests on" ); let _ = std::fs::remove_dir_all(&tmp); }