//! `wai.video.int_motion` receipt — the metered leg of the integer video class. //! //! The point of these tests is the split the profile rests on: everything except //! the energy is independently checkable from the codestream, and the energy is //! attested by the signature alone. Each half is tested for what it actually //! promises, and the tamper cases confirm the checkable half cannot be bent. #![cfg(feature = "video_receipt")] // Most tests here seal over the tool's JSON form through the deprecated entry points: // they are the legacy receipts, whose pinned bytes must keep verifying. The tests of // the WIV1 payload, the identity a receipt has now, are at the end. #![allow(deprecated)] use std::path::PathBuf; use ed25519_dalek::SigningKey; use wai::energy_class::{CounterFiltering, Uncertainty}; use wai::video_receipt::{work_pixel_ops, Provenance, VideoReceipt}; fn dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/int_video") } fn raw(name: &str) -> Vec { std::fs::read(dir().join(name)).expect("read vector") } fn key(seed: u8) -> SigningKey { SigningKey::from_bytes(&[seed; 32]) } /// An on-chip counter reading as the native meter emits it: a declared 3 % /// standard uncertainty over a 2.8 s bracket, filtering state not determinable /// by an unprivileged reader. fn chip() -> Provenance { Provenance::on_chip_counter(Uncertainty::new(30_000, 2_800_000).unwrap(), CounterFiltering::Unknown) .unwrap() } fn seal_as(name: &str, uj: u64, prov: Option) -> (VideoReceipt, Vec, serde_json::Value) { let bytes = raw(name); let bs: serde_json::Value = serde_json::from_slice(&bytes).expect("parse"); let r = VideoReceipt::seal_decode(&key(7), "did:example:meter", &bytes, &bs, uj, prov, None) .expect("seal"); (r, bytes, bs) } fn seal(name: &str, uj: u64) -> (VideoReceipt, Vec, serde_json::Value) { seal_as(name, uj, (uj > 0).then(chip)) } #[test] fn seals_and_verifies_against_the_committed_clip() { let (r, bytes, bs) = seal("int_video_bitstream.json", 4_200); assert!(r.verify(), "signature + internal consistency"); assert!(r.verify_reconstruction(&bytes, &bs), "re-decode must reproduce every frame"); // The work figure follows from the geometry and is recomputable, so it // cannot be inflated to flatter an energy number. assert_eq!(r.h, 48); assert_eq!(r.w, 48); assert_eq!(r.frames.len(), 5); assert_eq!(r.work_pixel_ops, work_pixel_ops(5, 48, 48)); assert_eq!(r.work_pixel_ops, 5 * 48 * 48 * 3); let (rate, prov) = r.uj_per_kilopixel_op().expect("metered"); assert!((rate - 4_200.0 * 1000.0 / 34_560.0).abs() < 1e-9); assert_eq!(prov, chip()); } /// An unmetered receipt reports **no** rate. A `0` would read as "this decode /// was free", which is a stronger and false claim than "nobody measured". #[test] fn unmetered_reports_no_rate_rather_than_zero() { let (r, _, _) = seal("int_video_bitstream.json", 0); assert!(r.verify()); assert_eq!(r.joules_micro, 0); assert!(r.uj_per_kilopixel_op().is_none()); assert_eq!(r.energy_provenance, None); assert!(r.to_json().contains("\"uj_per_kilopixel_op\":null")); assert!(r.to_json().contains("\"energy_provenance\":null")); } /// Real footage, the half-pel path — the clip whose decode is most at risk of /// differing between architectures, and therefore the one whose receipt is /// worth the most. #[test] fn real_footage_receipt_round_trips() { let (r, bytes, bs) = seal("video_real_bitstream.json", 91_000); assert!(r.verify()); assert!(r.verify_reconstruction(&bytes, &bs)); assert_eq!((r.h, r.w, r.frames.len()), (96, 96, 8)); assert_eq!(r.work_pixel_ops, 8 * 96 * 96 * 3); } /// A receipt sealed over one clip must not verify against another. #[test] fn a_receipt_does_not_vouch_for_a_different_clip() { let (r, _, _) = seal("int_video_bitstream.json", 10); let other = raw("video_real_bitstream.json"); let other_bs: serde_json::Value = serde_json::from_slice(&other).unwrap(); assert!(!r.verify_reconstruction(&other, &other_bs)); } /// Tampering with any checkable field must break the receipt. #[test] fn every_checkable_field_is_bound() { let (base, bytes, bs) = seal("int_video_bitstream.json", 4_200); // A flipped frame hash breaks the Merkle root. let mut t = base.clone(); t.frames[2].hash[0] ^= 1; assert!(!t.verify(), "flipped frame hash"); // Reordering frames breaks the index binding — inter-frame decode is // order-dependent, so a permuted clip is a different clip. let mut t = base.clone(); t.frames.swap(1, 3); assert!(!t.verify(), "permuted frames"); // Inflating the work figure (the denominator of any efficiency claim) // breaks recomputation. let mut t = base.clone(); t.work_pixel_ops *= 10; assert!(!t.verify(), "inflated work"); // Restating the geometry breaks the clip commitment. let mut t = base.clone(); t.w += 1; assert!(!t.verify(), "restated geometry"); // A different identity no longer describes this codestream. let mut t = base.clone(); t.codestream_hash[0] ^= 1; assert!(!t.verify_reconstruction(&bytes, &bs), "wrong codestream hash"); // And the energy is inside the signature even though it is not // independently checkable — an intermediary cannot restate it. let mut t = base.clone(); t.joules_micro = 1; assert!(!t.verify(), "restated energy"); } /// Receipts chain like every other class, and the chain link is covered by the /// signature. #[test] fn receipts_chain_by_parent_hash() { let (first, _, _) = seal("int_video_bitstream.json", 4_200); let bytes = raw("video_real_bitstream.json"); let bs: serde_json::Value = serde_json::from_slice(&bytes).unwrap(); let second = VideoReceipt::seal_decode( &key(7), "did:example:meter", &bytes, &bs, 91_000, Some(chip()), Some(first.receipt_hash()), ) .expect("seal"); assert!(second.verify()); assert_eq!(second.parent_receipt_hash, Some(first.receipt_hash())); let mut t = second.clone(); t.parent_receipt_hash = None; assert!(!t.verify(), "the chain link is signed"); } /// A number and its acquisition class stand or fall together — the doctrine /// this class exists to enforce, checked rather than merely documented. #[test] fn an_energy_figure_and_its_class_are_inseparable() { let (metered, _, _) = seal("int_video_bitstream.json", 4_200); let (unmetered, _, _) = seal("int_video_bitstream.json", 0); assert!(metered.verify() && unmetered.verify()); // Stripping the class off a real figure leaves a number nobody can compare. let mut t = metered.clone(); t.energy_provenance = None; assert!(!t.verify(), "unlabelled non-zero figure"); // Labelling a measurement that never happened is the more dangerous half: // it claims a sensor was in the loop when none was. let mut t = unmetered.clone(); t.energy_provenance = Some(chip()); assert!(!t.verify(), "labelled zero"); // The class is inside the signature, so an intermediary cannot promote a // modelled figure to a measured one. let mut t = metered.clone(); t.energy_provenance = Some(Provenance::ModelBased); assert!(!t.verify(), "restated class"); } fn hex(b: &[u8]) -> String { b.iter().map(|x| format!("{x:02x}")).collect() } /// A receipt sealed under the three-rung ladder keeps its signed bytes and its /// JSON rendering. The digests were captured from the legacy encoding (tag /// byte, no trailing evidence) and pin it: a verifier holding such a receipt /// must reach the same verdict after the acquisition-class split as before. #[test] #[allow(deprecated)] // sealing a legacy HwShunt receipt is the point fn legacy_labels_keep_their_signed_bytes() { let (hw, _, _) = seal_as("int_video_bitstream.json", 4_200, Some(Provenance::HwShunt)); let (none, _, _) = seal("int_video_bitstream.json", 0); assert!(hw.verify() && none.verify()); assert_eq!( [ hex(&hw.receipt_hash()), hex(blake3::hash(hw.to_json().as_bytes()).as_bytes()), hex(&none.receipt_hash()), ], [ "0dc851f603666496b1358ab427d8a84163c1841eade8970d537bcdc0567b1745", "1cdcf1ebd778c39aca8614c4aa4d470dc9c5ad7ff680eaf3e1ee7302c39db15b", "03a88242e999887d0ad9e515d9980755e6aad00989bed8e0ccba35b4bb2dd710", ] ); } /// The refined hardware class carries its declared uncertainty into the /// signature and into the rendered receipt; the same class with no uncertainty /// does not verify. #[test] fn a_refined_class_carries_its_uncertainty_and_cannot_omit_it() { let (r, _, _) = seal("int_video_bitstream.json", 4_200); assert!(r.verify()); let j = r.to_json(); assert!(j.contains("\"energy_provenance\":\"OnChipCounter\""), "{j}"); assert!(j.contains("\"energy_uncertainty\":{\"relative_ppm\":30000,\"window_us\":2800000}"), "{j}"); assert!(j.contains("\"energy_counter_filtering\":\"Unknown\""), "{j}"); serde_json::from_str::(&j).expect("the receipt renders as valid JSON"); // Sealed without an uncertainty (by hand, bypassing the checked // constructor): the signature is valid, the receipt is not. let undeclared = Provenance::OnChipCounter { uncertainty: Uncertainty { relative_ppm: 0, window_us: 2_800_000 }, filtering: CounterFiltering::Unknown, }; let (bad, _, _) = seal_as("int_video_bitstream.json", 4_200, Some(undeclared)); assert!(!bad.verify(), "a hardware class with no declared uncertainty must not verify"); // And a declared uncertainty cannot be tightened after sealing. let mut t = r.clone(); t.energy_provenance = Some( Provenance::on_chip_counter(Uncertainty::new(1_000, 2_800_000).unwrap(), CounterFiltering::Unknown) .unwrap(), ); assert!(!t.verify(), "restated uncertainty"); } /// The refined classes' signed bytes, pinned as they were when the hardware /// class was split. `legacy_labels_keep_their_signed_bytes` holds the legacy /// encodings; this holds the refined ones, so a byte that leaks into the /// evidence encoding (`write_signed`) fails here. The digests were captured /// from the code as the refined classes were introduced and must never be /// edited to make a test pass. #[test] fn refined_labels_keep_their_signed_bytes() { let (chip_r, _, _) = seal("int_video_bitstream.json", 4_200); let cal = Provenance::calibrated_instrument( Uncertainty::new(5_000, 60_000_000).unwrap(), "https://calibration.example.org/certificates/0001", ) .unwrap(); let (cal_r, _, _) = seal_as("int_video_bitstream.json", 4_200, Some(cal)); assert!(chip_r.verify() && cal_r.verify()); assert_eq!( [ hex(&chip_r.receipt_hash()), hex(blake3::hash(chip_r.to_json().as_bytes()).as_bytes()), hex(&cal_r.receipt_hash()), hex(blake3::hash(cal_r.to_json().as_bytes()).as_bytes()), ], [ "23b6f548184a543fa324066427648152de6bb367f64aaddb9ff608d8af41543d", "7ece767eb1428faff0d4dd035d1d85e285461bc4610c5af30223a5c0fc20dcc3", "c6aad993760ff0b23c57a49e64a4a24c568db2af1c3f18b3c11e659c4ffb4151", "2d5529def21fdbf30524a53c1638f06c6311bd3d0d50e125ba4d3a4b2fcf69bb", ] ); } /// A receipt sealed over the `WIV1` payload (`extensions/integer-payloads.md` §3): /// its identity is BLAKE3 over the payload, it re-derives from the payload alone, /// and every reconstruction field — frames, clip commitment, geometry, work — is the /// one the JSON form's receipt carries, since both decode to the same frames. #[test] fn a_wiv1_receipt_identifies_the_payload_and_reconstructs_the_same_clip() { for (bin, json) in [("int_video_bitstream.wiv1", "int_video_bitstream.json"), ("video_real_bitstream.wiv1", "video_real_bitstream.json")] { let payload = raw(bin); let r = VideoReceipt::seal_decode_wiv1(&key(7), "did:example:meter", &payload, 4_200, Some(chip()), None).expect("seal"); assert!(r.verify(), "{bin}"); assert!(r.verify_reconstruction_wiv1(&payload), "{bin}: re-decode must reproduce every frame"); assert_eq!(r.codestream_hash, *blake3::hash(&payload).as_bytes(), "{bin}: the identity is the payload's"); let (legacy, json_bytes, _) = seal(json, 4_200); assert_eq!((&r.frames, r.clip_hash, r.h, r.w, r.work_pixel_ops), (&legacy.frames, legacy.clip_hash, legacy.h, legacy.w, legacy.work_pixel_ops), "{bin}: the same reconstruction as the JSON form's receipt"); assert_ne!(r.codestream_hash, legacy.codestream_hash); // Neither receipt vouches for the other's codestream. assert!(!r.verify_reconstruction_wiv1(&json_bytes), "{bin}: a JSON form is not a WIV1 payload"); assert!(!legacy.verify_reconstruction_wiv1(&payload), "{bin}: the legacy receipt names the JSON bytes"); let mut other = payload.clone(); let at = other.len() - 5; other[at] ^= 1; assert!(!r.verify_reconstruction_wiv1(&other), "{bin}: one changed byte"); } } /// The signed bytes of receipts sealed over the `WIV1` payload, captured from the /// code as this identity was introduced: a metered one with the on-chip class, an /// unmetered one, and one chained to the first. Never edited to make a test pass. #[test] fn wiv1_receipts_keep_their_signed_bytes() { let payload = raw("int_video_bitstream.wiv1"); let metered = VideoReceipt::seal_decode_wiv1(&key(7), "did:example:meter", &payload, 4_200, Some(chip()), None).unwrap(); let unmetered = VideoReceipt::seal_decode_wiv1(&key(7), "did:example:meter", &payload, 0, None, None).unwrap(); let chained = VideoReceipt::seal_decode_wiv1(&key(7), "did:example:meter", &payload, 0, None, Some(metered.receipt_hash())).unwrap(); assert!(metered.verify() && unmetered.verify() && chained.verify()); let got = [ hex(&metered.codestream_hash), hex(&metered.receipt_hash()), hex(blake3::hash(metered.to_json().as_bytes()).as_bytes()), hex(&unmetered.receipt_hash()), hex(&chained.receipt_hash()), ]; assert_eq!( got, [ "277865b574029f700f4fac023de3ab06ae598ee6f8ed726444e91442a6bd59e9", "52c0ca1c9b383b9ad83226d423b36ecafbef92bf431432c7426a484c0406a742", "97a7cb3b2598f609d205159e2ef1b2d7fb2d35e429ee161a6a8fbbd8ffdcae52", "f19f86c9106981096bdbb5ea06926e3543112c26a5f737732938feb51c2b6119", "58ca49ab785116aec8d2f3b990722e962967cbca6b4d6fe41087343872bd1628", ] ); } /// `video_receipt::decode_wiv1` reports a refusal by its code /// (`extensions/integer-payloads.md` §8), the decode's as well as the parse's: the /// refusal fixtures of a run-out stream and an unread word, and a JSON form. #[test] fn decode_wiv1_reports_refusal_codes() { let fixture = |name: &str| std::fs::read(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../int-payload-conformance/refusals").join(name)).unwrap(); let code = |p: &[u8]| wai::video_receipt::decode_wiv1(p).map(|_| ()).unwrap_err(); assert_eq!(code(&fixture("wiv1_exhausted.bin")), "stream_exhausted"); assert_eq!(code(&fixture("wiv1_unread_word.bin")), "stream_noncanonical"); assert_eq!(code(&raw("int_video_bitstream.json")), "bad_magic"); } /// `VideoReceipt::from_json` is the inverse of `to_json`: the JSON of a receipt of every /// class (unmetered, the on-chip counter, a calibrated instrument, a model, the legacy /// label), chained or not, reads back as the same receipt; and a holder of that JSON and /// the payload, and nothing else, verifies it and re-derives it. #[test] #[allow(deprecated)] // the legacy HwShunt label is read back too fn a_receipt_reads_back_from_its_json() { use wai::video_receipt::ReadError; let payload = raw("int_video_bitstream.wiv1"); let cal = Provenance::calibrated_instrument(Uncertainty::new(5_000, 2_800_000).unwrap(), "urn:example:cert \"7\"").unwrap(); let seal = |uj, p, parent| VideoReceipt::seal_decode_wiv1(&key(7), "did:example:meter", &payload, uj, p, parent).unwrap(); let first = seal(4_200, Some(chip()), None); for r in [ first.clone(), seal(0, None, None), seal(4_200, Some(cal), None), seal(4_200, Some(Provenance::ModelBased), Some(first.receipt_hash())), seal(4_200, Some(Provenance::HwShunt), None), VideoReceipt::seal_decode_wiv1(&key(9), "did:example:a \"quoted\" id\\", &payload, 0, None, None).unwrap(), ] { let j = r.to_json(); let back = VideoReceipt::from_json(&j).unwrap_or_else(|e| panic!("{e}: {j}")); assert_eq!(back, r); assert!(back.verify() && back.verify_reconstruction_wiv1(&payload)); assert_eq!(back.to_json(), j, "to_json(from_json(j)) is j"); // Whitespace between the members is the reader's business, not the receipt's. let spaced = serde_json::to_string_pretty(&serde_json::from_str::(&j).unwrap()).unwrap(); assert!(spaced.contains("\"uj_per_kilopixel_op\": null") || r.joules_micro > 0); if r.joules_micro == 0 { assert_eq!(VideoReceipt::from_json(&spaced), Ok(r.clone()), "a re-indented unmetered receipt"); } } let j = first.to_json(); let refused = |t: String| VideoReceipt::from_json(&t).unwrap_err(); // Not what to_json writes. assert_eq!(refused(j.replacen("\"h\":48", "\"h\":48,\"h\":49", 1)), ReadError::Malformed("not JSON read strictly"), "a member named twice"); assert_eq!(refused(j.replacen("{\"profile\"", "{\"extra\":1,\"profile\"", 1)), ReadError::Malformed("members")); assert_eq!(refused(j.replacen(",\"energy_counter_filtering\":\"Unknown\"", "", 1)), ReadError::Malformed("members"), "the class's evidence dropped"); assert_eq!(refused(j.replacen("wai.video.int_motion/receipt", "wai.video.int_motion/other", 1)), ReadError::Malformed("profile")); assert_eq!(refused(j.replacen("\"h\":48", "\"h\":48.0", 1)), ReadError::Malformed("h"), "a plain integer is read from its spelling"); assert_eq!(refused(j.replacen("\"w\":48", "\"w\":4294967296", 1)), ReadError::Malformed("w")); let upper = format!("\"clip_hash\":\"{}\"", hex(&first.clip_hash).to_uppercase()); assert_eq!(refused(j.replacen(&format!("\"clip_hash\":\"{}\"", hex(&first.clip_hash)), &upper, 1)), ReadError::Malformed("clip_hash")); assert!(matches!(refused(j.replacen("\"OnChipCounter\"", "\"OnChipCountr\"", 1)), ReadError::Class(_))); // The derived members are held to the receipt. let rate = format!("{:.6}", first.uj_per_kilopixel_op().unwrap().0); assert_eq!(refused(j.replacen(&format!("\"uj_per_kilopixel_op\":{rate}"), "\"uj_per_kilopixel_op\":1.000000", 1)), ReadError::RateMismatch); assert_eq!(refused(j.replacen(&format!("\"uj_per_kilopixel_op\":{rate}"), &format!("\"uj_per_kilopixel_op\":{rate}0"), 1)), ReadError::RateMismatch, "spelled as to_json spells it"); let rh = hex(&first.receipt_hash()); let mut other = rh.clone().into_bytes(); other[0] = if other[0] == b'0' { b'1' } else { b'0' }; assert_eq!(refused(j.replacen(&rh, std::str::from_utf8(&other).unwrap(), 1)), ReadError::ReceiptHashMismatch); // A field restated reads, and then fails the receipt's own checks. let restated = VideoReceipt::from_json(&j.replacen("\"joules_micro\":4200", "\"joules_micro\":4201", 1)); assert!(matches!(restated, Err(ReadError::RateMismatch) | Err(ReadError::ReceiptHashMismatch))); } /// Three frames of a gradient that moves a pixel a frame, 16 × 16. fn moving_source() -> Vec> { (0..3u32) .map(|f| (0..16u32 * 16 * 3).map(|i| { let (p, c) = (i / 3, i % 3); ((p % 16 + f) * 9 + (p / 16) * 5 + c * 40) as u8 }).collect()) .collect() } fn enc_params() -> wai::int_video_enc::EncParams { wai::int_video_enc::EncParams { block: 8, search: 2, mv_precision: 1, q: 4 } } /// The encode receipt (integer-payloads §15.3): sealed over the payload the reference /// encoder writes, re-derived from that payload and the source alone (the source's hashes, /// the header's parameters, the error recomputed), and refused when any of them is not the /// receipt's; its JSON form reads back; its class is never the legacy label. #[test] #[allow(deprecated)] // the legacy label is refused here fn an_encode_receipt_is_re_derived_from_its_payload_and_source() { use wai::video_receipt::{ReadError, VideoEncodeReceipt}; let src = moving_source(); let model = Some(Provenance::ModelBased); let (r, payload) = VideoEncodeReceipt::seal_encode(&key(7), "did:example:encoder", &src, 16, 16, enc_params(), 1_200, model.clone(), None).unwrap(); assert!(r.verify() && r.verify_encode(&payload, &src)); assert_eq!(r.work_pixel_ops, 3 * 16 * 16 * 3); let enc = wai::int_video_enc::encode(&src, 16, 16, &enc_params()).unwrap(); assert_eq!((r.sse, payload.clone()), (enc.sse, enc.wiv1), "the error and the payload are the encoder's"); // The payload decodes to frames whose decode receipt is the payload's. let dec = VideoReceipt::seal_decode_wiv1(&key(7), "did:example:meter", &payload, 0, None, None).unwrap(); assert_eq!(dec.codestream_hash, r.codestream_hash); // Each part it rests on, changed, is refused. let mut other = src.clone(); other[1][7] ^= 1; assert!(!r.verify_encode(&payload, &other), "another source"); let mut p2 = payload.clone(); let at = p2.len() - 3; p2[at] ^= 1; assert!(!r.verify_encode(&p2, &src), "another payload"); let restated = |f: &dyn Fn(&mut VideoEncodeReceipt)| { let mut t = r.clone(); f(&mut t); t }; // Each control is signed again over its defect, so the defect alone is what is refused. let signed = |f: &dyn Fn(&mut VideoEncodeReceipt)| { let mut t = restated(f); t.resign(&key(7)); assert!(t.verify(), "a consistent control"); t }; assert!(!signed(&|t| t.sse += 1).verify_encode(&payload, &src), "an error the decode does not have"); assert!(!signed(&|t| t.params.q = 5).verify_encode(&payload, &src), "a parameter the payload does not have"); let others: Vec = other.iter().enumerate().map(|(i, f)| wai::video_receipt::source_frame_hash(i as u64, f)).collect(); let claims_other = signed(&|t| { t.source_frames = others.clone(); t.source_hash = wai::video_receipt::source_hash(16, 16, &others); }); assert!(!claims_other.verify_encode(&payload, &src), "a source the receipt does not state, though the decode's error against it is the stated one"); assert!(!restated(&|t| t.params.search = 3).verify(), "search is attested: inside the signature"); assert!(!restated(&|t| t.joules_micro = 1_201).verify(), "the figure is signed"); assert!(!restated(&|t| t.source_frames.swap(0, 2)).verify(), "the frames in order"); // The class: the unmetered marker, a refined class, never the legacy label. let (u, _) = VideoEncodeReceipt::seal_encode(&key(7), "did:example:encoder", &src, 16, 16, enc_params(), 0, None, None).unwrap(); assert!(u.verify()); let (legacy, _) = VideoEncodeReceipt::seal_encode(&key(7), "did:example:encoder", &src, 16, 16, enc_params(), 1_200, Some(Provenance::HwShunt), None).unwrap(); assert!(!legacy.verify(), "a new profile refuses HwShunt"); let (zero_classed, _) = VideoEncodeReceipt::seal_encode(&key(7), "did:example:encoder", &src, 16, 16, enc_params(), 0, model.clone(), None).unwrap(); assert!(!zero_classed.verify(), "a class beside the marker"); // The JSON form reads back as the receipt, and refuses what it does not write. for x in [r.clone(), u.clone(), VideoEncodeReceipt::seal_encode(&key(9), "did:example:e", &src, 16, 16, enc_params(), 4_000_000, Some(chip()), Some(r.receipt_hash())).unwrap().0] { let j = x.to_json(); assert_eq!(VideoEncodeReceipt::from_json(&j), Ok(x.clone()), "{j}"); } let j = r.to_json(); assert_eq!(VideoEncodeReceipt::from_json(&j.replacen("\"sse\":", "\"sse\":1,\"sse\":", 1)), Err(ReadError::Malformed("not JSON read strictly"))); assert_eq!(VideoEncodeReceipt::from_json(&j.replacen("encode-receipt", "receipt", 1)), Err(ReadError::Malformed("profile"))); assert!(matches!(VideoEncodeReceipt::from_json(&j.replacen(&format!("\"sse\":{}", r.sse), &format!("\"sse\":{}", r.sse + 1), 1)), Err(ReadError::ReceiptHashMismatch))); }