The error, the parameters and the source were each restated without signing again, so the signature refused the control and dropping the check under test went unnoticed. VideoEncodeReceipt::resign (hidden) seals a control over its defect; with it, dropping the SSE recomputation, the parameters' match with the header or the source frames' hashes each fails the test. Co-Authored-By: Claude Opus 5.5 <[email protected]>
473 lines
24 KiB
Rust
473 lines
24 KiB
Rust
//! `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<u8> {
|
||
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<Provenance>) -> (VideoReceipt, Vec<u8>, 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<u8>, 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::<serde_json::Value>(&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::<serde_json::Value>(&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<Vec<u8>> {
|
||
(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<wai::video_receipt::FrameHash> = 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)));
|
||
}
|