//! `jcr1_mint` — mint a fresh JCR-1 receipt from an energy reading. //! //! Reads CPU energy through `jouleclaw-energy::rapl`: the sum of every //! powercap package zone (`/sys/class/powercap/intel-rapl:` named //! `package-`), no FFI. On a system //! without RAPL (Apple, BSD, CI sandboxes, this Mac the example is //! built on) the producer does NOT fake a `HwShunt` reading; it falls //! back to a time-based `Estimator` value rounded to the 1 mJ //! resolution floor, exactly as JCR-1 §2.3 requires. //! //! Output: a single-vector JSON document in the same shape as //! `conformance/vectors.json`, accepted by both the Python and Go //! reference verifiers. Rust mints; Python and Go verify — three //! independent implementations agreeing on one standard. //! //! Run: //! cargo run -p jouleclaw-jcr1 --example jcr1_mint -- /tmp/minted.json use ed25519_dalek::SigningKey; use jouleclaw_energy::rapl::RaplCounter; use jouleclaw_energy::{EnergyCounter, Provenance}; use jouleclaw_intent::HeuristicClassifier; use jouleclaw_jcr1::{ canonical_signed_core, cose_sign1_envelope, hmac_tool_hex, to_jcr1_wire, TEST_KEYS_JSON, }; use jouleclaw_prov::{input_hash, CascadeTier, ReceiptBuilder, ToolReceipt, ToolTouch}; use std::fs; use std::time::Instant; // Fixed-but-illustrative timestamp so the receipt is reproducible by // description; in production this is the actual `Utc::now().to_rfc3339()` // at cascade close. const CLOSED_AT: &str = "2026-05-31T18:04:22Z"; /// Measure energy of one unit of deterministic work (stand-in for an L1 /// tool execution) via the typed `EnergyCounter` trait. If a real /// hardware counter is available (Linux/x86 RAPL via sysfs) the /// reading is `HwShunt` at microjoule resolution. Otherwise the /// fallback estimator returns an `Estimator` value rounded to the /// JCR-1 §2.3 1 mJ floor. /// /// Both paths route through `jouleclaw-energy` rather than raw sysfs /// reads, so the energy provenance is the trait's own /// `provenance()` declaration — no chance of the producer accidentally /// upgrading an Estimator span to look like HwShunt. fn measure_resolution_energy() -> (u64, Provenance) { let rapl = RaplCounter::new(); let start = if rapl.is_available() { rapl.read().ok() } else { None }; let t = Instant::now(); // A small bounded unit of deterministic work — enough to consume // measurable time on any platform. let mut acc = 0u64; for i in 0..200_000u64 { acc = acc.wrapping_add(blake3::hash(&i.to_le_bytes()).as_bytes()[0] as u64); } std::hint::black_box(acc); let elapsed = t.elapsed(); match (start, rapl.is_available().then(|| rapl.read().ok()).flatten()) { (Some(a), Some(b)) if b.uj > a.uj => (b.uj - a.uj, rapl.provenance()), _ => { // No hardware counter. Estimate from elapsed time at a // conservative ~6 W CPU draw, then ROUND to 1 mJ and tag // `Estimator` (JCR-1 §2.3 resolution floor). let est_uj = (elapsed.as_secs_f64() * 6.0 * 1_000_000.0) as u64; let floored = ((est_uj + 500) / 1000).max(1) * 1000; (floored, Provenance::Estimator) } } } fn main() { let out_path = std::env::args() .nth(1) .unwrap_or_else(|| "minted-vectors.json".to_string()); // 1. Derive the signing key from the published test seed so any // verifier with `conformance/test-keys.json` accepts the result. let keys: serde_json::Value = serde_json::from_str(TEST_KEYS_JSON).expect("parse keys"); let seed_hex = keys["ed25519_seed_hex"].as_str().unwrap(); let seed: [u8; 32] = hex::decode(seed_hex).unwrap().try_into().unwrap(); let sk = SigningKey::from_bytes(&seed); let pubkey_hex = hex::encode(sk.verifying_key().to_bytes()); let steward = keys["steward_kid"].as_str().unwrap(); let gw_id = "gw-edge-7"; let gw_key = keys["gateway_hmac_keys"][gw_id].as_str().unwrap().as_bytes(); // 2. Measure energy. let (joules_uj, prov) = measure_resolution_energy(); let prov_tag = match prov { Provenance::HwShunt => "HwShunt", Provenance::ModelBased => "ModelBased", Provenance::Estimator => "Estimator", }; // 3. Build a receipt — an L1 IaC describe call that closed the query. let tool_id = "iac:aws.ec2.describe"; let query = "list my ec2 instances in us-east-1 (minted live)"; let input_h = input_hash(query); let classifier = HeuristicClassifier::new(); let _intent_v = jouleclaw_intent::IntentClassifier::classify(&classifier, query); let receipt = ReceiptBuilder::new() .input_hash(&input_h) .tier(CascadeTier::L1Lawful) .steward(steward) .account_tool(ToolTouch { tool_id: tool_id.into(), joules_uj, energy_provenance: prov, }) .account_tool_receipt(ToolReceipt { tool_id: tool_id.into(), joules_uj, energy_provenance: prov, minted_at: CLOSED_AT.into(), gateway_id: gw_id.into(), hmac_sha256_hex: hmac_tool_hex(gw_key, tool_id, joules_uj, prov_tag, CLOSED_AT, gw_id), }) // Wave-8 fields — present on the receipt but NOT in the JCR-1 // signed core (proposed for v1.1): // .intent(_intent_v) // .model_id("…") .seal() .expect("seal"); // 4. Project to JCR-1 wire form. We also compute the canonical core // via the typed Receipt path to prove it matches the wire-form // path; the actual envelope is signed over the wire-form core // after the illustrative timestamp override below. let wire = to_jcr1_wire(&receipt).expect("project"); let _ = canonical_signed_core(&receipt).expect("encode"); // 5. Override the auto-generated id + closed_at with the illustrative // fixed values so the wire form matches the suite shape exactly. // (In production these come from the cascade walker.) let mut wire = wire; { let obj = wire.as_object_mut().unwrap(); obj.insert("id".into(), serde_json::json!("rcpt-mint-0001")); obj.insert("closed_at".into(), serde_json::json!(CLOSED_AT)); } // Re-canonicalise with the patched fields so envelope.payload == core. let core = jouleclaw_jcr1::signed_core_bytes(&wire).expect("encode"); let envelope = cose_sign1_envelope(&core, &sk, steward); // 6. Emit in the suite format both verifiers consume. let out = serde_json::json!({ "spec": "JCR-1", "canonical_cbor": "RFC 8949 §4.2.1 length-first key order", "envelope": "COSE_Sign1 (RFC 9052), EdDSA/Ed25519", "signed_core_excludes": ["token_attributions"], "vector_count": 1, "vectors": [{ "n": 1, "description": format!( "LIVE-MINTED by jouleclaw-jcr1: L1 IaC call closed the query; energy = {prov_tag} ({joules_uj} µJ)." ), "receipt": wire, "signed_core_cbor_hex": hex::encode(&core), "cose_sign1_hex": hex::encode(&envelope), }] }); fs::write(&out_path, serde_json::to_string_pretty(&out).unwrap()).expect("write"); println!("jouleclaw-jcr1 producer — minted 1 receipt"); println!(" derived ed25519 public : {pubkey_hex}"); println!(" energy provenance : {prov_tag}"); println!(" joules_uj : {joules_uj}"); println!(" signed core : {} bytes", core.len()); println!(" envelope (COSE_Sign1) : {} bytes", envelope.len()); println!(" output : {out_path}"); println!(); println!("Verify with either independent implementation:"); println!(" python3 conformance/verify.py {out_path} conformance/test-keys.json"); println!(" go run conformance/verify.go {out_path} conformance/test-keys.json"); }