wai: bind a decode's joules to the decode (C2PA 2.4 companion assertion) #68
4 changed files with 498 additions and 0 deletions
|
|
@ -251,6 +251,10 @@ private-channel sub-spec: MLS (RFC 9420) group keys + SFrame (RFC 9605)
|
||||||
object encryption, the byte-level framing behind §6.
|
object encryption, the byte-level framing behind §6.
|
||||||
See [`extensions/reconstruction-binding.md`](extensions/reconstruction-binding.md)
|
See [`extensions/reconstruction-binding.md`](extensions/reconstruction-binding.md)
|
||||||
for the reproducible-reconstruction binding: a C2PA / JPEG-Trust hard binding
|
for the reproducible-reconstruction binding: a C2PA / JPEG-Trust hard binding
|
||||||
|
See [`extensions/energy-binding.md`](extensions/energy-binding.md) for the
|
||||||
|
companion energy assertion: the joules a reconstruction cost, bound to that
|
||||||
|
reconstruction, with the acquisition class and coverage C2PA 2.4's
|
||||||
|
`c2pa.environmental-sustainability` has no field for.
|
||||||
over the *deterministically decoded pixels* of a neural-coded asset (SHA-256 over
|
over the *deterministically decoded pixels* of a neural-coded asset (SHA-256 over
|
||||||
the canonical RGB plus the descriptor that pins the reconstruction) — the
|
the canonical RGB plus the descriptor that pins the reconstruction) — the
|
||||||
reconstruct-side companion to `provenance`, applicable to `wai.image.jpegai` and
|
reconstruct-side companion to `provenance`, applicable to `wai.image.jpegai` and
|
||||||
|
|
|
||||||
102
wai/extensions/energy-binding.md
Normal file
102
wai/extensions/energy-binding.md
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
# WAI Extension: Energy Binding
|
||||||
|
|
||||||
|
> Status: Draft. Binds the **energy a reconstruction cost** to *that*
|
||||||
|
> reconstruction, as a companion to C2PA 2.4's
|
||||||
|
> `c2pa.environmental-sustainability` assertion — supplying the acquisition
|
||||||
|
> class, the coverage, and the outcome binding the standard assertion has no
|
||||||
|
> field for. Reference impl: the `energy_binding` module of `wai-rs`
|
||||||
|
> (feature `provenance`). Companion to
|
||||||
|
> [Reproducible-Reconstruction Binding](reconstruction-binding.md) and the
|
||||||
|
> acquisition protocol in [Energy Measurement](energy-measurement.md).
|
||||||
|
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||||||
|
|
||||||
|
## 1. Why a companion assertion
|
||||||
|
|
||||||
|
C2PA 2.4 defines `c2pa.environmental-sustainability`, carrying `energy_kwh`
|
||||||
|
(per ISO/IEC TR 20226:2025) alongside carbon and water figures. The dominant
|
||||||
|
signed-provenance container in media now has a place to put joules.
|
||||||
|
|
||||||
|
Three properties it does not have, and this extension supplies:
|
||||||
|
|
||||||
|
1. **It is self-declared.** `measurementMethod` is optional, and an
|
||||||
|
unattributed number cannot be compared with any other number. In practice
|
||||||
|
the optional field is the one that stays empty.
|
||||||
|
2. **It carries no acquisition class.** A hardware counter reading and a
|
||||||
|
modelled constant occupy the same field and are indistinguishable to a
|
||||||
|
reader, though they are different claims about the world.
|
||||||
|
3. **It is not bound to any reproducible output.** The assertion is about an
|
||||||
|
asset; a validator can check every signature in a manifest and still not
|
||||||
|
know what the figure was measured *over*.
|
||||||
|
|
||||||
|
The third is the load-bearing one. A signature proves who said a number, never
|
||||||
|
that the number is about anything checkable.
|
||||||
|
|
||||||
|
## 2. What the binding is
|
||||||
|
|
||||||
|
An energy binding is the tuple
|
||||||
|
|
||||||
|
```
|
||||||
|
{ reconstructionHash, alg, microjoules, acquisition, coverage, workUnits, method }
|
||||||
|
```
|
||||||
|
|
||||||
|
carried as a third-party C2PA assertion under the reverse-domain label
|
||||||
|
`science.transaction.wai.energy.reconstruction`, **alongside** — never instead
|
||||||
|
of — the standard `c2pa.environmental-sustainability` assertion.
|
||||||
|
|
||||||
|
`reconstructionHash` is the SHA-256 of the canonical decoded pixels, the same
|
||||||
|
digest [reconstruction-binding](reconstruction-binding.md) commits to. Both
|
||||||
|
assertions in one manifest therefore name the same decode by construction, and
|
||||||
|
a manifest whose two digests disagree is self-refuting.
|
||||||
|
|
||||||
|
## 3. What this makes checkable, and what it does not
|
||||||
|
|
||||||
|
Because a WAI reconstruction is byte-exact and reproducible on every
|
||||||
|
architecture, a verifier holding the codestream can:
|
||||||
|
|
||||||
|
- re-decode under the pinned descriptor,
|
||||||
|
- hash the pixels and confirm the energy figure is about **those** bytes,
|
||||||
|
- recompute `workUnits` from the descriptor rather than trusting it,
|
||||||
|
- and re-derive the rate.
|
||||||
|
|
||||||
|
What remains is **exactly one** unverifiable term: the joules. That is the
|
||||||
|
point of the construction, and the honest limit of it. An energy claim about an
|
||||||
|
output nobody can reproduce is not checkable at all. An energy claim about an
|
||||||
|
output anyone can reproduce is checkable except for one number — and knowing
|
||||||
|
precisely which number is the difference between a measurement and an
|
||||||
|
assertion.
|
||||||
|
|
||||||
|
An implementation **MUST NOT** describe an energy binding as proving the energy
|
||||||
|
figure. It binds the figure to a reproducible outcome; it does not attest the
|
||||||
|
measurement.
|
||||||
|
|
||||||
|
## 4. Normative rules
|
||||||
|
|
||||||
|
- The binding **MUST** carry an acquisition class (`HwShunt` / `ModelBased` /
|
||||||
|
`Estimator`, per [energy-measurement](energy-measurement.md) §2) and a
|
||||||
|
coverage statement (`Whole` / `Partial`, §5 there).
|
||||||
|
- `method` **MUST** be present and name the published protocol the figure was
|
||||||
|
obtained under. This is deliberately stricter than the standard assertion's
|
||||||
|
optional `measurementMethod`.
|
||||||
|
- A figure of zero **MUST NOT** be recorded. A reconstruction that measured
|
||||||
|
0 µJ did not consume nothing; it failed to measure, and the honest artifact is
|
||||||
|
the absence of an energy assertion rather than a zero in one.
|
||||||
|
- `microjoules` is an integer and is authoritative. `energy_kwh` in the standard
|
||||||
|
assertion **MUST** be treated as a derived interop value: one microjoule is
|
||||||
|
2.8 × 10⁻¹³ kWh, so a whole image decode lands near 10⁻⁹ kWh, where the
|
||||||
|
trailing digits of the float are artifacts of the conversion rather than of
|
||||||
|
the measurement.
|
||||||
|
- An implementation **MUST NOT** populate `carbon_kgco2e` or `water_litres`
|
||||||
|
from an energy figure and a grid-average factor while presenting the result
|
||||||
|
as measured. Those are modelled quantities; emitting them beside a measured
|
||||||
|
joule figure, in the same assertion, with no per-field provenance, is how a
|
||||||
|
model comes to be read as a measurement.
|
||||||
|
- A `Partial` figure **MUST NOT** be presented as the reconstruction's total.
|
||||||
|
|
||||||
|
## 5. Relationship to the standard assertion
|
||||||
|
|
||||||
|
Interoperability is the reason both are emitted. A validator that understands
|
||||||
|
only C2PA 2.4 reads `energy_kwh` and a populated `measurementMethod`, which is
|
||||||
|
already better than the common case. A validator that understands this
|
||||||
|
extension additionally learns how the figure was acquired, how much of the work
|
||||||
|
it covers, and which reconstruction it describes — and can check the last of
|
||||||
|
those itself.
|
||||||
385
wai/wai-rs/src/energy_binding.rs
Normal file
385
wai/wai-rs/src/energy_binding.rs
Normal file
|
|
@ -0,0 +1,385 @@
|
||||||
|
//! Energy binding — the joules a reconstruction cost, tied to *that* reconstruction.
|
||||||
|
//!
|
||||||
|
//! # Why this exists
|
||||||
|
//!
|
||||||
|
//! C2PA 2.4 added a standard `c2pa.environmental-sustainability` assertion carrying
|
||||||
|
//! `energy_kwh` (per ISO/IEC TR 20226:2025) alongside carbon and water figures. The
|
||||||
|
//! dominant signed-provenance container in media now has a slot for joules.
|
||||||
|
//!
|
||||||
|
//! What it does not have is anything behind that slot. The figure is **self-declared**,
|
||||||
|
//! its `measurementMethod` is **optional**, and — the part that matters most — it is not
|
||||||
|
//! bound to any reproducible output. A manifest can assert a number about an asset, and
|
||||||
|
//! a validator that checks every signature in the manifest still learns nothing about
|
||||||
|
//! where the number came from or what it was measured over.
|
||||||
|
//!
|
||||||
|
//! This module fills the slot rather than replacing it. It emits the standard assertion
|
||||||
|
//! for interoperability, and carries a companion assertion with the three things the
|
||||||
|
//! standard one omits.
|
||||||
|
//!
|
||||||
|
//! # What the companion assertion adds
|
||||||
|
//!
|
||||||
|
//! - **A binding to the reconstruction.** The figure carries the SHA-256 of the decoded
|
||||||
|
//! pixels it was measured over — the same digest
|
||||||
|
//! [`crate::reconstruction_binding::ReconstructionBinding`] commits to. This is the
|
||||||
|
//! whole point: because a WAI reconstruction is byte-exact and reproducible on every
|
||||||
|
//! architecture, a verifier can re-decode, confirm it holds the same pixels the figure
|
||||||
|
//! is about, and be left with **exactly one** unverifiable term — the joules. An
|
||||||
|
//! energy claim about an output nobody can reproduce is not checkable at all; an
|
||||||
|
//! energy claim about an output anyone can reproduce is checkable except for one
|
||||||
|
//! number, and knowing precisely which number that is, is the difference.
|
||||||
|
//! - **An acquisition class.** `HwShunt` / `ModelBased` / `Estimator` — the family's
|
||||||
|
//! ladder, stated rather than implied. A counter reading and a modelled constant are
|
||||||
|
//! not the same claim, and the standard assertion has no way to tell them apart.
|
||||||
|
//! - **A coverage statement.** Whether the figure accounts for the whole reconstruction
|
||||||
|
//! or part of it. A partial reading is not wrong; presenting it as a total is.
|
||||||
|
//!
|
||||||
|
//! # On units
|
||||||
|
//!
|
||||||
|
//! The truth carried here is **integer microjoules**. The standard assertion wants
|
||||||
|
//! `energy_kwh`, and one microjoule is 2.8e-13 kWh, so a whole image decode lands
|
||||||
|
//! around 1e-9 kWh — eleven orders of magnitude below the unit's natural scale, where
|
||||||
|
//! the trailing digits are an artifact of the conversion rather than of the
|
||||||
|
//! measurement. So `energy_kwh` is emitted as a *derived* interop value and labelled as
|
||||||
|
//! such, while the integer stays authoritative. A verifier should read the integer.
|
||||||
|
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
/// How the energy figure was acquired — the family's provenance ladder.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Acquisition {
|
||||||
|
/// Read from a hardware energy interface (RAPL / NVML / SMC / IOReport / shunt).
|
||||||
|
HwShunt,
|
||||||
|
/// Computed from a calibrated per-operation model. A real number, honestly derived.
|
||||||
|
ModelBased,
|
||||||
|
/// A coarse constant or load estimator.
|
||||||
|
Estimator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Acquisition {
|
||||||
|
/// The wire label.
|
||||||
|
pub fn label(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Acquisition::HwShunt => "HwShunt",
|
||||||
|
Acquisition::ModelBased => "ModelBased",
|
||||||
|
Acquisition::Estimator => "Estimator",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How much of the reconstruction the figure accounts for.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Coverage {
|
||||||
|
/// The figure covers the whole reconstruction.
|
||||||
|
Whole,
|
||||||
|
/// It covers part of it; the remainder is unmeasured, which is not the same as zero.
|
||||||
|
Partial,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Coverage {
|
||||||
|
/// The wire label.
|
||||||
|
pub fn label(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Coverage::Whole => "Whole",
|
||||||
|
Coverage::Partial => "Partial",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors from constructing a binding.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum EnergyBindingError {
|
||||||
|
/// A measured figure of zero is a failed measurement, not free work.
|
||||||
|
ZeroMeasurement,
|
||||||
|
/// The work unit must be recomputable and non-zero to express a rate.
|
||||||
|
ZeroWork,
|
||||||
|
/// A figure must name the protocol it was obtained under.
|
||||||
|
NoMethod,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Display for EnergyBindingError {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
match self {
|
||||||
|
EnergyBindingError::ZeroMeasurement => {
|
||||||
|
write!(f, "a measured figure of 0 uJ is a failed measurement, not free work")
|
||||||
|
}
|
||||||
|
EnergyBindingError::ZeroWork => write!(f, "work unit must be non-zero"),
|
||||||
|
EnergyBindingError::NoMethod => {
|
||||||
|
write!(f, "a figure must name the acquisition protocol it was obtained under")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for EnergyBindingError {}
|
||||||
|
|
||||||
|
/// The joules a reconstruction cost, bound to the reconstruction itself.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct EnergyBinding {
|
||||||
|
/// SHA-256 of the canonical decoded pixels this figure was measured over — the same
|
||||||
|
/// digest the reconstruction binding commits to.
|
||||||
|
pub reconstruction_hash: [u8; 32],
|
||||||
|
/// The measured energy, integer microjoules. Authoritative; `energy_kwh` in the
|
||||||
|
/// standard assertion is derived from this.
|
||||||
|
pub microjoules: u64,
|
||||||
|
/// How the figure was acquired.
|
||||||
|
pub acquisition: Acquisition,
|
||||||
|
/// How much of the reconstruction it accounts for.
|
||||||
|
pub coverage: Coverage,
|
||||||
|
/// The recomputable work unit the rate is expressed against (for an image, decoded
|
||||||
|
/// subpixels: `width * height * 3`). A verifier derives this from the reconstruction
|
||||||
|
/// descriptor rather than trusting it.
|
||||||
|
pub work_units: u64,
|
||||||
|
/// The published protocol the figure was obtained under — for WAI decodes,
|
||||||
|
/// `extensions/energy-measurement.md`. Required: an unattributed number is not a
|
||||||
|
/// measurement, and the standard assertion's optional `measurementMethod` is exactly
|
||||||
|
/// the field everyone leaves empty.
|
||||||
|
pub method: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnergyBinding {
|
||||||
|
/// The companion assertion's label (reverse-domain, as C2PA requires for third-party
|
||||||
|
/// assertions). Carried *alongside* `c2pa.environmental-sustainability`, never
|
||||||
|
/// instead of it.
|
||||||
|
pub const C2PA_LABEL: &'static str = "science.transaction.wai.energy.reconstruction";
|
||||||
|
|
||||||
|
/// The standard C2PA 2.4 assertion this one accompanies.
|
||||||
|
pub const C2PA_STANDARD_LABEL: &'static str = "c2pa.environmental-sustainability";
|
||||||
|
|
||||||
|
/// Bind a measured figure to a reconstruction.
|
||||||
|
///
|
||||||
|
/// Rejects a zero measurement rather than recording it: a decode that measured 0 µJ
|
||||||
|
/// did not consume nothing, it failed to measure, and the honest artifact in that
|
||||||
|
/// case is no energy assertion at all.
|
||||||
|
pub fn bind(
|
||||||
|
reconstruction_hash: [u8; 32],
|
||||||
|
microjoules: u64,
|
||||||
|
acquisition: Acquisition,
|
||||||
|
coverage: Coverage,
|
||||||
|
work_units: u64,
|
||||||
|
method: impl Into<String>,
|
||||||
|
) -> Result<Self, EnergyBindingError> {
|
||||||
|
if microjoules == 0 {
|
||||||
|
return Err(EnergyBindingError::ZeroMeasurement);
|
||||||
|
}
|
||||||
|
if work_units == 0 {
|
||||||
|
return Err(EnergyBindingError::ZeroWork);
|
||||||
|
}
|
||||||
|
let method = method.into();
|
||||||
|
if method.trim().is_empty() {
|
||||||
|
return Err(EnergyBindingError::NoMethod);
|
||||||
|
}
|
||||||
|
Ok(EnergyBinding {
|
||||||
|
reconstruction_hash,
|
||||||
|
microjoules,
|
||||||
|
acquisition,
|
||||||
|
coverage,
|
||||||
|
work_units,
|
||||||
|
method,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bind directly to decoded pixels, hashing them the same way the reconstruction
|
||||||
|
/// binding does, so the two assertions in one manifest necessarily agree.
|
||||||
|
pub fn bind_pixels(
|
||||||
|
rgb: &[u8],
|
||||||
|
microjoules: u64,
|
||||||
|
acquisition: Acquisition,
|
||||||
|
coverage: Coverage,
|
||||||
|
method: impl Into<String>,
|
||||||
|
) -> Result<Self, EnergyBindingError> {
|
||||||
|
let hash: [u8; 32] = Sha256::digest(rgb).into();
|
||||||
|
Self::bind(hash, microjoules, acquisition, coverage, rgb.len() as u64, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this figure describes the pixels a verifier just decoded.
|
||||||
|
///
|
||||||
|
/// This is the check that makes the assertion worth carrying: re-decode, hash, and
|
||||||
|
/// confirm the energy claim is about *these* bytes and not some other run.
|
||||||
|
pub fn describes(&self, rgb: &[u8]) -> bool {
|
||||||
|
<[u8; 32]>::from(Sha256::digest(rgb)) == self.reconstruction_hash
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Microjoules per thousand work units — the comparable rate, with its acquisition
|
||||||
|
/// class, because a rate without one cannot be compared to another.
|
||||||
|
pub fn uj_per_kilo_unit(&self) -> (f64, Acquisition) {
|
||||||
|
(self.microjoules as f64 * 1000.0 / self.work_units as f64, self.acquisition)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The derived `energy_kwh` for the standard assertion.
|
||||||
|
///
|
||||||
|
/// Lossy by construction — a microjoule-scale decode lands around 1e-13 kWh, where
|
||||||
|
/// the trailing digits come from the conversion rather than the measurement. Emitted
|
||||||
|
/// for interoperability; [`Self::microjoules`] remains authoritative.
|
||||||
|
pub fn energy_kwh_derived(&self) -> f64 {
|
||||||
|
self.microjoules as f64 / 3.6e12
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lowercase-hex of the reconstruction digest.
|
||||||
|
pub fn hash_hex(&self) -> String {
|
||||||
|
let mut s = String::with_capacity(64);
|
||||||
|
for b in self.reconstruction_hash {
|
||||||
|
use std::fmt::Write;
|
||||||
|
let _ = write!(s, "{b:02x}");
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The standard `c2pa.environmental-sustainability` assertion body, for interop.
|
||||||
|
///
|
||||||
|
/// `measurementMethod` is always populated — it is optional in the standard, and an
|
||||||
|
/// unattributed figure is the failure this module exists to avoid. Carbon and water
|
||||||
|
/// are deliberately absent: this measures energy, and inventing the other two from a
|
||||||
|
/// grid-average factor would be a modelled figure wearing a measured one's clothes.
|
||||||
|
pub fn to_standard_assertion_json(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"{{\"energy_kwh\":{:e},\"measurementMethod\":{}}}",
|
||||||
|
self.energy_kwh_derived(),
|
||||||
|
json_str(&self.method)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The companion assertion body: everything the standard one cannot express.
|
||||||
|
pub fn to_json(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"{{\"reconstructionHash\":\"{}\",\"alg\":\"sha256\",\"microjoules\":{},\
|
||||||
|
\"acquisition\":\"{}\",\"coverage\":\"{}\",\"workUnits\":{},\"method\":{}}}",
|
||||||
|
self.hash_hex(),
|
||||||
|
self.microjoules,
|
||||||
|
self.acquisition.label(),
|
||||||
|
self.coverage.label(),
|
||||||
|
self.work_units,
|
||||||
|
json_str(&self.method)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_str(s: &str) -> String {
|
||||||
|
let mut o = String::with_capacity(s.len() + 2);
|
||||||
|
o.push('"');
|
||||||
|
for c in s.chars() {
|
||||||
|
match c {
|
||||||
|
'"' => o.push_str("\\\""),
|
||||||
|
'\\' => o.push_str("\\\\"),
|
||||||
|
'\n' => o.push_str("\\n"),
|
||||||
|
c => o.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o.push('"');
|
||||||
|
o
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const METHOD: &str = "https://transaction.science/wai/extensions/energy-measurement";
|
||||||
|
|
||||||
|
fn pixels() -> Vec<u8> {
|
||||||
|
(0..(4 * 4 * 3u32)).map(|i| (i % 251) as u8).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The point of the module: the figure is about pixels a verifier can reproduce.
|
||||||
|
#[test]
|
||||||
|
fn the_figure_is_bound_to_the_pixels_it_was_measured_over() {
|
||||||
|
let rgb = pixels();
|
||||||
|
let b = EnergyBinding::bind_pixels(&rgb, 10_565, Acquisition::HwShunt, Coverage::Whole, METHOD)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(b.describes(&rgb), "must describe the pixels it was bound to");
|
||||||
|
|
||||||
|
// A different reconstruction is a different claim.
|
||||||
|
let mut other = rgb.clone();
|
||||||
|
other[0] ^= 1;
|
||||||
|
assert!(!b.describes(&other), "must not vouch for pixels it never saw");
|
||||||
|
|
||||||
|
// And it agrees with the reconstruction binding's own digest, so two
|
||||||
|
// assertions in one manifest cannot disagree about which decode is meant.
|
||||||
|
use crate::reconstruction_binding::{ReconstructionBinding, ReconstructionDescriptor};
|
||||||
|
let rb = ReconstructionBinding::bind(
|
||||||
|
&rgb,
|
||||||
|
ReconstructionDescriptor {
|
||||||
|
decoder_id: 0,
|
||||||
|
profile: "p".into(),
|
||||||
|
level: "l".into(),
|
||||||
|
width: 4,
|
||||||
|
height: 4,
|
||||||
|
format: "RGB8".into(),
|
||||||
|
color_space: "bt709".into(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert_eq!(rb.hash_hex(), b.hash_hex(), "both assertions must name the same decode");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A zero measurement is a failed measurement, not free work — so it cannot be
|
||||||
|
/// recorded as an energy claim at all.
|
||||||
|
#[test]
|
||||||
|
fn a_zero_measurement_is_refused() {
|
||||||
|
let rgb = pixels();
|
||||||
|
assert_eq!(
|
||||||
|
EnergyBinding::bind_pixels(&rgb, 0, Acquisition::HwShunt, Coverage::Whole, METHOD),
|
||||||
|
Err(EnergyBindingError::ZeroMeasurement)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The standard assertion leaves `measurementMethod` optional, which is exactly the
|
||||||
|
/// field that ends up empty. Here it cannot be.
|
||||||
|
#[test]
|
||||||
|
fn a_figure_must_name_its_method() {
|
||||||
|
let rgb = pixels();
|
||||||
|
assert_eq!(
|
||||||
|
EnergyBinding::bind_pixels(&rgb, 10, Acquisition::Estimator, Coverage::Whole, " "),
|
||||||
|
Err(EnergyBindingError::NoMethod)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Acquisition and coverage survive into the wire, because a bare number is not
|
||||||
|
/// comparable to another bare number.
|
||||||
|
#[test]
|
||||||
|
fn the_wire_carries_what_the_standard_assertion_cannot() {
|
||||||
|
let rgb = pixels();
|
||||||
|
let b = EnergyBinding::bind_pixels(&rgb, 2_469, Acquisition::ModelBased, Coverage::Partial, METHOD)
|
||||||
|
.unwrap();
|
||||||
|
let j = b.to_json();
|
||||||
|
assert!(j.contains("\"acquisition\":\"ModelBased\""));
|
||||||
|
assert!(j.contains("\"coverage\":\"Partial\""));
|
||||||
|
assert!(j.contains("\"microjoules\":2469"));
|
||||||
|
assert!(j.contains("reconstructionHash"));
|
||||||
|
|
||||||
|
// The standard assertion always names its method, and carries no carbon or
|
||||||
|
// water figure this module did not measure.
|
||||||
|
let s = b.to_standard_assertion_json();
|
||||||
|
assert!(s.contains("measurementMethod"));
|
||||||
|
assert!(!s.contains("carbon"), "must not invent a carbon figure: {s}");
|
||||||
|
assert!(!s.contains("water"), "must not invent a water figure: {s}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// kWh is a derived interop value; the integer is the truth. Round-tripping is
|
||||||
|
/// lossy at this scale and the docs say so — this pins that it really is.
|
||||||
|
#[test]
|
||||||
|
fn kwh_is_derived_and_lossy_while_the_integer_is_exact() {
|
||||||
|
let rgb = pixels();
|
||||||
|
let b = EnergyBinding::bind_pixels(&rgb, 10_565, Acquisition::HwShunt, Coverage::Whole, METHOD)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(b.microjoules, 10_565, "the integer is carried exactly");
|
||||||
|
|
||||||
|
let kwh = b.energy_kwh_derived();
|
||||||
|
let back = (kwh * 3.6e12).round() as u64;
|
||||||
|
assert_eq!(back, 10_565, "this magnitude survives the round trip");
|
||||||
|
// 10_565 uJ is 2.93e-9 kWh. Asserted against computed arithmetic rather than
|
||||||
|
// an eyeballed exponent — the first draft of this test guessed 1e-11 and was
|
||||||
|
// wrong by two orders of magnitude, which is the entire hazard of the unit.
|
||||||
|
assert!((kwh - 10_565.0 / 3.6e12).abs() < 1e-20);
|
||||||
|
assert!(kwh < 1e-8 && kwh > 1e-10, "an image decode sits around 1e-9 kWh, got {kwh:e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The rate is only meaningful with its acquisition class attached.
|
||||||
|
#[test]
|
||||||
|
fn a_rate_carries_its_acquisition_class() {
|
||||||
|
let rgb = pixels(); // 48 bytes of work
|
||||||
|
let b = EnergyBinding::bind_pixels(&rgb, 4_800, Acquisition::HwShunt, Coverage::Whole, METHOD)
|
||||||
|
.unwrap();
|
||||||
|
let (rate, acq) = b.uj_per_kilo_unit();
|
||||||
|
assert_eq!(acq, Acquisition::HwShunt);
|
||||||
|
assert!((rate - 100_000.0).abs() < 1e-6, "4800 uJ over 48 units = 100 uJ/unit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -197,6 +197,13 @@ pub mod provenance;
|
||||||
#[cfg(feature = "provenance")]
|
#[cfg(feature = "provenance")]
|
||||||
pub mod reconstruction_binding;
|
pub mod reconstruction_binding;
|
||||||
|
|
||||||
|
// The joules a reconstruction cost, bound to that reconstruction. Fills the slot
|
||||||
|
// C2PA 2.4's `c2pa.environmental-sustainability` opened but left self-declared:
|
||||||
|
// same digest as the reconstruction binding, plus the acquisition class and
|
||||||
|
// coverage the standard assertion cannot express (extensions/energy-binding.md).
|
||||||
|
#[cfg(feature = "provenance")]
|
||||||
|
pub mod energy_binding;
|
||||||
|
|
||||||
// wai.world.model — generative-world transport: a signed, replay-equivalent action
|
// wai.world.model — generative-world transport: a signed, replay-equivalent action
|
||||||
// trace (the world CausalOp log) + per-frame provenance. The wire carries the
|
// trace (the world CausalOp log) + per-frame provenance. The wire carries the
|
||||||
// action trace + a declared model_id, not the pixels; the sink renders. The neural
|
// action trace + a declared model_id, not the pixels; the sink renders. The neural
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue