wai: prove integer video byte-identical, meter it, and publish the method #60

Merged
dcharlot merged 5 commits from wai/video-byte-equality into main 2026-08-29 03:11:48 -04:00
12 changed files with 1319 additions and 1 deletions

View file

@ -8,6 +8,13 @@ name: wai cross-architecture byte-equality
# int_synth output hash produced on the reference machine (macOS arm64). One
# diverging byte anywhere fails the suite.
#
# Coverage is still-image AND temporal, classical AND learned: the rANS coder,
# integer fixed-point synthesis, the JPEG-AI entropy stage, the inter-frame video
# decoder (`wai.video.int_motion`), and the neural-keyframe variant
# (`wai.video.int_hyper`) whose I-frame comes out of a learned conditional codec. Video is the strictest leg — inter-frame decode is a
# feedback loop, so one divergent half-pel rounding does not stay one byte, it
# predicts the next frame and compounds for the rest of the clip.
#
# The conformance crate (`wai/byte-exact-conformance`) #[path]-includes the
# pure-integer sources from wai-rs — no system codec libraries, no ONNX runtime
# — so every leg runs in about a minute.
@ -18,6 +25,17 @@ on:
- 'wai/byte-exact-conformance/**'
- 'wai/wai-rs/src/neural/int_synth.rs'
- 'wai/wai-rs/src/neural/jpegai_mans.rs'
# Every source the crate #[path]-includes must be able to fire this
# workflow, or a change can break cross-arch equality and merge green.
- 'wai/wai-rs/src/int_synth.rs'
- 'wai/wai-rs/src/int_entropy.rs'
- 'wai/wai-rs/src/jpegai_mans.rs'
- 'wai/wai-rs/src/rans.rs'
- 'wai/wai-rs/src/int_video.rs'
- 'wai/wai-rs/src/int_transform.rs'
- 'wai/wai-rs/src/int_hyper_synth.rs'
- 'wai/wai-rs/tests/vectors/int_video/**'
- 'wai/wai-rs/tests/vectors/int_hyper/**'
- '.github/workflows/wai-byte-equality.yml'
push:
branches:
@ -26,6 +44,17 @@ on:
- 'wai/byte-exact-conformance/**'
- 'wai/wai-rs/src/neural/int_synth.rs'
- 'wai/wai-rs/src/neural/jpegai_mans.rs'
# Every source the crate #[path]-includes must be able to fire this
# workflow, or a change can break cross-arch equality and merge green.
- 'wai/wai-rs/src/int_synth.rs'
- 'wai/wai-rs/src/int_entropy.rs'
- 'wai/wai-rs/src/jpegai_mans.rs'
- 'wai/wai-rs/src/rans.rs'
- 'wai/wai-rs/src/int_video.rs'
- 'wai/wai-rs/src/int_transform.rs'
- 'wai/wai-rs/src/int_hyper_synth.rs'
- 'wai/wai-rs/tests/vectors/int_video/**'
- 'wai/wai-rs/tests/vectors/int_hyper/**'
- '.github/workflows/wai-byte-equality.yml'
jobs:

View file

@ -241,6 +241,11 @@ capabilities are added by registration; the WAI envelope is unchanged.
See [`extensions/jwp-receipts.md`](extensions/jwp-receipts.md) for the
receipt-chain sub-spec covering per-object content hashes, per-group
Merkle roots, Ed25519 group signatures, and cross-hop preservation.
See [`extensions/energy-measurement.md`](extensions/energy-measurement.md) for
how a receipt's `joules_micro` is acquired: the required acquisition class
(`HwShunt` / `ModelBased` / `Estimator`), the bracketed marginal protocol, and
the attribution-quality rule that decides whether a figure may be published at
all.
See [`extensions/confidentiality.md`](extensions/confidentiality.md) for the
private-channel sub-spec: MLS (RFC 9420) group keys + SFrame (RFC 9605)
object encryption, the byte-level framing behind §6.

View file

@ -0,0 +1,119 @@
//! Cross-architecture byte-equality for `wai.video.int_motion` — the temporal
//! leg of the determinism contract.
//!
//! The still-image goldens next door prove a *single* decode is portable. Video
//! is a strictly harder claim, because inter-frame decode is a feedback loop:
//! every P-frame is predicted from the previously **reconstructed** frame, so an
//! architecture that rounds one half-pel sample differently does not produce one
//! wrong byte — it produces a frame that is then used to predict the next frame,
//! and the error compounds for the rest of the clip. Per-frame hashes are
//! asserted rather than only the clip hash for exactly that reason: they say
//! *where* divergence began, which a single terminal hash cannot.
//!
//! The vectors are the ones `wai-rs/tests/int_video.rs` already commits. That
//! test only ever ran where someone passed `--features video_int`; these run on
//! every leg of the matrix, which is the difference between a determinism claim
//! and a determinism proof.
#![cfg(test)]
use std::path::PathBuf;
use serde_json::Value;
use crate::int_video::{decode, fnv1a};
fn vectors() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../wai-rs/tests/vectors/int_video")
}
fn jload(name: &str) -> Value {
let path = vectors().join(name);
let bytes = std::fs::read(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
serde_json::from_slice(&bytes).unwrap_or_else(|e| panic!("parse {}: {e}", path.display()))
}
/// The 8 raw bytes behind a 16-hex string. The clip hash is taken over the
/// concatenated frame-hash *bytes*, not their hex text.
fn hex16(s: &str) -> [u8; 8] {
let mut o = [0u8; 8];
for (i, b) in o.iter_mut().enumerate() {
*b = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).expect("hex");
}
o
}
/// Decode a committed clip and assert every frame hash and the clip hash.
/// Returns the clip hash so callers can pin it verbatim.
fn assert_clip(bitstream: &str, conf: &str) -> String {
let bs = jload(bitstream);
let conf = jload(conf);
let clip = decode(&bs).expect("decode must succeed on every architecture");
let h = conf["h"].as_u64().expect("h") as usize;
let w = conf["w"].as_u64().expect("w") as usize;
let want = conf["frame_hashes"].as_array().expect("frame_hashes");
assert_eq!((clip.h, clip.w), (h, w), "frame geometry");
assert_eq!(clip.frames.len(), want.len(), "frame count");
let mut all = Vec::with_capacity(want.len() * 8);
for (i, frame) in clip.frames.iter().enumerate() {
assert_eq!(frame.len(), h * w * 3, "frame {i} size");
let got = format!("{:016x}", fnv1a(frame));
assert_eq!(
got,
want[i].as_str().expect("frame hash"),
"frame {i} diverged — inter-frame decode is a feedback loop, so this \
is also the last frame that can be trusted"
);
all.extend_from_slice(&hex16(&got));
}
let clip_hash = format!("{:016x}", fnv1a(&all));
assert_eq!(clip_hash, conf["clip_hash"].as_str().expect("clip_hash"), "clip hash");
clip_hash
}
/// The synthetic clip: 5 frames of 48×48 RGB, whole-pel motion.
#[test]
fn int_video_synthetic_clip_is_byte_identical() {
let clip = assert_clip("int_video_bitstream.json", "int_video.json");
assert_eq!(clip, "27573cdba1ad6628");
println!("int_video (synthetic, 5x48x48): clip {clip}");
}
/// Real footage, 8 frames of 96×96. This is the load-bearing case: the encoder
/// chose half-pel motion for it (27_960 bytes against 38_232 whole-pel), so the
/// bilinear predictor — the integer rounding most at risk of differing between
/// architectures — is actually exercised rather than merely present.
#[test]
fn int_video_real_footage_exercises_the_half_pel_predictor() {
let conf = jload("video_real.json");
let half = conf["half_pel_bytes"].as_u64().expect("half_pel_bytes");
let whole = conf["whole_pel_bytes"].as_u64().expect("whole_pel_bytes");
assert!(
half < whole,
"vector no longer prefers half-pel ({half} >= {whole}); this test would \
still pass while silently covering only the whole-pel path"
);
let clip = assert_clip("video_real_bitstream.json", "video_real.json");
assert_eq!(clip, "1ec1f0fa7da0d70f");
println!("int_video (real footage, 8x96x96, half-pel): clip {clip}");
}
// NOT covered here: `video_nk` (`wai.video.int_hyper`, the NEURAL-keyframe
// variant). Its I-frame is produced by `int_hyper_synth::decode_conditional`
// and only its P-frames go through `int_video::decode_pframes`, so it cannot be
// driven by `decode()` — an earlier draft of this file tried and got a slice
// overrun out of the rANS reader, which is what feeding one codec another
// codec'"'"'s bitstream looks like. Covering it means `#[path]`-including
// `int_hyper_synth.rs` as well; that is a real follow-on, not a line to add
// here, and until it lands the neural-keyframe leg of the temporal contract
// remains proven only where someone runs `--features neural_int,video_int`
// locally.
//
// The synthetic/real pair above already separates the two failure modes this
// file needs to distinguish: the synthetic clip is whole-pel only, so a failure
// there implicates the entropy or residual stage, while a failure confined to
// the real-footage clip implicates the half-pel predictor.

View file

@ -23,5 +23,37 @@ pub mod int_entropy;
#[path = "../../wai-rs/src/jpegai_mans.rs"]
pub mod jpegai_mans_exact;
// `int_video.rs` names the rANS decoder as `crate::rans` (its path in wai-rs,
// where rans is a crate-root module). Here rans is included under `neural`, so
// re-export rather than `#[path]`-include a second copy: two includes would
// compile two structurally identical but distinct types, and the whole point of
// this crate is that there is one source of truth per decode path.
pub use neural::rans;
// The deterministic integer INTER-FRAME video decoder (`wai.video.int_motion`).
// Included so the cross-arch matrix covers temporal decode, not only the
// still-image and synthesis paths. This one matters disproportionately: its
// half-pel predictor is the only place in the integer contract where a
// *rounding* decision (`(a + b + 1) >> 1` over clamped neighbours) sits inside a
// feedback loop — each P-frame predicts from the previously reconstructed
// frame, so a single divergent byte on one architecture does not stay one byte.
// It propagates through every subsequent frame.
#[path = "../../wai-rs/src/int_video.rs"]
pub mod int_video;
// The integer transform kernels (`igdn` and friends) the learned codecs use.
// Self-contained — no crate-internal dependencies of its own.
#[path = "../../wai-rs/src/int_transform.rs"]
pub mod int_transform;
// The learned integer conditional codec that decodes a NEURAL keyframe. Needed
// for `wai.video.int_hyper`, whose I-frame comes from here rather than from
// `int_video::decode` — the gap the previous commit named and left open, since
// covering it meant pulling this source in rather than adding a line.
#[path = "../../wai-rs/src/int_hyper_synth.rs"]
pub mod int_hyper_synth;
mod golden;
mod int_video_golden;
mod neural_keyframe_golden;
mod jpegai_golden;

View file

@ -0,0 +1,108 @@
//! Cross-architecture byte-equality for `wai.video.int_hyper` — the
//! **neural-keyframe** inter-frame codec.
//!
//! This closes the gap the video goldens next door named and left open. That
//! file covers `wai.video.int_motion`, whose I-frame is an ordinary integer
//! keyframe; this one covers the variant whose I-frame is decoded by the
//! *learned* conditional codec (`int_hyper_synth`) and whose P-frames are
//! integer motion-compensated residuals on top of it.
//!
//! It is the strictest composition in the container: a learned decode feeding a
//! feedback loop. The keyframe comes out of a quantised transform network, and
//! every subsequent frame predicts from the previously reconstructed one — so a
//! single divergent byte anywhere in the learned synthesis does not stay in the
//! keyframe, it propagates through the whole clip. A learned keyframe that
//! reproduces bit-for-bit on four architectures is the claim that makes
//! "a learned codec you can put in a receipt" mean anything.
#![cfg(test)]
use std::path::PathBuf;
use serde_json::Value;
use crate::int_hyper_synth::{decode_conditional, CondBitstream, CondEntropy, IntModel};
use crate::int_video::{decode_pframes, fnv1a};
fn vectors() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../wai-rs/tests/vectors")
}
fn jload(rel: &str) -> Value {
let path = vectors().join(rel);
let bytes = std::fs::read(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
serde_json::from_slice(&bytes).unwrap_or_else(|e| panic!("parse {}: {e}", path.display()))
}
/// The 8 raw bytes behind a 16-hex string.
fn hex16(s: &str) -> [u8; 8] {
let mut o = [0u8; 8];
for (i, b) in o.iter_mut().enumerate() {
*b = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).expect("hex");
}
o
}
/// Frame 0 from the learned conditional codec, frames 1..N from integer motion
/// compensation on top — every one asserted against its committed hash.
#[test]
fn the_neural_keyframe_clip_is_byte_identical() {
// The learned I-frame: analysis/synthesis models, the conditional entropy
// parameters, and the committed bitstream.
let g_s = IntModel::from_json(&jload("int_hyper/tiny_g_s.json"));
let h_s = IntModel::from_json(&jload("int_hyper/tiny_h_s.json"));
let ent = CondEntropy::from_value(&jload("int_hyper/tiny_entropy.json"));
let bs = CondBitstream::from_value(&jload("int_hyper/tiny_bitstream.json"));
let (frame0, h, w) = decode_conditional(&g_s, &h_s, &ent, &bs).expect("learned I-frame");
let conf = jload("int_video/video_nk.json");
let vbs = jload("int_video/video_nk_bitstream.json");
assert_eq!(h, conf["h"].as_u64().expect("h") as usize, "keyframe height");
assert_eq!(w, conf["w"].as_u64().expect("w") as usize, "keyframe width");
let clip = decode_pframes(&frame0, h, w, &vbs).expect("integer P-frames");
let want = conf["frame_hashes"].as_array().expect("frame_hashes");
assert_eq!(clip.frames.len(), want.len(), "frame count");
let mut all = Vec::with_capacity(want.len() * 8);
for (i, frame) in clip.frames.iter().enumerate() {
assert_eq!(frame.len(), h * w * 3, "frame {i} size");
let got = format!("{:016x}", fnv1a(frame));
assert_eq!(
got,
want[i].as_str().expect("frame hash"),
"frame {i} diverged — frame 0 is the learned keyframe, so a mismatch there \
implicates the conditional codec, while a later-only mismatch implicates \
the motion predictor"
);
all.extend_from_slice(&hex16(&got));
}
let clip_hash = format!("{:016x}", fnv1a(&all));
assert_eq!(clip_hash, conf["clip_hash"].as_str().expect("clip_hash"));
assert_eq!(clip_hash, "a5f53d477999d159");
println!("int_hyper keyframe clip ({} frames {h}x{w}): {clip_hash}", clip.frames.len());
}
/// Frame 0 of the clip must be exactly what the learned codec produced, with no
/// motion compensation applied. Pinned separately so a regression in the
/// conditional codec is distinguishable from one in the temporal stage.
#[test]
fn the_learned_keyframe_alone_is_byte_identical() {
let g_s = IntModel::from_json(&jload("int_hyper/tiny_g_s.json"));
let h_s = IntModel::from_json(&jload("int_hyper/tiny_h_s.json"));
let ent = CondEntropy::from_value(&jload("int_hyper/tiny_entropy.json"));
let bs = CondBitstream::from_value(&jload("int_hyper/tiny_bitstream.json"));
let (frame0, h, w) = decode_conditional(&g_s, &h_s, &ent, &bs).expect("learned I-frame");
let conf = jload("int_video/video_nk.json");
let want0 = conf["frame_hashes"].as_array().expect("frame_hashes")[0]
.as_str()
.expect("frame 0 hash");
assert_eq!(frame0.len(), h * w * 3);
assert_eq!(
format!("{:016x}", fnv1a(&frame0)),
want0,
"the learned keyframe itself diverged, before any temporal decode"
);
}

View file

@ -0,0 +1,216 @@
# WAI Extension: Energy Measurement
> Status: Draft. Specifies **how a joule figure is acquired**, so that the
> `joules_micro` field the receipt profiles already carry means the same thing
> in two independent implementations. No new wire format; an acquisition
> protocol, a reporting contract, and a quality rule over the existing receipt
> fields.
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
## 1. Why an acquisition protocol
[`jwp-receipts.md`](jwp-receipts.md) defines `joules_micro` as "microjoules
consumed producing this object". That fixes the *unit* and the *scope of
attribution*. It does not fix how a number gets into the field, and without that
the field is a number-shaped hole: one implementation can write a counter
reading, another an arithmetic model, a third a constant, and every one of them
is conformant. The figures are then incomparable, and — worse — indistinguishable,
because nothing in the receipt says which kind it is.
This extension closes that. It requires two things of any receipt carrying
energy: that the figure declare **how it was acquired**, and that the
measurement declare **whether it was good enough to publish**.
The contribution is not the number. It is the number *plus its acquisition
class*, and the discipline of withholding both when the measurement fails.
## 2. The acquisition class (REQUIRED)
A receipt carrying a non-zero energy figure **MUST** carry an acquisition class
alongside it, from the shared vocabulary:
| class | meaning | requires |
|---|---|---|
| `HwShunt` | Read from a hardware energy interface — RAPL, NVML, SMC, IOReport, or an external shunt. | a present, non-zero measurement |
| `ModelBased` | Computed from a calibrated per-operation model (e.g. wall time × a power constant). A real number, honestly labelled as derived. | — |
| `Estimator` | A coarse constant or load estimator. | — |
These are the same three names the receipt families elsewhere in the ecosystem
already use; this extension deliberately reuses the vocabulary rather than
minting a parallel one for media.
A counter reading is **not** an estimate. RAPL, NVML and IOReport are sensor
values, and `HwShunt` says so. What separates it from the rungs below is
provenance and scope, not accuracy — see §5.
Normative:
- An implementation **MUST NOT** emit a non-zero energy figure without a class.
An unlabelled number cannot be compared to any other number.
- An implementation **MUST NOT** emit a class with a zero or absent figure. That
claims a measurement which did not occur.
- The class **MUST** be inside the signature, so an intermediary cannot promote
a `ModelBased` figure to `HwShunt` in transit.
- A verifier **MUST** treat a receipt violating either coupling as invalid, not
merely as unlabelled.
The reference implementation enforces both couplings in `verify()` rather than
documenting them, and returns *no rate at all* — never `0` — when unmetered,
because a zero reads as "this work was free", which is a stronger and false
claim than "nobody measured".
## 3. The reference protocol: bracketed marginal measurement
On-die energy counters report the draw of a whole package or SoC, not of one
process. A decode cannot be isolated on such an interface, so the reference
protocol measures the **marginal** draw the work adds to an otherwise-idle
machine:
1. **Idle bracket.** Sample power for a fixed count with no work running.
Record the mean and the **standard deviation**.
2. **Saturating burst.** Run the reconstruction continuously on *N* threads,
counting completions. Many threads are used deliberately: a single-threaded
burst on a modern SoC often fails to lift total draw clear of idle drift.
3. **Busy bracket.** Sample power again for a fixed count while the burst runs,
recording the wall-clock window it spans.
4. **Attribute.** `energy = max(0, mean_busy − mean_idle) × window`, then divide
by the number of completions in that window.
Per-unit energy is total marginal energy ÷ total completions, so the thread
count does not enter the result. Completions **MUST** be counted per
reconstruction, not per fixed-size batch: a cheap payload runs millions of times
and an expensive one a handful, and a coarse batch collapses the count.
An implementation **MUST** report the idle mean, the busy mean, the sample
counts, the window, and the completion count alongside any figure it publishes.
Those five numbers are what make a figure auditable; the figure alone is not.
## 4. Attribution quality (REQUIRED)
The marginal method is only meaningful when the burst lifts power **clearly
above the machine's own idle fluctuation**. When it does not, the method does
not degrade gracefully — it returns a confidently-formatted number that is far
too low, because an inflated idle baseline is subtracted from a busy mean that
never rose.
This is not a theoretical concern. Measuring one fixed clip with one binary,
minutes apart on the same machine:
| condition | idle | busy | marginal | reported |
|---|---|---|---|---|
| quiet | 20.45 W | 54.89 W | 34.45 W | **10,565 µJ/decode** |
| *(the quiet row predates this rule, so its ratio was not recorded)* | | | | |
| build running | ~23.9 W | 24.26 W | 0.36 W | **497 µJ/decode** |
A **21×** error, in the flattering direction, with nothing in the output to
distinguish the two.
So an implementation **MUST** compute the ratio of the marginal draw to the idle
standard deviation and report it:
| ratio | verdict |
|---|---|
| ≥ 10 | clean — publishable |
| ≥ 3 | usable for relative comparison — **not** publishable |
| ≥ 1 | **noisy — do not publish** |
| < 1 | **unusable — the machine was not idle** |
The publication bar is 10, not 3, and that is an empirical result rather than a
round number. A later reading of the same clip at a ratio of **3.8** — inside
what an earlier draft of this rule called "usable" — reported 2,469 µJ against
the 10,565 µJ above: a **4.3× disagreement from a reading the rule had passed.**
A ratio in the 3–10 band is sound for comparing two workloads measured
back-to-back on one machine, because the bias applies to both. It is not sound
for an absolute figure.
The bias has a direction. A busy machine inflates the idle baseline, which is
then subtracted from a busy mean that never rose as far, so a poor bracket
always reads **low**. An implementation reporting energy therefore errs toward
flattering itself, which is precisely why the check must be automatic rather
than left to the operator's judgement.
- An implementation **MUST NOT** seal a `HwShunt` figure when the ratio is below
1, or when the per-unit figure rounds to zero. Neither is a measurement of low
energy; both are a failure to measure, and the receipt **MUST** be emitted
unmetered instead.
- An implementation **MUST NOT** publish an absolute figure taken below a ratio
of 10, and **SHOULD** warn whenever it produces one.
Emitting an unmetered receipt is the correct outcome of a failed measurement,
not an error state. A tool that says "I did not measure this" is more useful
than one that says "this decode cost 267 µJ" when the true figure is forty times
higher.
## 5. What the figure is, and is not
- **It is machine-specific.** The same codestream on different silicon
legitimately measures differently. Energy is a property of an execution, not
of an artifact.
- **It is a marginal figure over an idle baseline**, so it excludes the fixed
cost of having a machine powered on, and attributes shared platform overhead
to the work only insofar as the work raised it.
- **It is scoped to what the interface reports** — a package or an SoC, not a
process. Provenance and scope are the honest limitations, not sensor accuracy.
- **It depends on the build.** A debug build of the reference decoder measured
**4.9×** a release build of the same code. An implementation **MUST** state
the build profile alongside any published figure.
- **It is not verifiable by re-execution.** A third party re-running the decode
reproduces the *result* and the *work*, never the joules.
That last point is the reason the receipt profiles are shaped as they are. Where
a class's reconstruction is byte-exact and proven across architectures, every
field except the energy is independently checkable, and the energy is the single
attested variable. Pairing a meter with a reproducible outcome is what turns an
energy receipt from a claim into a measurement with one unverifiable term — so
an implementation **SHOULD** report energy against a work unit that a verifier
can recompute from the artifact, and **MUST NOT** report it against a work unit
only the producer can compute.
## 6. Reporting contract
A published figure **MUST** be accompanied by: the acquisition class (§2); the
idle mean and standard deviation, busy mean, sample counts, window and
completion count (§3); the attribution-quality ratio (§4); the build profile
(§5); and the recomputable work unit the rate is expressed against.
A figure published without these is not wrong so much as unusable: nothing about
it can be checked, reproduced, or compared.
## 7. Worked example
`wai.video.int_motion`, real-footage vector, 8 frames of 96×96 RGB, release
build, IOReport via a non-privileged reader:
```
work : 221,184 subpixel-ops (8 x 96 x 96 x 3, recomputable)
cpu power idle : 24.95 W (sd 0.23 W, 10 samples)
cpu power busy : 25.84 W (14 samples)
marginal (busy-idle): 0.89 W
decodes : 1,525 over 4.23 s
marginal / idle sd : 3.8x (usable for relative comparison — NOT publishable)
-> per decode : 2,468.8 uJ
-> per kilo-subpixel : 11.16 uJ
acquisition class : HwShunt
```
**This example is deliberately a failing one.** It is a complete, honestly
reported bracket that this extension nonetheless forbids publishing as an
absolute figure, because 3.8 is below the bar of §4 — and the earlier reading of
the same clip on a quieter machine differed by 4.3×. It is shown rather than a
clean bracket because a clean bracket is not currently reproducible on the
authoring machine, and substituting an invented one would be the exact failure
this document exists to prevent. An implementation quoting a figure for this
vector **MUST** first obtain a bracket at ≥ 10.
The receipt seals the codestream hash, the per-frame decoded-pixel hashes, the
work figure, the energy and its class under one signature. A verifier re-derives
everything but the energy from the codestream alone.
## 8. Reproducing a figure
An implementation claiming conformance **SHOULD** publish, with any figure: the
vector decoded, the build profile and toolchain, the reader used for the power
interface, and the five bracket parameters of §3. A figure whose bracket is not
published cannot be reproduced, and an unreproducible energy number is an
assertion, not a measurement — which is the failure this extension exists to
prevent.

View file

@ -150,7 +150,7 @@ Required fields:
| `group_id` | u64 | Mirrors the parent group receipt. |
| `object_id` | u64 | Monotonic from 0 within the group. |
| `content_hash` | 32 B hex | §2.1 BLAKE3 over the WAI envelope bytes. |
| `joules_micro` | u64 | Microjoules consumed producing this object. |
| `joules_micro` | u64 | Microjoules consumed producing this object. This field fixes the unit and the scope of attribution, not how the number is obtained — see [energy-measurement](energy-measurement.md), which requires an acquisition class beside it and forbids publishing a figure from a measurement that failed. |
| `origin` | string | Datacenter / origin label. |
| `merkle_proof` | object | Inclusion proof: `{ "siblings": [32 B hex, …], "direction_bits": u64, "leaf_index": u64 }`. `direction_bits` bit `i` is `1` iff the current node at level `i` is the right child. |

View file

@ -155,6 +155,13 @@ quantum = ["dep:blake3"]
# from the sink's meter), both bound to a re-simulatable statevector hash. Reuses
# the JWP Merkle + Ed25519 from `world` — reuse, not reinvention.
quantum_receipt = ["quantum", "world"]
# The JWP receipt profile for the integer inter-frame video codec. Follows the
# same shape as `quantum_receipt`: the class feature plus `world`, which is what
# supplies blake3, ed25519 and the shared `merkle_root`. Kept OUT of `video_int`
# on purpose — `int_video.rs` is #[path]-included by the byte-exact conformance
# crate, and that crate must keep its three-dependency set to stay runnable on
# every leg of the cross-arch matrix.
video_receipt = ["video_int", "world"]
# Balanced-ternary matrix-product-state transform (`wai.ternary.tt`,
# extensions/ternary-tt): quantum-INSPIRED (tensor-network) compression whose
# novel delta is an INTEGER core scale — the whole decode is pure i64/i128, so a
@ -287,6 +294,11 @@ name = "wai_quantum_meter"
path = "src/bin/wai_quantum_meter.rs"
required-features = ["quantum_receipt"]
[[bin]]
name = "wai_video_meter"
path = "src/bin/wai_video_meter.rs"
required-features = ["video_receipt"]
[[bin]]
name = "wai_meter"
path = "src/bin/wai_meter.rs"

View file

@ -0,0 +1,236 @@
//! `wai_video_meter` — the native sink-metering path for `wai.video.int_motion`,
//! the integer inter-frame video codec.
//!
//! Measures the **real CPU energy** a sink spends decoding an integer video
//! codestream to frames, with no elevated privileges. On Apple Silicon the SoC
//! energy counters are readable through IOReport — the same source `macmon`/
//! `asitop` use — so this meter samples power via `macmon`, brackets a tight
//! decode burst against an idle baseline, and reports the **marginal** CPU
//! energy attributable to the decode.
//!
//! wai_video_meter <bitstream.json> [receipt_out.json]
//!
//! Video was the last cargo class with no meter: haptics, 4D splats, scores and
//! film are metered by `wai_meter`, worlds/feeds/avatars by `wai_world_meter`,
//! quantum circuits by `wai_quantum_meter`. It also has the strongest claim of
//! any of them, because its decode is not merely deterministic by construction
//! but **proven byte-identical on four architectures** by the cross-arch
//! matrix. So of everything the emitted receipt says, only `joules_micro` rests
//! on trusting this machine — the result and the work can be re-derived from the
//! codestream by anyone, anywhere.
//!
//! This is a *measurement* with stated bounds, exactly as the sibling meters:
//! IOReport CPU power is SoC-wide, so the meter reports the DELTA over an
//! otherwise-idle machine (busy − idle mean power × wall-time ÷ decodes). Run it
//! on a quiet box for a clean number; it never fabricates one. Without `macmon`
//! it emits an UNMETERED receipt (`joules_micro = 0`) rather than an estimate,
//! and that receipt reports no rate at all — an absent measurement must never be
//! readable as "this decode was free".
use std::process::Command;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Instant;
use ed25519_dalek::SigningKey;
use wai::int_video::decode;
use wai::video_receipt::{work_pixel_ops, Provenance, VideoReceipt};
fn main() {
let args: Vec<String> = std::env::args().collect();
let Some(path) = args.get(1) else {
eprintln!("usage: wai_video_meter <bitstream.json> [receipt_out.json]");
std::process::exit(2);
};
let bytes = std::fs::read(path).expect("read bitstream");
let bs: serde_json::Value = serde_json::from_slice(&bytes).expect("parse bitstream JSON");
// A decode that does not even run is not meterable.
let clip = decode(&bs).expect("codestream decodes");
let (n, h, w) = (clip.frames.len() as u64, clip.h as u64, clip.w as u64);
let work = work_pixel_ops(n, h, w);
eprintln!(
"metering {path} ({n} frames {h}x{w} RGB, work {work} subpixel-ops), \
no sudo — IOReport via macmon"
);
// Phase A: idle baseline (no decode running).
eprintln!(" idle baseline …");
let idle = sample_cpu_power(10);
let metered = idle.len() >= 3;
if !metered {
eprintln!(
"macmon unavailable or too few samples; emitting an UNMETERED receipt \
(joules_micro = 0). Install `brew install macmon` (the non-sudo \
IOReport reader) for a measured figure."
);
}
// `Some(uj)` only when a figure was genuinely obtained. A measurement that
// rounds to zero is not a measurement of zero energy — the decode plainly
// spent some — it is a failure to measure, and it must be reported as one.
let measured: Option<u64> = if metered {
// Phase B: saturate several cores with real decodes while sampling.
// Many threads lift the marginal draw above the box's idle fluctuation;
// energy per decode is total measured energy ÷ total decodes regardless
// of how many cores ran them.
let workers = std::thread::available_parallelism()
.map(|n| n.get().saturating_sub(1).clamp(1, 10))
.unwrap_or(6);
eprintln!(" decode burst on {workers} threads …");
let stop = Arc::new(AtomicBool::new(false));
let iters = Arc::new(AtomicU64::new(0));
let handles: Vec<_> = (0..workers)
.map(|_| {
let (stop, iters, b) = (stop.clone(), iters.clone(), bs.clone());
std::thread::spawn(move || {
// Increment per decode, not per fixed batch: a short clip
// runs many times and a long one only a few, and `runs` must
// track whichever actually happened.
while !stop.load(Ordering::Relaxed) {
let _ = decode(&b);
iters.fetch_add(1, Ordering::Relaxed);
}
})
})
.collect();
let i0 = iters.load(Ordering::Relaxed);
let t0 = Instant::now();
let busy = sample_cpu_power(14);
let window = t0.elapsed().as_secs_f64();
let i1 = iters.load(Ordering::Relaxed);
stop.store(true, Ordering::Relaxed);
for hd in handles {
hd.join().ok();
}
let runs = (i1 - i0).max(1);
let mean = |v: &[f64]| v.iter().sum::<f64>() / v.len() as f64;
let sd = |v: &[f64]| {
let m = mean(v);
(v.iter().map(|x| (x - m).powi(2)).sum::<f64>() / v.len() as f64).sqrt()
};
let idle_w = mean(&idle);
let busy_w = mean(&busy);
let idle_sd = sd(&idle);
let marginal_w = (busy_w - idle_w).max(0.0);
let energy_j = marginal_w * window;
let per_run_uj = energy_j / runs as f64 * 1e6;
// Attribution quality, stated rather than assumed. The marginal method
// is only meaningful when the burst lifts power clearly above the box's
// own idle fluctuation. On a machine that was already busy the baseline
// is inflated, the delta collapses toward zero, and the meter would
// otherwise report a confidently-formatted number that is far too low —
// measured on this very tool, the same clip read 10,565 uJ on a quiet
// box and 497 uJ while a build was running, a 21x error in the
// flattering direction. Silence about that is how a bad number travels.
let snr = if idle_sd > 0.0 { marginal_w / idle_sd } else { f64::INFINITY };
println!("== wai_video_meter — measured CPU energy (IOReport via macmon, no sudo) ==");
println!("decodes : {runs} over {window:.2}s");
println!("cpu power idle : {idle_w:.2} W (sd {idle_sd:.2} W, {} samples)", idle.len());
println!("cpu power busy : {busy_w:.2} W ({} samples)", busy.len());
println!("marginal (busy-idle): {marginal_w:.2} W");
println!("→ per decode : {per_run_uj:.1} µJ ({n} frames {h}x{w}, work {work})");
if work > 0 {
println!("→ per kilo-subpixel : {:.4} µJ", per_run_uj * 1000.0 / work as f64);
}
println!("marginal / idle sd : {snr:.1}x ({})", quality(snr));
// The publication bar is 10x, not 3x, and that is an empirical finding
// rather than a round number: on this tool the same clip read 2,469 uJ
// at a 3.8x ratio and 10,565 uJ on a quiet box — a 4.3x disagreement
// from a reading an earlier version of this check called "usable". A
// ratio in the 3-10 band is good enough to compare two workloads
// measured back-to-back on the same machine; it is not good enough to
// publish an absolute figure.
if snr < 10.0 {
eprintln!(
"WARNING: marginal draw {marginal_w:.2} W against idle sd {idle_sd:.2} W \n\
({snr:.1}x). Absolute figures are only publishable at >= 10x; below \n\
that the inflated idle baseline biases the result LOW by a factor \n\
that has been observed as large as 21x. Close other work and \n\
re-run on a quiet box before quoting this number."
);
}
let uj = per_run_uj.round() as u64;
if uj == 0 || snr < 1.0 {
eprintln!(
"no usable figure obtained (per-decode {uj} uJ at {snr:.1}x noise); \n\
emitting an UNMETERED receipt rather than sealing a number the \n\
measurement does not support."
);
None
} else {
println!("joules_micro (receipt, MEASURED): {uj}");
Some(uj)
}
} else {
None
};
// Optional: emit a receipt carrying the measured (or unmetered) energy.
if let Some(out) = args.get(2) {
// A native sink signs with its own identity key; this metering tool
// uses a fixed, clearly-labeled demo signer.
let signer = SigningKey::from_bytes(&[0x76u8; 32]); // 'v' for video-meter
let receipt = VideoReceipt::seal_decode(
&signer,
"did:web:wai.transaction.science#wai_video_meter",
&bytes,
&bs,
measured.unwrap_or(0),
// IOReport is a hardware energy interface, so a reading from it is
// `HwShunt` — a sensor value, not an estimate. Its limitation is
// scope (SoC-wide, hence the marginal-over-idle attribution), not
// accuracy. With no reader present there is no measurement at all,
// and the receipt says so by carrying no class rather than
// downgrading to a label it also did not earn.
measured.map(|_| Provenance::HwShunt),
None,
)
.expect("seal receipt");
assert!(receipt.verify(), "emitted receipt must verify");
assert!(
receipt.verify_reconstruction(&bytes, &bs),
"emitted receipt must re-decode to the same frames"
);
std::fs::write(out, receipt.to_json()).expect("write receipt");
let tag = if measured.is_some() { "MEASURED" } else { "UNMETERED" };
println!("receipt : {out} (verified, joules_micro {tag}, work {work})");
}
}
/// A plain-language verdict on attribution quality: how far the burst's
/// marginal draw rose above the machine's own idle fluctuation.
fn quality(snr: f64) -> &'static str {
match snr {
s if s >= 10.0 => "clean — publishable",
s if s >= 3.0 => "usable for relative comparison — NOT publishable",
s if s >= 1.0 => "NOISY — do not publish",
_ => "UNUSABLE — the box was not idle",
}
}
/// One IOReport sample burst via `macmon pipe`, returning the CPU-power readings
/// (W). Same non-sudo reader and parse as the sibling meters.
fn sample_cpu_power(n: u32) -> Vec<f64> {
let out = Command::new("macmon")
.args(["pipe", "-s", &n.to_string(), "-i", "200"])
.output();
let Ok(out) = out else { return vec![] };
String::from_utf8_lossy(&out.stdout)
.lines()
.filter_map(cpu_power_w)
.collect()
}
/// Pull `"cpu_power": <W>` out of one macmon JSON line.
fn cpu_power_w(line: &str) -> Option<f64> {
let key = "\"cpu_power\":";
let i = line.find(key)? + key.len();
let rest = line[i..].trim_start();
let end = rest.find([',', '}']).unwrap_or(rest.len());
rest[..end].trim().parse().ok()
}

View file

@ -230,6 +230,9 @@ pub mod quantum;
#[cfg(feature = "quantum_receipt")]
pub mod quantum_receipt;
#[cfg(feature = "video_receipt")]
pub mod video_receipt;
// Balanced-ternary matrix-product-state transform (extensions/ternary-tt,
// `wai.ternary.tt`): quantum-INSPIRED (tensor-network) compression. The ternary
// weight fabric (BitNet-b1.58, 5 trits/byte) + MPS are ported, not reinvented;

View file

@ -0,0 +1,382 @@
//! Video receipt — a JWP profile for `wai.video.int_motion`, the integer
//! inter-frame codec. The **codestream hash** is the identity; the group's
//! objects are the per-frame decoded-pixel hashes; one Ed25519 signature covers
//! codestream hash + frame hashes + exact reconstruction work + measured joules.
//! Chains via `parent_receipt_hash` exactly as the other classes — reuse, not
//! reinvention.
//!
//! **What makes this profile different from its siblings.** In every other
//! cargo class the reconstruction is deterministic *by construction* and taken
//! on trust. Here it is deterministic *and proven on real silicon*: the
//! cross-architecture byte-equality matrix decodes these same vectors on
//! linux-x86_64, linux-aarch64, macos-arm64 and windows-x86_64 and asserts
//! identical bytes. So a verifier holding only the codestream can independently
//! recompute both `clip_hash` and `work_pixel_ops` — see
//! [`VideoReceipt::verify_reconstruction`] — and every field except the energy
//! stops being a claim and becomes a checkable fact.
//!
//! That leaves exactly one attested-not-verifiable field, `joules_micro`, and
//! the receipt is shaped so a reader cannot mistake which is which:
//!
//! - `codestream_hash`, `clip_hash`, `frames`, `work_pixel_ops` — **verifiable**
//! by anyone with the bitstream, on any of the four proven architectures.
//! - `joules_micro` — **measured-attested**: the real marginal CPU energy the
//! metering sink spent, vouched for by the signature and by nothing else. A
//! different machine will legitimately measure a different number for a
//! byte-identical decode.
//!
//! This module deliberately lives outside `int_video.rs`. That file is
//! `#[path]`-included by `wai/byte-exact-conformance`, whose whole purpose is to
//! compile the decode path with no codec libraries, no ONNX and a three-crate
//! dependency set; pulling blake3 and ed25519 into it would break the matrix
//! that makes the paragraph above true.
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use serde_json::Value;
use crate::int_video::{decode, DecodedClip};
use crate::world::receipt::merkle_root;
const DOMAIN_RECEIPT: &[u8] = b"wai:video-receipt\x01";
const DOMAIN_RECEIPT_ID: &[u8] = b"wai:video-receipt-id\x01";
const DOMAIN_FRAME: &[u8] = b"wai:video-rframe\x01";
const DOMAIN_CLIP: &[u8] = b"wai:video-clip\x01";
/// How an energy figure was acquired — the family's ladder, reused verbatim
/// from `mesh-schema` / `proof-schema` rather than reinvented for this class.
///
/// A joule figure without this label is not comparable to another: the same
/// number can mean a counter reading, an arithmetic model, or a constant.
/// Sealing the label beside the number is the whole point — the number is not
/// the contribution, the number *plus its acquisition class* is.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Provenance {
/// Read from a hardware energy interface (RAPL / NVML / SMC / IOReport /
/// shunt). Requires a present, non-zero measurement.
HwShunt,
/// Computed from a calibrated per-operation model (e.g. wall time x a power
/// constant). A real number, honestly labelled as derived.
ModelBased,
/// A coarse constant or load estimator.
Estimator,
}
impl Provenance {
/// The receipt wire label.
pub fn label(self) -> &'static str {
match self {
Provenance::HwShunt => "HwShunt",
Provenance::ModelBased => "ModelBased",
Provenance::Estimator => "Estimator",
}
}
/// The byte written into the signing payload.
fn tag(self) -> u8 {
match self {
Provenance::HwShunt => 1,
Provenance::ModelBased => 2,
Provenance::Estimator => 3,
}
}
}
/// `(frame_index, BLAKE3 over the frame's decoded RGB bytes)`.
///
/// The index is inside the hash, not merely beside it: inter-frame decode is
/// order-dependent, so a receipt whose frames could be permuted without
/// changing a leaf would vouch for a clip nobody can play.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FrameHash {
pub index: u64,
pub hash: [u8; 32],
}
/// The exact, portable reconstruction work: one op per decoded subpixel.
///
/// Verified by recomputation from `(frames, h, w)`, so it cannot be inflated to
/// make an energy figure look efficient. It counts *output* subpixels rather
/// than motion-vector or residual operations because only the output is fixed
/// by the format — a future encoder may spend fewer residual ops on the same
/// clip, and the receipt should stay comparable across encoders.
pub fn work_pixel_ops(n_frames: u64, h: u64, w: u64) -> u64 {
n_frames.saturating_mul(h).saturating_mul(w).saturating_mul(3)
}
/// BLAKE3 over one frame's decoded RGB bytes, bound to its index.
pub fn frame_hash(index: u64, rgb: &[u8]) -> FrameHash {
let mut h = blake3::Hasher::new();
h.update(DOMAIN_FRAME);
h.update(&index.to_le_bytes());
h.update(rgb);
FrameHash { index, hash: *h.finalize().as_bytes() }
}
/// The portable whole-clip commitment: geometry, then every frame hash in order.
pub fn clip_hash(h: u32, w: u32, frames: &[FrameHash]) -> [u8; 32] {
let mut hs = blake3::Hasher::new();
hs.update(DOMAIN_CLIP);
hs.update(&h.to_le_bytes());
hs.update(&w.to_le_bytes());
hs.update(&(frames.len() as u64).to_le_bytes());
for f in frames {
hs.update(&f.index.to_le_bytes());
hs.update(&f.hash);
}
*hs.finalize().as_bytes()
}
#[derive(Clone, Debug, PartialEq)]
pub struct VideoReceipt {
/// Identity: BLAKE3 over the canonical codestream bytes as they were read.
pub codestream_hash: [u8; 32],
/// The reconstruction result: the portable whole-clip commitment.
pub clip_hash: [u8; 32],
pub h: u32,
pub w: u32,
/// Per-frame decoded-pixel hashes, in decode order.
pub frames: Vec<FrameHash>,
/// Merkle root over the frame hashes — JWP's exact domains, reused.
pub merkle_root: [u8; 32],
/// Exact, portable reconstruction work (see [`work_pixel_ops`]).
/// Verified by recomputation.
pub work_pixel_ops: u64,
/// Measured microjoules the sink spent reconstructing (`0` = unmetered).
/// Attested by the signature, not independently verifiable.
pub joules_micro: u64,
/// How `joules_micro` was acquired. `None` iff the receipt is unmetered.
///
/// The coupling is enforced by [`VideoReceipt::verify`], not merely
/// documented: a labelled figure of zero, or an unlabelled figure above
/// zero, is rejected. Either would let a reader take a number at a
/// confidence the producer never claimed.
pub energy_provenance: Option<Provenance>,
pub parent_receipt_hash: Option<[u8; 32]>,
pub signer_pubkey: [u8; 32],
pub signer_id: String,
pub sig: [u8; 64],
}
impl VideoReceipt {
fn signing_payload(&self) -> Vec<u8> {
let mut o = DOMAIN_RECEIPT.to_vec();
o.extend_from_slice(&self.codestream_hash);
o.extend_from_slice(&self.clip_hash);
o.extend_from_slice(&self.h.to_be_bytes());
o.extend_from_slice(&self.w.to_be_bytes());
o.extend_from_slice(&self.merkle_root);
o.extend_from_slice(&(self.frames.len() as u64).to_be_bytes());
o.extend_from_slice(&self.work_pixel_ops.to_be_bytes());
o.extend_from_slice(&self.joules_micro.to_be_bytes());
o.push(self.energy_provenance.map_or(0, Provenance::tag));
match self.parent_receipt_hash {
Some(h) => {
o.push(1);
o.extend_from_slice(&h);
}
None => o.push(0),
}
for f in &self.frames {
o.extend_from_slice(&f.index.to_be_bytes());
o.extend_from_slice(&f.hash);
}
o.extend_from_slice(&self.signer_pubkey);
let id = self.signer_id.as_bytes();
o.extend_from_slice(&(id.len() as u32).to_be_bytes());
o.extend_from_slice(id);
o
}
/// Seal a receipt over an already-decoded clip.
///
/// Prefer [`VideoReceipt::seal_decode`], which decodes the codestream here
/// and so cannot be handed a clip that is not what the codestream produces.
pub fn build(
signer: &SigningKey,
signer_id: impl Into<String>,
codestream_hash: [u8; 32],
clip: &DecodedClip,
joules_micro: u64,
energy_provenance: Option<Provenance>,
parent_receipt_hash: Option<[u8; 32]>,
) -> VideoReceipt {
let frames: Vec<FrameHash> = clip
.frames
.iter()
.enumerate()
.map(|(i, f)| frame_hash(i as u64, f))
.collect();
let (h, w) = (clip.h as u32, clip.w as u32);
let leaves: Vec<[u8; 32]> = frames.iter().map(|f| f.hash).collect();
let mut r = VideoReceipt {
codestream_hash,
clip_hash: clip_hash(h, w, &frames),
h,
w,
merkle_root: merkle_root(&leaves),
work_pixel_ops: work_pixel_ops(frames.len() as u64, h as u64, w as u64),
frames,
joules_micro,
energy_provenance,
parent_receipt_hash,
signer_pubkey: signer.verifying_key().to_bytes(),
signer_id: signer_id.into(),
sig: [0u8; 64],
};
r.sig = signer.sign(&r.signing_payload()).to_bytes();
r
}
/// Decode `codestream` and seal a receipt over what it actually produces.
///
/// The identity is hashed from `raw` — the codestream bytes exactly as they
/// arrived — rather than from a re-serialisation of the parsed value, so the
/// hash identifies the artifact a verifier will be handed rather than this
/// process's idea of it.
pub fn seal_decode(
signer: &SigningKey,
signer_id: impl Into<String>,
raw: &[u8],
codestream: &Value,
joules_micro: u64,
energy_provenance: Option<Provenance>,
parent_receipt_hash: Option<[u8; 32]>,
) -> Result<VideoReceipt, &'static str> {
let clip = decode(codestream)?;
let codestream_hash = *blake3::hash(raw).as_bytes();
Ok(Self::build(
signer,
signer_id,
codestream_hash,
&clip,
joules_micro,
energy_provenance,
parent_receipt_hash,
))
}
/// Signature + internal consistency: the Merkle root, the clip commitment
/// and the work figure must all follow from the frame hashes carried here.
///
/// This does **not** prove the frames are what the codestream decodes to —
/// only [`VideoReceipt::verify_reconstruction`] does that.
pub fn verify(&self) -> bool {
let leaves: Vec<[u8; 32]> = self.frames.iter().map(|f| f.hash).collect();
if merkle_root(&leaves) != self.merkle_root {
return false;
}
if clip_hash(self.h, self.w, &self.frames) != self.clip_hash {
return false;
}
if work_pixel_ops(self.frames.len() as u64, self.h as u64, self.w as u64)
!= self.work_pixel_ops
{
return false;
}
for (i, f) in self.frames.iter().enumerate() {
if f.index != i as u64 {
return false;
}
}
// A figure and its acquisition class stand or fall together. An
// unlabelled non-zero figure is a number nobody can compare; a labelled
// zero claims a measurement that did not happen. Both are rejected here
// rather than left to a reader's charity.
match (self.energy_provenance, self.joules_micro) {
(None, 0) => {}
(Some(_), n) if n > 0 => {}
_ => return false,
}
let Ok(k) = VerifyingKey::from_bytes(&self.signer_pubkey) else { return false };
k.verify(&self.signing_payload(), &Signature::from_bytes(&self.sig)).is_ok()
}
/// The independent check: re-decode the codestream and confirm this receipt
/// describes it — identity, geometry, every frame, the clip commitment and
/// the work figure.
///
/// Byte-equality across architectures is what makes this meaningful rather
/// than circular: the caller need not trust the machine that sealed the
/// receipt, because the same bitstream reconstructs the same bytes on any of
/// the proven architectures. `joules_micro` is untouched here — it is not
/// reproducible and is not meant to be.
pub fn verify_reconstruction(&self, raw: &[u8], codestream: &Value) -> bool {
if *blake3::hash(raw).as_bytes() != self.codestream_hash {
return false;
}
let Ok(clip) = decode(codestream) else { return false };
if clip.h as u32 != self.h || clip.w as u32 != self.w {
return false;
}
if clip.frames.len() != self.frames.len() {
return false;
}
for (i, f) in clip.frames.iter().enumerate() {
if frame_hash(i as u64, f) != self.frames[i] {
return false;
}
}
self.verify()
}
pub fn receipt_hash(&self) -> [u8; 32] {
let mut h = blake3::Hasher::new();
h.update(DOMAIN_RECEIPT_ID);
h.update(&self.signing_payload());
h.update(&self.sig);
*h.finalize().as_bytes()
}
/// Microjoules per thousand decoded subpixels, **with** its acquisition
/// class — the only form in which the figure is comparable to another.
/// `None` when unmetered, so an absent measurement can never be read as
/// "zero energy" (SPEC: state what you did, never claim what you did not).
pub fn uj_per_kilopixel_op(&self) -> Option<(f64, Provenance)> {
match (self.energy_provenance, self.work_pixel_ops) {
(Some(p), w) if self.joules_micro > 0 && w > 0 => {
Some((self.joules_micro as f64 * 1000.0 / w as f64, p))
}
_ => None,
}
}
pub fn to_json(&self) -> String {
let hx = |b: &[u8]| b.iter().map(|x| format!("{x:02x}")).collect::<String>();
let objs: Vec<String> =
self.frames.iter().map(|f| format!("\"{}\"", hx(&f.hash))).collect();
let parent = match self.parent_receipt_hash {
Some(h) => format!("\"{}\"", hx(&h)),
None => "null".to_string(),
};
let (rate, prov) = match self.uj_per_kilopixel_op() {
Some((r, p)) => (format!("{r:.6}"), format!("\"{}\"", p.label())),
None => ("null".to_string(), "null".to_string()),
};
format!(
concat!(
"{{\"profile\":\"wai.video.int_motion/receipt\",",
"\"codestream_hash\":\"{}\",\"clip_hash\":\"{}\",",
"\"h\":{},\"w\":{},\"frames\":[{}],\"merkle_root\":\"{}\",",
"\"work_pixel_ops\":{},\"joules_micro\":{},\"uj_per_kilopixel_op\":{},",
"\"energy_provenance\":{},",
"\"parent_receipt_hash\":{},\"signer_pubkey\":\"{}\",",
"\"signer_id\":\"{}\",\"sig\":\"{}\",\"receipt_hash\":\"{}\"}}"
),
hx(&self.codestream_hash),
hx(&self.clip_hash),
self.h,
self.w,
objs.join(","),
hx(&self.merkle_root),
self.work_pixel_ops,
self.joules_micro,
rate,
prov,
parent,
hx(&self.signer_pubkey),
self.signer_id,
hx(&self.sig),
hx(&self.receipt_hash())
)
}
}

View file

@ -0,0 +1,176 @@
//! `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")]
use std::path::PathBuf;
use ed25519_dalek::SigningKey;
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])
}
fn seal(name: &str, uj: u64) -> (VideoReceipt, Vec<u8>, serde_json::Value) {
let bytes = raw(name);
let bs: serde_json::Value = serde_json::from_slice(&bytes).expect("parse");
let prov = (uj > 0).then_some(Provenance::HwShunt);
let r = VideoReceipt::seal_decode(&key(7), "did:example:meter", &bytes, &bs, uj, prov, None)
.expect("seal");
(r, bytes, bs)
}
#[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, Provenance::HwShunt);
}
/// 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(Provenance::HwShunt),
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(Provenance::HwShunt);
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");
}