//! End-to-end: a **real** byte-exact JPEG-AI decode → a reproducible-reconstruction //! binding over the decoded pixels. The [`wai::reconstruction_binding`] unit tests cover //! the binding on synthetic RGB; this closes the loop on real content — the byte-exact //! decoder produces the pixels, and the C2PA/JPEG-Trust hard binding is taken over them. //! //! It exercises the property the binding rests on: the exact decoder is deterministic, //! so the binding taken over one decode verifies a second decode, and a single flipped //! pixel breaks it. Both decodes run in this process; that the same pixels come out on //! another machine rests on the conditions and measurements in //! `extensions/determinism-tiers.md` §5–§6. This test compares with other machines //! only through `WAI_JPEGAI_EXPECT_SHA256` (below). (`wai.image.jpegai` is registered //! at entropy-consistency; the pinned reconstruction is a property of the exact //! decoder.) //! //! env: WAI_JPEGAI_CODESTREAM, WAI_JPEGAI_WEIGHTS_DIR, WAI_JPEGAI_NORM //! (the bundle from `tools/wai_jpegai_fetch_e2e_inputs.sh`; see jpegai_e2e_full). //! WAI_JPEGAI_EXPECT_SHA256 (optional) = the SHA-256 the decoded pixels must //! have. Set, it also compares this machine's decode with a value decoded on //! other machines, which the two in-process decodes cannot do. That value rests //! on a measurement, not on the registry: the codec registry classes //! `wai.image.jpegai` as `NeuralEntropyExact`, a conservative class that //! guarantees only the entropy decode (the decoded symbols and their entropy //! parameters) to be byte-identical across machines, not the reconstruction. //! The exact decoder's pinned output for the `jpegai/v1` inputs was measured //! identical on four platforms: macOS arm64, wasm32-wasip2, Linux arm64, and //! Linux x86_64 under user-mode emulation (`extensions/determinism-tiers.md` //! §6). `wai-jpegai-e2e.yml` pins that value, so a mismatch there is a departure //! from that measurement. //! //! The inputs are not in the repository; see jpegai_e2e_full for where they come //! from and how CI fetches them. Without the three variables this test skips //! loudly; with `WAI_REQUIRE_SOTA_VECTORS=1` it fails instead; a variable naming a //! missing path, or a weights directory missing a file, fails either way. #![cfg(all(feature = "neural_int", feature = "provenance"))] #[path = "support/require_vectors.rs"] mod require_vectors; use wai::jpegai_decode::{decode, DecoderWeights, NormTables, PlaneWeights}; use wai::jpegai_icci::IcciNet; use wai::jpegai_synthesis::{IntNet, Weights}; use wai::reconstruction_binding::{ReconstructionBinding, ReconstructionDescriptor}; fn jload(p: &std::path::Path) -> serde_json::Value { serde_json::from_slice(&std::fs::read(p).unwrap()).unwrap() } #[test] fn real_decode_to_reconstruction_binding() { let Some([cs, dir, nf]) = require_vectors::env_paths( "jpegai_reconstruction_binding::real_decode_to_reconstruction_binding", ["WAI_JPEGAI_CODESTREAM", "WAI_JPEGAI_WEIGHTS_DIR", "WAI_JPEGAI_NORM"], "tools/wai_jpegai_fetch_e2e_inputs.sh (pinned by tools/jpegai-e2e-v1.sha256)", ) else { return; }; let data = std::fs::read(&cs).unwrap_or_else(|e| panic!("read {cs}: {e}")); // --- load the model bundle (same shape as jpegai_e2e_full) --- let dpath = std::path::Path::new(&dir); let w = |n: &str| Weights::from_json(&jload(&dpath.join(n))).unwrap(); let net = |n: &str| IntNet::from_json(&jload(&dpath.join(n))).unwrap(); let weights = DecoderWeights { luma: PlaneWeights { hyper_scale: net("hs_y.json"), hyper_decoder: w("hyp_y.json"), synthesis: w("synth_y.json") }, chroma: PlaneWeights { hyper_scale: net("hs_uv.json"), hyper_decoder: w("hyp_uv.json"), synthesis: w("synth_uv.json") }, mcm_y: [w("mcm0_y.json"), w("mcm1_y.json"), w("mcm2_y.json"), w("mcm3_y.json")], icci: IcciNet::from_json(&jload(&dpath.join("icci.json"))).unwrap(), }; let nv = jload(std::path::Path::new(&nf)); let i64v = |k: &str| nv[k].as_array().unwrap().iter().map(|x| x.as_i64().unwrap()).collect::>(); let i32v = |k: &str| nv[k].as_array().unwrap().iter().map(|x| x.as_i64().unwrap() as i32).collect::>(); let i3 = |k: &str| { let a = nv[k].as_array().unwrap(); [a[0].as_i64().unwrap(), a[1].as_i64().unwrap(), a[2].as_i64().unwrap()] }; let f3 = |k: &str| { let a = nv[k].as_array().unwrap(); [a[0].as_f64().unwrap() as f32, a[1].as_f64().unwrap() as f32, a[2].as_f64().unwrap() as f32] }; let norm = NormTables { pmf: nv["pmf"].as_array().unwrap().iter() .map(|r| r.as_array().unwrap().iter().map(|x| x.as_i64().unwrap()).collect()).collect(), bounds: i64v("bounds"), per_channel: nv["per_channel"].as_u64().unwrap() as usize, cdfs_y: i64v("cdfs_y"), cdfs_uv: i64v("cdfs_uv"), gain_y: i64v("gain_y"), gain_uv: i64v("gain_uv"), lsbs_t0: i32v("lsbs_t0"), lsbs_t1: i32v("lsbs_t1"), lef_thr: i3("lef_thr0"), lef_mag: f3("lef_mag0"), }; // --- decode the real codestream → RGB, then bind over the decoded pixels --- let img = decode(&data, &weights, &norm).expect("supported geometry"); assert_eq!((img.h, img.w), (256, 256)); assert_eq!(img.data.len(), 256 * 256 * 3); // The descriptor names the synthesis branch the decode actually ran. The exact // decoder implements only the high-operation-point synthesis // (`synthesis_transform_id` 2) and refuses codestreams that select another. let segs = wai::jpegai_bitstream::parse_container(&data); let pih = wai::jpegai_bitstream::find_segment(&segs, wai::jpegai_bitstream::Marker::Pih).unwrap(); let ph = wai::jpegai_bitstream::parse_picture_header(&data, pih.start_bit()); assert_eq!(ph.synthesis_transform_ids[0], 2, "test256 selects the high-operation-point synthesis"); let desc = ReconstructionDescriptor { decoder_id: ph.synthesis_transform_ids[0] as u8, profile: "tools_0".into(), level: "L1".into(), width: img.w as u32, height: img.h as u32, format: "RGB8".into(), color_space: "bt709".into(), }; let binding = ReconstructionBinding::bind(&img.data, desc.clone()); assert_eq!(binding.alg, "sha256"); assert_eq!(binding.hash_hex().len(), 64); // The binding verifies its own decoded pixels. assert!(binding.verify(&img.data), "the binding must verify the pixels it was taken over"); // A second decode in this process is byte-identical, and the binding — taken over // the first — verifies the second. let img2 = decode(&data, &weights, &norm).expect("supported geometry"); assert_eq!(img2.data, img.data, "the exact decoder is deterministic within a process"); assert!(binding.verify(&img2.data), "the binding verifies any faithful reproduction"); // A single flipped pixel breaks the hard binding. let mut tampered = img.data.clone(); tampered[12_345] ^= 1; assert!(!binding.verify(&tampered), "a single flipped byte must fail the binding"); eprintln!("reconstruction binding over the real JPEG-AI decode:"); eprintln!(" assertion = {}", ReconstructionBinding::C2PA_LABEL); eprintln!(" sha256 = {}", binding.hash_hex()); eprintln!(" json = {}", binding.to_json()); // Against other machines: the pixels this machine decoded must hash to the value // measured from other machines' decodes of the same inputs (see the header). if let Ok(want) = std::env::var("WAI_JPEGAI_EXPECT_SHA256") { assert_eq!( binding.hash_hex(), want.trim().to_ascii_lowercase(), "the decoded pixels differ from the pinned decode of these inputs" ); eprintln!(" matches the pinned WAI_JPEGAI_EXPECT_SHA256"); } }