//! `wai.edge.derivation` (extensions/deriving-edge.md): the committed corpus //! `edge-conformance/vectors.json` regenerates byte for byte from its fixed seeds, every //! vector in it gets the outcome it states, §9 lists exactly the reasons the reference //! reports and §4 exactly its derivations; and the reference edge, `wai_edge`, run twice over //! a publisher's clip, makes a chain of two hops that verifies back to the clip, and that is //! refused once a byte of it changes. The generator itself asserts that the corpus gives every //! reason. #![cfg(feature = "derive_edge")] use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use std::process::Command; use serde_json::{json, Value}; use wai::derive_edge::{corpus, Derivation, Reason}; use wai::jwp_delivery::GroupBuilder; use wai::jwp_hash::content_hash; fn wai_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") } fn committed() -> BTreeMap { let dir = wai_dir().join("edge-conformance"); std::fs::read_dir(&dir) .expect("the committed corpus") .flatten() .filter(|e| e.path().is_file()) .map(|e| (e.file_name().to_string_lossy().into_owned(), e.path())) .filter(|(n, _)| n != "README.md" && n != "edge_verify.py") .map(|(n, p)| (n, std::fs::read_to_string(p).expect("a text file"))) .collect() } #[test] fn the_committed_corpus_regenerates_from_its_seeds() { let files = committed(); let generated: BTreeMap = corpus::generate().into_iter().collect(); let extra: Vec<&String> = files.keys().filter(|k| !generated.contains_key(*k)).collect(); let missing: Vec<&String> = generated.keys().filter(|k| !files.contains_key(*k)).collect(); let differ: Vec<&String> = generated.iter().filter(|(k, v)| files.get(*k).is_some_and(|f| f != *v)).map(|(k, _)| k).collect(); assert!(extra.is_empty() && missing.is_empty() && differ.is_empty(), "extra {extra:?}, missing {missing:?}, differ {differ:?}: run `wai_edge_vectors gen`"); } #[test] fn every_vector_gets_its_stated_outcome() { let fails = corpus::verify(&committed()); assert!(fails.is_empty(), "{}", fails.join("\n")); } fn section(text: &str, heading: &str) -> String { let at = text.find(heading).unwrap_or_else(|| panic!("no {heading}")); let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); text[at..end].to_string() } fn first_cells(section: &str) -> Vec { section.lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect() } /// §9 lists exactly the codes `Reason::ALL` has, each once, in order; §4's table lists /// exactly the derivations, in order. #[test] fn the_extension_lists_the_reasons_and_the_derivations() { let text = std::fs::read_to_string(wai_dir().join("extensions/deriving-edge.md")).expect("the extension"); let want: Vec = Reason::ALL.iter().map(|r| r.code().to_string()).collect(); assert_eq!(first_cells(§ion(&text, "\n## 9. Reasons")), want, "§9's table and Reason::ALL differ"); let s4 = section(&text, "\n## 4. The derivations"); let table: String = s4.lines().take_while(|l| !l.starts_with("### ")).collect::>().join("\n"); let want: Vec = Derivation::ALL.iter().map(|d| d.id().to_string()).collect(); assert_eq!(first_cells(&table), want, "§4's table and Derivation::ALL differ"); } // ───────────────────────────── the reference edge ───────────────────────────── fn hex(b: &[u8]) -> String { b.iter().map(|x| format!("{x:02x}")).collect() } fn sha256(b: &[u8]) -> [u8; 32] { use sha2::Digest; sha2::Sha256::digest(b).into() } fn seed(name: &str) -> [u8; 32] { sha256(format!("wai-edge-test:{name}").as_bytes()) } fn wai1(manifest: &str, payload: &[u8]) -> Vec { let mut out = b"WAI1".to_vec(); out.extend_from_slice(&(manifest.len() as u32).to_le_bytes()); out.extend_from_slice(manifest.as_bytes()); out.extend_from_slice(&(payload.len() as u32).to_le_bytes()); out.extend_from_slice(payload); out } /// The C2PA Manifest Store the test binds to the clip: a byte string standing for one, which /// the edge names by its SHA-256 and does not parse. const STORE: &[u8] = b"C2PA Manifest Store stand-in bound to the clip"; /// A publisher's bundle: one `wai.video.int_motion` clip of two frames, in a group it signed, /// with a C2PA Manifest Store bound to it. fn source_bundle(dir: &Path) -> (PathBuf, String, [u8; 32]) { let frames: Vec> = (0..2u32).map(|f| (0..6 * 4 * 3u32).map(|i| ((i * 11 + f * 50) % 256) as u8).collect()).collect(); let clip = wai::int_video_enc::encode(&frames, 4, 6, &wai::int_video_enc::EncParams { block: 2, search: 1, mv_precision: 1, q: 1 }).unwrap(); let manifest = r#"{"wai":"1.0","media":"video","intent":"replicate","model_requirement":{"capability":"wai.video.int_motion"},"conditioning":{"kind":"int_motion"},"target":{"frames":2,"h":4,"w":6}}"#; let env = wai1(manifest, &clip.wiv1); let key = ed25519_dalek::SigningKey::from_bytes(&seed("publisher")); let mut b = GroupBuilder::default(); b.add(content_hash(&env), 0, "origin", None); let (g, objs) = b.finish(&key, "did:web:source.example#pub", 1, 0, "2026-10-20T00:00:00Z", "2026-10-20T00:00:00Z", None, false).unwrap(); let bundle = json!({ "records": [], "receipts": [g.to_json(), objs[0].to_json()], "objects": { hex(&content_hash(&env)): hex(&env) }, "claims": [], "c2pa": { hex(&content_hash(&env)): [hex(STORE)] }, }); let path = dir.join("source.json"); std::fs::write(&path, serde_json::to_string(&bundle).unwrap()).unwrap(); (path, hex(&key.verifying_key().to_bytes()), content_hash(&env)) } fn edge(args: &[&str]) -> (bool, Value) { let out = Command::new(env!("CARGO_BIN_EXE_wai_edge")).args(args).output().expect("wai_edge runs"); let text = String::from_utf8_lossy(&out.stdout); let v = serde_json::from_str(text.trim()).unwrap_or_else(|_| panic!("wai_edge {args:?}: {text} {}", String::from_utf8_lossy(&out.stderr))); (out.status.success(), v) } #[test] fn the_reference_edge_makes_a_chain_that_verifies() { let dir = std::env::temp_dir().join(format!("wai-edge-test-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); let (source, publisher, clip) = source_bundle(&dir); let s = |p: &Path| p.to_string_lossy().into_owned(); for (name, n) in [("a", "edge-a"), ("b", "edge-b")] { std::fs::write(dir.join(format!("{name}.key")), hex(&seed(n))).unwrap(); } // Hop 1: edge A rebuilds frame 1 as a pinned still. let hop1 = dir.join("hop1.json"); let (ok, made) = edge(&[ "derive", "--bundle", &s(&source), "--out", &s(&hop1), "--key", &s(&dir.join("a.key")), "--signer-id", "did:web:edge-a.example#derive", "--derivation", "wai.derive.identity", "--output", "wai.image.png", "--input", &format!("{}@1", hex(&clip)), "--track", "10", "--group", "0", "--time", "2026-10-20T12:00:00Z", "--source", &publisher, "--personal", &hex(&sha256(STORE)), "--synthesis", &format!("{}=generate", hex(&clip)), ]); assert!(ok, "{made}"); let still = made["output"].as_str().unwrap().to_owned(); // Hop 2: edge B crops it, with a metered figure. let hop2 = dir.join("hop2.json"); let (ok, made) = edge(&[ "derive", "--bundle", &s(&hop1), "--out", &s(&hop2), "--key", &s(&dir.join("b.key")), "--signer-id", "did:web:edge-b.example#derive", "--derivation", "wai.derive.crop", "--params", r#"{"x":1,"y":1,"w":4,"h":2}"#, "--output", "wai.image.png", "--input", &still, "--energy", r#"{"joules_micro":2500,"energy_provenance":"ModelBased","energy_uncertainty":null,"energy_counter_filtering":null,"energy_calibration_ref":null}"#, "--track", "20", "--group", "0", "--time", "2026-10-20T12:00:01Z", "--source", &publisher, ]); assert!(ok, "{made}"); let crop = made["output"].as_str().unwrap().to_owned(); let (ok, v) = edge(&["verify", "--bundle", &s(&hop2), "--subject", &crop, "--source", &publisher, "--require-recompute"]); assert!(ok, "{v}"); let hops = v["facts"]["hops"].as_array().unwrap(); assert_eq!(hops.len(), 2); assert!(hops.iter().all(|h| h["checked"] == "recomputed" && h["check"] == "replicate"), "{v}"); assert_eq!(v["facts"]["sources"][0]["object"], hex(&clip)); assert_eq!(v["facts"]["energy"], json!([{"class": "ModelBased", "joules_micro": 2500}])); assert_eq!(v["facts"]["unmetered"], 1); // The store bound to the clip, carried through both hops in the compartment hop 1 stated, // and the clip's label with it. assert_eq!( v["facts"]["provenance"], json!([{"input": 0, "kind": "c2pa", "digest": hex(&sha256(STORE)), "compartment": "personal"}]) ); assert!(hops.iter().all(|h| h["synthesis"] == "generate"), "{v}"); // A sink that does not take the publisher as a source finds a hop missing. let (ok, v) = edge(&["verify", "--bundle", &s(&hop2), "--subject", &crop, "--source", &hex(&[7; 32])]); assert!(!ok); assert_eq!(v["reason"], "chain_skips_hop"); // A byte of the still changed: the bundle no longer verifies. let mut b: Value = serde_json::from_str(&std::fs::read_to_string(&hop2).unwrap()).unwrap(); let env = b["objects"][&still].as_str().unwrap().to_owned(); let flipped = format!("{}{}", &env[..env.len() - 1], if env.ends_with('0') { "1" } else { "0" }); b["objects"][&still] = json!(flipped); let tampered = dir.join("tampered.json"); std::fs::write(&tampered, serde_json::to_string(&b).unwrap()).unwrap(); let (ok, v) = edge(&["verify", "--bundle", &s(&tampered), "--subject", &crop, "--source", &publisher]); assert!(!ok); assert_eq!(v["reason"], "output_hash_mismatch", "{v}"); // The edge does not derive from an input whose chain does not verify. let out = Command::new(env!("CARGO_BIN_EXE_wai_edge")) .args([ "derive", "--bundle", &s(&tampered), "--out", &s(&dir.join("never.json")), "--key", &s(&dir.join("a.key")), "--signer-id", "did:web:edge-a.example#derive", "--derivation", "wai.derive.mirror", "--params", r#"{"flip":"left-right"}"#, "--output", "wai.image.png", "--input", &still, "--track", "10", "--group", "1", "--time", "2026-10-20T12:00:02Z", "--source", &publisher, ]) .output() .unwrap(); assert!(!out.status.success()); let _ = std::fs::remove_dir_all(&dir); }