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.
|
||||
See [`extensions/reconstruction-binding.md`](extensions/reconstruction-binding.md)
|
||||
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
|
||||
the canonical RGB plus the descriptor that pins the reconstruction) — the
|
||||
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")]
|
||||
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
|
||||
// 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
|
||||
|
|
|
|||
Loading…
Reference in a new issue