All checks were successful
energy meters on real hardware / RAPL and NVML on jetson-hub (pull_request) Successful in 38s
energy meters on real hardware / sink decode table · RAPL on jetson-hub (NVML surveyed) (pull_request) Successful in 2m45s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 7s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m1s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m13s
standards workspaces build + test / proof-rs (proof/conformance/v1/joulespec.toml, proof-conformance, proof/proof-rs, proof-rs, linux, true) (pull_request) Successful in 1m27s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, -p sandbox-tee --features amd-kds,intel-pcs, sandbox-rs, linux) (pull_request) Successful in 2m7s
energy meters on real hardware / sink decode table · per-task energy and the GPU rail on macOS (pull_request) Successful in 3m17s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 4m56s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 5m40s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m1s
standards workspaces build + test / joulecontract-rs (joulecontract/joulecontract-rs, joulecontract-rs, linux-arm64, true) (pull_request) Successful in 2m48s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 15m55s
Since sandbox-meter's RAPL probe falls back to the energy-status MSRs where powercap lists no package zone, everything built on it reads such a host: PowerMeter (`rapl-msr`), and sandbox-exec-microvm's RaplMeter, which is that probe and which sandbox-proof's S5 proof meters with. Three places still assumed powercap. - sandbox-tee's attested_efficiency example named only `rapl-powercap` as a CPU counter, so on an MSR host it attested the reading as `Estimator`. Both probes are the same hardware counter: `rapl-msr` now attests as CounterClass::Rapl and seals HwShunt, as `rapl-powercap` does. Its test accepts either probe for the class, and a new test pins every probe name to its class. - sandbox-proof's accelerator test read its oracle from powercap only, so where the meter read the MSRs the oracle had no package and the test panicked. The oracle (oracle_package_uj_at, independent of the crate) now reads the powercap package zones where any is listed (package-<n>, or package-<n>-die-<m>), and otherwise each RAPL domain's energy-status MSR on its first CPU: a die of a package on Intel, a package on AMD and Hygon, its 32-bit count at its own unit, wrapping at 2^32 counts. A new test drives it over fake files: two dies of one Intel package, an AMD package whose CPUs list several die_ids, a zero unit, and a powercap die zone preferred. - sandbox-exec-microvm's rapl_hardware oracle watched only powercap, so where RaplMeter opened on the MSRs the oracle called the host Unreadable; and the MSR meter's test bracketed its figure with powercap zones a host without powercap does not have. The oracle now watches and brackets with its own MSR reads there, scoped the same way, and accepts die zones as packages. RaplMeter's docs say what it reads. On two bare-metal servers with no powercap zone (a one-socket Xeon E-2278G, two Xeon Silver 4215R sockets), as root: the accelerator span read rapl-msr at a ratio of 1.000 and 0.999 to the oracle around the call; both microvm hardware tests sat inside the oracle's MSR bracket (RaplMeter 8.9 J and 23.5 J over a 300 ms burn); the example attested `rapl` and sealed HwShunt. As an unprivileged user every counter is Unavailable, the figures are estimates, and every test passes; as that user with the msr group and CAP_SYS_RAWIO (systemd-run), the results match root's. Linux x86_64: sandbox-proof, sandbox-tee (default and amd-kds,intel-pcs) and sandbox-exec-microvm tests pass, `cargo check --workspace --all-targets` is clean, clippy -D warnings is clean for the three crates. macOS: the CI step for sandbox-proof passes with its grep, sandbox-tee passes, clippy is clean for sandbox-meter, sandbox-proof, sandbox-tee and sandbox-exec-microvm. Co-Authored-By: Claude Opus 5.5 <[email protected]>
225 lines
11 KiB
Rust
225 lines
11 KiB
Rust
//! End-to-end: a small task runs here, this host's meter reads its energy, the
|
|
//! figure is attested inside a TEE, committed into an `efficiency/1` receipt,
|
|
//! and then independently re-verified from scratch — deriving the trust level,
|
|
//! never asserting it. This is the whole frontier composed in one runnable flow.
|
|
//!
|
|
//! cargo run -p sandbox-tee --example attested_efficiency
|
|
//!
|
|
//! **What the figure is.** The task is a bounded SHA-512 chain on this thread,
|
|
//! metered by [`PowerMeter::detect_in_process`] (the work runs on this
|
|
//! process's own threads). The attested counter class and the receipt's grade
|
|
//! both follow how the figure was obtained:
|
|
//!
|
|
//! - where a vendor counter read this process's CPU work over the span (the
|
|
//! kernel's per-process count on macOS, a RAPL package on Linux, IOReport's
|
|
//! CPU rails on Apple Silicon where they are live), the statement names that
|
|
//! counter's class and the receipt seals the reading, `HwShunt`. RAPL is one
|
|
//! class however it is read: through powercap (`rapl-powercap`) or, on a
|
|
//! kernel without the powercap RAPL driver, through the energy-status MSRs
|
|
//! (`rapl-msr`) — the same hardware counter, a reading while it is live;
|
|
//! - where none did (no live counter, or one that did not advance over the
|
|
//! span), the figure is the meter's estimate: attested as
|
|
//! [`CounterClass::Estimator`] and sealed `Estimator`.
|
|
//!
|
|
//! Either way the derived trust level is `IntegrityAttested`: a software-grounded
|
|
//! report binds the figure, as it was obtained, to a measured environment. What
|
|
//! the figure itself is — a reading or an estimate — is the counter class the
|
|
//! report binds, printed beside it.
|
|
|
|
use ed25519_dalek::SigningKey;
|
|
use jcp_efficiency::{verify as verify_efficiency, Efficiency, EfficiencyReceipt};
|
|
use jcp_schema::{ContentId, Energy};
|
|
use sandbox_meter::{Measurement, PowerMeter, Provenance};
|
|
use sandbox_schema::DeviceClass;
|
|
use sandbox_tee::energy::{
|
|
attest_energy, verify_attested_energy, AttestTrust, AttestedEnergy, CounterClass, EnergyStatement,
|
|
};
|
|
use sandbox_tee::provider::LocalSnpProvider;
|
|
use sandbox_tee::Grounding;
|
|
use sha2::{Digest, Sha512};
|
|
|
|
/// Links in the task's SHA-512 chain: a fixed amount of real CPU work, some
|
|
/// hundreds of milliseconds in a debug build and a few in a release one. A
|
|
/// span too short for the counter to advance over it is not a reading, and
|
|
/// comes back as the meter's estimate.
|
|
pub const CHAIN_LINKS: u64 = 20_000;
|
|
|
|
/// The task: a SHA-512 chain of [`CHAIN_LINKS`] links from a fixed seed. Its
|
|
/// output is the last link, so the work cannot be optimised away.
|
|
pub fn task() -> [u8; 64] {
|
|
let mut link = [0x5au8; 64];
|
|
for _ in 0..CHAIN_LINKS {
|
|
let next = Sha512::digest(link);
|
|
link.copy_from_slice(&next);
|
|
}
|
|
link
|
|
}
|
|
|
|
/// The counter class of the probe among `sources` (a [`PowerMeter`]'s live
|
|
/// hardware probes) that reads CPU energy: only a known one is named. `None`
|
|
/// for a probe set with no CPU counter this example can name. RAPL read
|
|
/// through powercap and RAPL read through the MSRs are the same counter, so
|
|
/// both are [`CounterClass::Rapl`].
|
|
pub fn cpu_counter(sources: &[&str]) -> Option<CounterClass> {
|
|
sources.iter().find_map(|s| match *s {
|
|
"process-energy" => Some(CounterClass::ProcessEnergy),
|
|
"rapl-powercap" | "rapl-msr" => Some(CounterClass::Rapl),
|
|
"ioreport-energy-model" => Some(CounterClass::IoReport),
|
|
_ => None,
|
|
})
|
|
}
|
|
|
|
/// The figure to attest for span `m` on `meter`, and the class that obtained
|
|
/// it. A hardware reading of the CPU work — the span's `HwShunt` CPU records,
|
|
/// from a counter this example can name — is attested under that counter's
|
|
/// class and sealed as a reading. Anything else is the meter's estimate of the
|
|
/// span ([`PowerMeter::estimate`]): attested as `Estimator`, sealed as one.
|
|
pub fn figure(meter: &PowerMeter, m: &Measurement) -> (CounterClass, Energy) {
|
|
if m.reads_cpu() {
|
|
if let Some(class) = cpu_counter(&meter.sources()) {
|
|
let cpu_uj: u64 = m
|
|
.devices
|
|
.iter()
|
|
.filter(|d| d.class == DeviceClass::Cpu && d.provenance == Provenance::HwShunt)
|
|
.filter_map(|d| d.measured_uj)
|
|
.sum();
|
|
return (class, Energy::measured(cpu_uj));
|
|
}
|
|
}
|
|
(CounterClass::Estimator, Energy::estimator(meter.estimate(m.wall).total_uj()))
|
|
}
|
|
|
|
/// One run of the composed flow.
|
|
pub struct Run {
|
|
/// The meter's live hardware probes (empty: the estimator alone).
|
|
pub sources: Vec<&'static str>,
|
|
/// The span over the task, as the meter recorded it.
|
|
pub measurement: Measurement,
|
|
/// The attested statement and its report.
|
|
pub attested: AttestedEnergy,
|
|
/// The `efficiency/1` receipt, as a relying party verified it.
|
|
pub receipt: EfficiencyReceipt,
|
|
/// The trust level derived from the attestation.
|
|
pub trust: AttestTrust,
|
|
/// The task's output.
|
|
pub output: [u8; 64],
|
|
/// Whether a tampered joule figure failed attestation verification.
|
|
pub tamper_rejected: bool,
|
|
}
|
|
|
|
/// Run the task, meter it, attest the figure, seal the receipt, and verify the
|
|
/// composition from scratch.
|
|
pub fn run() -> Run {
|
|
// ── 1. The task runs here, metered as it runs.
|
|
let mut meter = PowerMeter::detect_in_process();
|
|
let sources = meter.sources();
|
|
let (output, measurement) = meter.measure(task);
|
|
let (counter, energy) = figure(&meter, &measurement);
|
|
let window_ns = u64::try_from(measurement.wall.as_nanos()).unwrap_or(u64::MAX);
|
|
|
|
// ── 2. A verifier issues a fresh nonce; we attest the figure inside a TEE,
|
|
// under the class that obtained it. The LocalSnpProvider mints a real
|
|
// ECDSA-P384 report — software-grounded.
|
|
let nonce = [0x5au8; 32];
|
|
let measurement_digest = [0x11u8; 48];
|
|
let provider = LocalSnpProvider::new(measurement_digest, 7);
|
|
let statement = EnergyStatement { counter, energy_uj: energy.uj(), window_ns, nonce };
|
|
let attested = attest_energy(statement, &provider).expect("attest the figure");
|
|
|
|
// ── 3. Seal the efficiency/1 receipt at the figure's own grade, committing
|
|
// to the attestation by content id. Its output units are the chain's
|
|
// links; no value figure is stated (none is invented).
|
|
let sk = SigningKey::from_bytes(&[0xefu8; 32]);
|
|
let steward = "did:jcp:attested-efficiency-demo";
|
|
let eff = Efficiency {
|
|
task: "sha512-chain".into(),
|
|
completed: true,
|
|
verdict: "chain-computed".into(),
|
|
tokens_in: 1,
|
|
tokens_out: CHAIN_LINKS,
|
|
energy,
|
|
wall_ns: window_ns,
|
|
value_uusd: None,
|
|
energy_attestation: None,
|
|
}
|
|
.with_energy_attestation(&attested.report);
|
|
let envelope = eff.seal(0, &ContentId([0u8; 32]), &sk, steward);
|
|
|
|
// ── 4. A relying party runs the COMPOSED verification, from scratch:
|
|
// (a) the receipt verifies under the steward key.
|
|
let receipt =
|
|
verify_efficiency(&envelope, steward, &sk.verifying_key().to_bytes()).expect("efficiency/1 verifies");
|
|
// (b) its commitment resolves to the attestation we hold.
|
|
let commitment = receipt.energy_attestation.as_deref().expect("receipt carries an attestation commitment");
|
|
assert_eq!(commitment, ContentId::of_bytes(&attested.report).to_hex(), "commitment must match the attestation");
|
|
// (c) the attestation verifies — deriving the trust level from evidence.
|
|
let trust = verify_attested_energy(&attested, measurement_digest, &nonce, &provider.vcek(), Grounding::Software)
|
|
.expect("attestation verifies");
|
|
// (d) the attested joules equal the sealed joules.
|
|
assert_eq!(attested.statement.energy_uj, receipt.energy_uj, "attested energy must match the receipt");
|
|
|
|
// Tampering is caught: rewrite the attested energy figure downstream.
|
|
let mut forged = attested.clone();
|
|
forged.statement.energy_uj = forged.statement.energy_uj.wrapping_add(1);
|
|
let tamper_rejected =
|
|
verify_attested_energy(&forged, measurement_digest, &nonce, &provider.vcek(), Grounding::Software).is_err();
|
|
|
|
Run { sources, measurement, attested, receipt, trust, output, tamper_rejected }
|
|
}
|
|
|
|
fn main() {
|
|
let r = run();
|
|
let counter = r.attested.statement.counter;
|
|
let sources = if r.sources.is_empty() { "none — the estimator".to_string() } else { r.sources.join(", ") };
|
|
|
|
println!("task '{}' ran here: {} SHA-512 links in {:?}", r.receipt.task, CHAIN_LINKS, r.measurement.wall);
|
|
println!(" meter's live counters : {sources}");
|
|
println!(
|
|
" sealed efficiency/1 : {} µJ [{}] over {} links\n",
|
|
r.receipt.energy_uj, r.receipt.provenance, r.receipt.tokens_out
|
|
);
|
|
|
|
println!("composed verification ✓");
|
|
println!(" receipt : verified under the steward key");
|
|
println!(" commitment : matches the attestation");
|
|
println!(
|
|
" attested energy : {} µJ ({}) == receipt energy {} µJ [{}]",
|
|
r.attested.statement.energy_uj,
|
|
counter.tag(),
|
|
r.receipt.energy_uj,
|
|
r.receipt.provenance
|
|
);
|
|
println!(" nonce : fresh (matches the issued challenge)");
|
|
println!(
|
|
" efficiency : {} µJ / link",
|
|
r.receipt
|
|
.uj_per_accepted_token
|
|
.map_or_else(|| "—".to_string(), |uj| uj.to_string())
|
|
);
|
|
println!(" TRUST LEVEL : {:?}, over a `{}` figure", r.trust, counter.tag());
|
|
println!();
|
|
|
|
assert_eq!(r.trust, AttestTrust::IntegrityAttested);
|
|
if counter == CounterClass::Estimator {
|
|
println!(
|
|
"This is IntegrityAttested over an ESTIMATE: no counter on this host read the\n\
|
|
task's CPU work over the span, so the figure is the meter's estimate, attested\n\
|
|
under the `estimator` class and sealed `Estimator`. The report binds the figure\n\
|
|
to a measured environment; it does not make the figure a reading."
|
|
);
|
|
} else {
|
|
println!(
|
|
"This is IntegrityAttested, not PhysicallyRooted: a software-grounded report\n\
|
|
over a platform-self-reported counter (`{}`) attests an honest *relay* inside\n\
|
|
a measured environment — not a reading rooted independently of the platform.\n\
|
|
(Not a claim that the counter is inaccurate: it is a real sensor reading;\n\
|
|
nothing binds it to the sensor.) An independent root needs a dedicated sensor\n\
|
|
signed at the source (a shunt) on confidential hardware — derived, never asserted.",
|
|
counter.tag()
|
|
);
|
|
}
|
|
|
|
assert!(r.tamper_rejected, "a tampered energy figure must fail attestation verification");
|
|
println!("\n(a tampered joule figure fails attestation verification — checked.)");
|
|
std::hint::black_box(r.output);
|
|
}
|