wai: bind a decode's joules to the decode (C2PA 2.4 companion assertion) #68

Merged
dcharlot merged 1 commit from wai/energy-binding into main 2026-09-03 15:10:02 -04:00

2026-08-29

David Charlot
wai: bind a decode's joules to the decode (C2PA 2.4 companion assertion)
Some checks failed
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 2s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 29s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 14s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 1m58s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 46s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 3m44s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 35s
WAI conformance / cargo-class corpora · windows (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · windows (pull_request) Has been cancelled
C2PA 2.4 added `c2pa.environmental-sustainability`, carrying `energy_kwh` per
ISO/IEC TR 20226:2025. The dominant signed-provenance container in media now has
a slot for joules — and nothing behind it. Verified against the spec text: the
figure is self-declared, `measurementMethod` is optional, and it is not bound to
any reproducible output. A validator can check every signature in a manifest and
still not know where the number came from or what it was measured over.

This fills the slot rather than replacing it: the standard assertion is emitted
for interoperability, with a companion carrying the three things it cannot
express.

The load-bearing one is the binding. `reconstructionHash` is the SHA-256 of the
canonical decoded pixels — the same digest `reconstruction_binding` commits to,
so two assertions in one manifest name the same decode by construction and a
manifest whose digests disagree is self-refuting.

Because a WAI reconstruction is byte-exact on every architecture, a verifier can
re-decode, confirm the figure is about *those* bytes, recompute `workUnits` from
the descriptor rather than trusting it, and re-derive the rate — leaving exactly
one unverifiable term, the joules. That is the point and also the honest limit:
the extension says plainly that a binding does NOT prove the figure. An energy
claim about an output nobody can reproduce is not checkable at all; one about an
output anyone can reproduce is checkable except for a single number, and knowing
which number that is, is the whole difference.

The other two: an acquisition class (`HwShunt`/`ModelBased`/`Estimator`) because
a counter reading and a modelled constant are different claims occupying the
same field in the standard assertion; and a coverage statement, because a
partial reading is not wrong but presenting it as a total is.

Deliberate refusals:

- A zero measurement is rejected, not recorded. A decode that measured 0 uJ did
  not consume nothing; it failed to measure, and the honest artifact is the
  absence of an energy assertion.
- `method` is required, where the standard leaves `measurementMethod` optional —
  which is exactly the field that stays empty in practice.
- `carbon_kgco2e` and `water_litres` are NOT emitted. Deriving them from an
  energy figure and a grid-average factor, then placing them beside a measured
  joule figure in one assertion with no per-field provenance, is precisely how a
  model comes to be read as a measurement.

Integer microjoules are authoritative and `energy_kwh` is derived: one
microjoule is 2.8e-13 kWh, so an image decode lands near 1e-9 kWh, where the
float's trailing digits are conversion artifacts. The first draft of the unit
test asserted that magnitude from memory and was wrong by two orders; it now
asserts against computed arithmetic, and the comment says why.

Verified: 76 wai-rs tests pass under `--features provenance` (6 new), no clippy
warnings in the new file, extension linked from SPEC.md beside its sibling.
2026-08-29 12:07:02 -04:00