Skip to main content

wai_quantum/
quantum_ops.rs

1//! Quantum-operations attestation — `wai.quantum.calibration` + `wai.quantum.job`
2//! (extensions/quantum-ops). The verify-half of WAI applied to the quantum
3//! processing industry.
4//!
5//! A calibration run or a QPU job leaves a dataset, a state snapshot and a log.
6//! This module adds a receipt over each operation with two properties:
7//! **cryptographic signature/portability** and **measured joules**.
8//!
9//! It does NOT reconstruct anything and does NOT run hardware — the calibration/job
10//! is performed by the sink's control system (or a real QPU), exactly as WAI dispatches
11//! a decode to a sink's codec. WAI owns the **receipt**: a signed, content-
12//! addressed, joule-metered, capability-granted record of what ran, under what
13//! device state, at what energy cost, authorized by whom.
14//!
15//! Two receipts, cross-linked:
16//! - [`CalibrationReceipt`] — a bring-up / tune-up run: binds the applied config
17//!   to the evidence it produced (RB / T1·T2 / gate fidelity) and the device
18//!   lineage, sealed with the measured joules and the grant that authorized it.
19//! - [`JobReceipt`] — a dispatched circuit: binds the circuit to its result and
20//!   the **`CalibrationReceipt` the device was in** when it ran (the QCIVET
21//!   "verify the claimed calibration was in effect" check), sealed the same way.
22//!
23//! The **grant ceiling is enforced in `verify()`**: a receipt whose measured
24//! joules exceed the grant's joule budget is not cryptographically valid — an
25//! operation that blew its energy grant cannot produce a passing receipt. Gate ⊗
26//! meter ⊗ receipt, in one object. A JWP profile — the world/provenance Merkle +
27//! Ed25519, reused.
28
29use crate::merkle::merkle_root;
30use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
31
32/// A signed attestation that a calibration run happened, with what result and
33/// energy cost, under what authorization.
34pub const CAP_QUANTUM_CALIBRATION: &str = "wai.quantum.calibration";
35/// A signed attestation that a circuit was dispatched to a backend, with what
36/// result and energy cost, under what calibration state and authorization.
37pub const CAP_QUANTUM_JOB: &str = "wai.quantum.job";
38/// A signed attestation of ONE classical error-mitigation post-processing step —
39/// the deterministic layer between raw QPU counts and the reported number, which
40/// today runs in nondeterministic float with no provenance over the step.
41pub const CAP_QUANTUM_MITIGATE: &str = "wai.quantum.mitigate";
42/// A signed attestation of ONE quantum error-correction DECODE — the classical
43/// decoder that turns a syndrome into a correction, with its measured energy.
44/// Joules-per-decode is the metric no decoder reports today.
45pub const CAP_QUANTUM_DECODE: &str = "wai.quantum.decode";
46/// A signed attestation that a COMPILED / routed circuit is equivalent to its
47/// source — compilation correctness, which no compiler signs today.
48pub const CAP_QUANTUM_COMPILE: &str = "wai.quantum.compile";
49/// A signed attestation of a neutral-atom REARRANGEMENT plan — turning a low-yield
50/// stochastic load into a defect-free target array, with the plan's cost.
51pub const CAP_QUANTUM_REARRANGE: &str = "wai.quantum.rearrange";
52/// A signed Quantum Bill of Materials — one provenance manifest bundling the stage
53/// receipts of a quantum computation (compile, calibrate, execute, mitigate, …).
54pub const CAP_QUANTUM_QBOM: &str = "wai.quantum.qbom";
55/// A signed statement of where a result ran: the fabric member, the kernel
56/// implementation, the parity its certificate earned against the CPU
57/// reference, and the joules (`crate::quantum_fabric`).
58pub const CAP_QUANTUM_FABRIC: &str = "wai.quantum.fabric";
59
60const DOMAIN_CAL: &[u8] = b"wai:quantum-calibration\x01";
61const DOMAIN_CAL_ID: &[u8] = b"wai:quantum-calibration-id\x01";
62const DOMAIN_JOB: &[u8] = b"wai:quantum-job\x01";
63const DOMAIN_JOB_ID: &[u8] = b"wai:quantum-job-id\x01";
64const DOMAIN_MIT: &[u8] = b"wai:quantum-mitigate\x01";
65const DOMAIN_MIT_ID: &[u8] = b"wai:quantum-mitigate-id\x01";
66const DOMAIN_MIT_RESULT: &[u8] = b"wai:quantum-mitigate-result\x01";
67const DOMAIN_DEC: &[u8] = b"wai:quantum-decode\x01";
68const DOMAIN_DEC_ID: &[u8] = b"wai:quantum-decode-id\x01";
69const DOMAIN_CMP: &[u8] = b"wai:quantum-compile\x01";
70const DOMAIN_CMP_ID: &[u8] = b"wai:quantum-compile-id\x01";
71const DOMAIN_REA: &[u8] = b"wai:quantum-rearrange\x01";
72const DOMAIN_REA_ID: &[u8] = b"wai:quantum-rearrange-id\x01";
73
74fn hx(b: &[u8]) -> String {
75    b.iter().map(|x| format!("{x:02x}")).collect()
76}
77
78fn from_hex32(s: &str) -> Option<[u8; 32]> {
79    if s.len() != 64 || !s.bytes().all(|b| b.is_ascii_hexdigit()) {
80        return None;
81    }
82    let mut out = [0u8; 32];
83    for (i, c) in s.as_bytes().chunks(2).enumerate() {
84        out[i] = u8::from_str_radix(std::str::from_utf8(c).ok()?, 16).ok()?;
85    }
86    Some(out)
87}
88
89fn from_hex64(s: &str) -> Option<[u8; 64]> {
90    if s.len() != 128 || !s.bytes().all(|b| b.is_ascii_hexdigit()) {
91        return None;
92    }
93    let mut out = [0u8; 64];
94    for (i, c) in s.as_bytes().chunks(2).enumerate() {
95        out[i] = u8::from_str_radix(std::str::from_utf8(c).ok()?, 16).ok()?;
96    }
97    Some(out)
98}
99
100/// Content hash of arbitrary operation bytes (a pulse config, an HDF5 dataset,
101/// a result histogram) — the content-addressing primitive the whole module is
102/// built on. Anyone with the bytes recomputes it and checks the binding.
103pub fn content_hash(bytes: &[u8]) -> [u8; 32] {
104    *blake3::hash(bytes).as_bytes()
105}
106
107/// A reference to the JCP grant that authorized an operation: capability ⊗
108/// joule-ceiling (⊗ optional funds-ceiling, JCP §4.8 dual ceiling). The full
109/// grant lives in the JCP layer; the receipt binds its content hash plus the
110/// ceilings it enforces, so the receipt is self-contained for the budget check.
111#[derive(Clone, Debug, PartialEq, Eq)]
112pub struct GrantRef {
113    /// Content hash of the full JCP grant (authoritative record elsewhere).
114    pub grant_hash: [u8; 32],
115    /// The capability the grant conferred, e.g. `"quantum.calibrate"`.
116    pub capability: String,
117    /// The joule budget (µJ) the grant authorized. `u64::MAX` = no joule ceiling.
118    pub joule_ceiling_micro: u64,
119    /// Optional money ceiling (minor units); `None` = no funds ceiling.
120    pub funds_ceiling: Option<u64>,
121}
122
123impl GrantRef {
124    /// A grant with no ceilings — for unmetered/unbudgeted operations (the
125    /// receipt still binds and signs everything else).
126    pub fn unbounded(capability: impl Into<String>) -> GrantRef {
127        GrantRef {
128            grant_hash: [0u8; 32],
129            capability: capability.into(),
130            joule_ceiling_micro: u64::MAX,
131            funds_ceiling: None,
132        }
133    }
134
135    fn append_to(&self, o: &mut Vec<u8>) {
136        o.extend_from_slice(&self.grant_hash);
137        o.extend_from_slice(self.capability.as_bytes());
138        o.push(0); // capability terminator (unambiguous framing)
139        o.extend_from_slice(&self.joule_ceiling_micro.to_be_bytes());
140        match self.funds_ceiling {
141            Some(f) => {
142                o.push(1);
143                o.extend_from_slice(&f.to_be_bytes());
144            }
145            None => o.push(0),
146        }
147    }
148
149    fn to_json(&self) -> String {
150        let funds = match self.funds_ceiling {
151            Some(f) => f.to_string(),
152            None => "null".into(),
153        };
154        format!(
155            "{{\"capability\":{},\"funds_ceiling\":{},\"grant_hash\":\"{}\",\"joule_ceiling_micro\":{}}}",
156            serde_json::to_string(&self.capability).unwrap(),
157            funds,
158            hx(&self.grant_hash),
159            self.joule_ceiling_micro,
160        )
161    }
162
163    fn from_json(v: &serde_json::Value) -> Option<GrantRef> {
164        let o = v.as_object()?;
165        Some(GrantRef {
166            grant_hash: from_hex32(o.get("grant_hash")?.as_str()?)?,
167            capability: o.get("capability")?.as_str()?.to_owned(),
168            joule_ceiling_micro: o.get("joule_ceiling_micro")?.as_u64()?,
169            funds_ceiling: match o.get("funds_ceiling") {
170                Some(serde_json::Value::Number(n)) => Some(n.as_u64()?),
171                _ => None,
172            },
173        })
174    }
175}
176
177/// One piece of calibration evidence — a benchmark outcome, content-addressed to
178/// its raw bytes. The `blob_hash` binds a fidelity *claim* to the exact dataset
179/// (HDF5/JSON) that produced it; `summary` is human-readable and not trusted.
180#[derive(Clone, Debug, PartialEq, Eq)]
181pub struct Evidence {
182    /// e.g. `"randomized_benchmarking"`, `"t1t2"`, `"rabi"`, `"readout_gmm"`.
183    pub kind: String,
184    /// BLAKE3 of the canonical evidence bytes (the vendor dataset).
185    pub blob_hash: [u8; 32],
186    /// Human summary, e.g. `"1q RB fidelity 0.9992"`. Informational only.
187    pub summary: String,
188}
189
190impl Evidence {
191    fn leaf(&self) -> [u8; 32] {
192        let mut h = blake3::Hasher::new();
193        h.update(b"wai:qcal-evidence\x01");
194        h.update(self.kind.as_bytes());
195        h.update(&[0]);
196        h.update(&self.blob_hash);
197        *h.finalize().as_bytes()
198    }
199}
200
201// ---------------------------------------------------------------------------
202// CalibrationReceipt
203// ---------------------------------------------------------------------------
204
205/// A signed, joule-metered, grant-authorized record of one calibration/tune-up
206/// run. Chains to the prior device state via `parent_receipt_hash`: a signed
207/// lineage of device states.
208#[derive(Clone, Debug, PartialEq)]
209pub struct CalibrationReceipt {
210    /// Device / QPU identity (e.g. `"example:lab3-device"`, a wafer/die id).
211    pub device_id: String,
212    /// What was calibrated: `"q3"`, `"cz(q3,q4)"`, `"readout:q3"`.
213    pub target: String,
214    /// Content hash of the applied pulse/gate config (the "config").
215    pub config_hash: [u8; 32],
216    /// The evidence produced (the "proof it's tuned").
217    pub evidence: Vec<Evidence>,
218    /// Merkle root over `[config_hash] ++ evidence leaves`.
219    pub merkle_root: [u8; 32],
220    /// Measured energy of the run (µJ). Attested by the signature.
221    pub joules_micro: u64,
222    /// The grant that authorized the run (capability ⊗ joule ceiling).
223    pub grant: GrantRef,
224    /// Prior calibration state this one supersedes (signed lineage).
225    pub parent_receipt_hash: Option<[u8; 32]>,
226    pub signer_pubkey: [u8; 32],
227    pub signer_id: String,
228    pub sig: [u8; 64],
229}
230
231impl CalibrationReceipt {
232    fn leaves(&self) -> Vec<[u8; 32]> {
233        let mut v = vec![self.config_hash];
234        v.extend(self.evidence.iter().map(|e| e.leaf()));
235        v
236    }
237
238    fn signing_payload(&self) -> Vec<u8> {
239        self.payload_with(None)
240    }
241
242    /// The signing payload: unlabelled (the legacy bytes), or labelled
243    /// (`crate::quantum_energy`) — the `0x02` domain, with the class after
244    /// the figure it labels.
245    fn payload_with(&self, label: Option<&crate::quantum_energy::EnergyClass>) -> Vec<u8> {
246        let mut o = crate::quantum_energy::labelled_domain(DOMAIN_CAL, label);
247        o.extend_from_slice(self.device_id.as_bytes());
248        o.push(0);
249        o.extend_from_slice(self.target.as_bytes());
250        o.push(0);
251        o.extend_from_slice(&self.config_hash);
252        o.extend_from_slice(&self.merkle_root);
253        o.extend_from_slice(&(self.evidence.len() as u64).to_be_bytes());
254        o.extend_from_slice(&self.joules_micro.to_be_bytes());
255        crate::quantum_energy::write_label(label, &mut o);
256        self.grant.append_to(&mut o);
257        match self.parent_receipt_hash {
258            Some(h) => {
259                o.push(1);
260                o.extend_from_slice(&h);
261            }
262            None => o.push(0),
263        }
264        o.extend_from_slice(&self.signer_pubkey);
265        o.extend_from_slice(self.signer_id.as_bytes());
266        o
267    }
268
269    /// Build and sign a calibration receipt.
270    #[allow(clippy::too_many_arguments)]
271    pub fn seal(
272        signer: &SigningKey,
273        signer_id: impl Into<String>,
274        device_id: impl Into<String>,
275        target: impl Into<String>,
276        config_hash: [u8; 32],
277        evidence: Vec<Evidence>,
278        joules_micro: u64,
279        grant: GrantRef,
280        parent_receipt_hash: Option<[u8; 32]>,
281    ) -> CalibrationReceipt {
282        let mut r = CalibrationReceipt {
283            device_id: device_id.into(),
284            target: target.into(),
285            config_hash,
286            evidence,
287            merkle_root: [0u8; 32],
288            joules_micro,
289            grant,
290            parent_receipt_hash,
291            signer_pubkey: signer.verifying_key().to_bytes(),
292            signer_id: signer_id.into(),
293            sig: [0u8; 64],
294        };
295        r.merkle_root = merkle_root(&r.leaves());
296        r.sig = signer.sign(&r.signing_payload()).to_bytes();
297        r
298    }
299
300    /// The measured energy is within the grant's joule ceiling — the metered
301    /// half of the gate ⊗ meter ⊗ receipt property.
302    pub fn honors_budget(&self) -> bool {
303        self.joules_micro <= self.grant.joule_ceiling_micro
304    }
305
306    /// Every check [`Self::verify`] makes except the signature — shared with
307    /// the labelled form (`crate::quantum_energy::Labelled`).
308    fn body_verifies(&self) -> bool {
309        if merkle_root(&self.leaves()) != self.merkle_root {
310            return false;
311        }
312        if !self.honors_budget() {
313            return false;
314        }
315        true
316    }
317
318    /// Cryptographically valid AND within budget: the Merkle root recomputes,
319    /// the measured joules are within the grant ceiling, and the signature is
320    /// valid. A run that blew its energy grant cannot produce a passing receipt.
321    pub fn verify(&self) -> bool {
322        if !self.body_verifies() {
323            return false;
324        }
325        let Ok(k) = VerifyingKey::from_bytes(&self.signer_pubkey) else {
326            return false;
327        };
328        k.verify(&self.signing_payload(), &Signature::from_bytes(&self.sig))
329            .is_ok()
330    }
331
332    /// Confirm a fidelity claim binds to real bytes: recompute `content_hash` of
333    /// the supplied dataset and match the i-th evidence's `blob_hash`. This is
334    /// what makes "1q RB fidelity 0.9992" checkable instead of asserted.
335    pub fn evidence_matches(&self, i: usize, dataset_bytes: &[u8]) -> bool {
336        self.evidence
337            .get(i)
338            .is_some_and(|e| content_hash(dataset_bytes) == e.blob_hash)
339    }
340
341    /// Stable id for chaining and for a job to reference this device state.
342    pub fn receipt_hash(&self) -> [u8; 32] {
343        let mut h = blake3::Hasher::new();
344        h.update(DOMAIN_CAL_ID);
345        h.update(&self.signing_payload());
346        h.update(&self.sig);
347        *h.finalize().as_bytes()
348    }
349
350    pub fn to_json(&self) -> String {
351        let ev: Vec<String> = self
352            .evidence
353            .iter()
354            .map(|e| {
355                format!(
356                    "{{\"blob_hash\":\"{}\",\"kind\":{},\"summary\":{}}}",
357                    hx(&e.blob_hash),
358                    serde_json::to_string(&e.kind).unwrap(),
359                    serde_json::to_string(&e.summary).unwrap(),
360                )
361            })
362            .collect();
363        let parent = match self.parent_receipt_hash {
364            Some(h) => format!("\"{}\"", hx(&h)),
365            None => "null".into(),
366        };
367        format!(
368            "{{\"kind\":\"quantum-calibration\",\"config_hash\":\"{}\",\"device_id\":{},\
369             \"evidence\":[{}],\"grant\":{},\"joules_micro\":{},\"merkle_root\":\"{}\",\
370             \"parent_receipt_hash\":{},\"receipt_hash\":\"{}\",\"sig\":\"{}\",\
371             \"signer_id\":{},\"signer_pubkey\":\"{}\",\"target\":{}}}",
372            hx(&self.config_hash),
373            serde_json::to_string(&self.device_id).unwrap(),
374            ev.join(","),
375            self.grant.to_json(),
376            self.joules_micro,
377            hx(&self.merkle_root),
378            parent,
379            hx(&self.receipt_hash()),
380            hx(&self.sig),
381            serde_json::to_string(&self.signer_id).unwrap(),
382            hx(&self.signer_pubkey),
383            serde_json::to_string(&self.target).unwrap(),
384        )
385    }
386
387    pub fn from_json(s: &str) -> Option<CalibrationReceipt> {
388        let v: serde_json::Value = serde_json::from_str(s).ok()?;
389        let o = v.as_object()?;
390        let mut evidence = Vec::new();
391        for ev in o.get("evidence")?.as_array()? {
392            let eo = ev.as_object()?;
393            evidence.push(Evidence {
394                kind: eo.get("kind")?.as_str()?.to_owned(),
395                blob_hash: from_hex32(eo.get("blob_hash")?.as_str()?)?,
396                summary: eo.get("summary")?.as_str()?.to_owned(),
397            });
398        }
399        let parent = match o.get("parent_receipt_hash") {
400            Some(serde_json::Value::String(s)) => Some(from_hex32(s)?),
401            _ => None,
402        };
403        Some(CalibrationReceipt {
404            device_id: o.get("device_id")?.as_str()?.to_owned(),
405            target: o.get("target")?.as_str()?.to_owned(),
406            config_hash: from_hex32(o.get("config_hash")?.as_str()?)?,
407            evidence,
408            merkle_root: from_hex32(o.get("merkle_root")?.as_str()?)?,
409            joules_micro: o.get("joules_micro")?.as_u64()?,
410            grant: GrantRef::from_json(o.get("grant")?)?,
411            parent_receipt_hash: parent,
412            signer_pubkey: from_hex32(o.get("signer_pubkey")?.as_str()?)?,
413            signer_id: o.get("signer_id")?.as_str()?.to_owned(),
414            sig: from_hex64(o.get("sig")?.as_str()?)?,
415        })
416    }
417}
418
419// ---------------------------------------------------------------------------
420// JobReceipt
421// ---------------------------------------------------------------------------
422
423/// A signed, joule-metered, grant-authorized record of one dispatched circuit —
424/// bound to the exact calibration state the device was in when it ran.
425#[derive(Clone, Debug, PartialEq)]
426pub struct JobReceipt {
427    /// Which backend actually ran it: `"example:qpu-a"`, `"wai.quantum.circuit"`.
428    pub backend_id: String,
429    /// Content hash of the dispatched circuit (e.g. a WQC container / QASM).
430    pub circuit_hash: [u8; 32],
431    /// The [`CalibrationReceipt::receipt_hash`] of the device state in effect —
432    /// the QCIVET "the claimed calibration was actually in effect" binding.
433    /// `None` for a simulator or an uncalibrated run.
434    pub calibration_ref: Option<[u8; 32]>,
435    /// Content hash of the returned result (counts histogram / statevector).
436    pub result_hash: [u8; 32],
437    /// Shots requested (informational).
438    pub shots: u64,
439    /// Merkle root over `[circuit_hash, result_hash, (calibration_ref?)]`.
440    pub merkle_root: [u8; 32],
441    /// Measured energy of the job (µJ). Attested by the signature.
442    pub joules_micro: u64,
443    pub grant: GrantRef,
444    pub parent_receipt_hash: Option<[u8; 32]>,
445    pub signer_pubkey: [u8; 32],
446    pub signer_id: String,
447    pub sig: [u8; 64],
448}
449
450impl JobReceipt {
451    fn leaves(&self) -> Vec<[u8; 32]> {
452        let mut v = vec![self.circuit_hash, self.result_hash];
453        if let Some(c) = self.calibration_ref {
454            v.push(c);
455        }
456        v
457    }
458
459    fn signing_payload(&self) -> Vec<u8> {
460        self.payload_with(None)
461    }
462
463    /// The signing payload: unlabelled (the legacy bytes), or labelled
464    /// (`crate::quantum_energy`) — the `0x02` domain, with the class after
465    /// the figure it labels.
466    fn payload_with(&self, label: Option<&crate::quantum_energy::EnergyClass>) -> Vec<u8> {
467        let mut o = crate::quantum_energy::labelled_domain(DOMAIN_JOB, label);
468        o.extend_from_slice(self.backend_id.as_bytes());
469        o.push(0);
470        o.extend_from_slice(&self.circuit_hash);
471        o.extend_from_slice(&self.result_hash);
472        match self.calibration_ref {
473            Some(c) => {
474                o.push(1);
475                o.extend_from_slice(&c);
476            }
477            None => o.push(0),
478        }
479        o.extend_from_slice(&self.shots.to_be_bytes());
480        o.extend_from_slice(&self.merkle_root);
481        o.extend_from_slice(&self.joules_micro.to_be_bytes());
482        crate::quantum_energy::write_label(label, &mut o);
483        self.grant.append_to(&mut o);
484        match self.parent_receipt_hash {
485            Some(h) => {
486                o.push(1);
487                o.extend_from_slice(&h);
488            }
489            None => o.push(0),
490        }
491        o.extend_from_slice(&self.signer_pubkey);
492        o.extend_from_slice(self.signer_id.as_bytes());
493        o
494    }
495
496    #[allow(clippy::too_many_arguments)]
497    pub fn seal(
498        signer: &SigningKey,
499        signer_id: impl Into<String>,
500        backend_id: impl Into<String>,
501        circuit_hash: [u8; 32],
502        result_hash: [u8; 32],
503        calibration_ref: Option<[u8; 32]>,
504        shots: u64,
505        joules_micro: u64,
506        grant: GrantRef,
507        parent_receipt_hash: Option<[u8; 32]>,
508    ) -> JobReceipt {
509        let mut r = JobReceipt {
510            backend_id: backend_id.into(),
511            circuit_hash,
512            calibration_ref,
513            result_hash,
514            shots,
515            merkle_root: [0u8; 32],
516            joules_micro,
517            grant,
518            parent_receipt_hash,
519            signer_pubkey: signer.verifying_key().to_bytes(),
520            signer_id: signer_id.into(),
521            sig: [0u8; 64],
522        };
523        r.merkle_root = merkle_root(&r.leaves());
524        r.sig = signer.sign(&r.signing_payload()).to_bytes();
525        r
526    }
527
528    pub fn honors_budget(&self) -> bool {
529        self.joules_micro <= self.grant.joule_ceiling_micro
530    }
531
532    /// Every check [`Self::verify`] makes except the signature — shared with
533    /// the labelled form (`crate::quantum_energy::Labelled`).
534    fn body_verifies(&self) -> bool {
535        if merkle_root(&self.leaves()) != self.merkle_root {
536            return false;
537        }
538        if !self.honors_budget() {
539            return false;
540        }
541        true
542    }
543
544    /// Merkle recomputes, joules within the grant ceiling, signature valid.
545    pub fn verify(&self) -> bool {
546        if !self.body_verifies() {
547            return false;
548        }
549        let Ok(k) = VerifyingKey::from_bytes(&self.signer_pubkey) else {
550            return false;
551        };
552        k.verify(&self.signing_payload(), &Signature::from_bytes(&self.sig))
553            .is_ok()
554    }
555
556    /// The cross-link check: this job claims it ran under `cal`'s device state.
557    /// Confirms `cal` verifies and its id equals this job's `calibration_ref`.
558    /// A third party can now assert "this result was produced on a device in
559    /// this attested calibration state" — the property QCIVET names, here signed.
560    pub fn verify_against_calibration(&self, cal: &CalibrationReceipt) -> bool {
561        self.calibration_ref == Some(cal.receipt_hash()) && cal.verify() && self.verify()
562    }
563
564    /// Confirm the result bytes bind to this receipt's `result_hash`.
565    pub fn result_matches(&self, result_bytes: &[u8]) -> bool {
566        content_hash(result_bytes) == self.result_hash
567    }
568
569    pub fn receipt_hash(&self) -> [u8; 32] {
570        let mut h = blake3::Hasher::new();
571        h.update(DOMAIN_JOB_ID);
572        h.update(&self.signing_payload());
573        h.update(&self.sig);
574        *h.finalize().as_bytes()
575    }
576
577    pub fn to_json(&self) -> String {
578        let cal = match self.calibration_ref {
579            Some(c) => format!("\"{}\"", hx(&c)),
580            None => "null".into(),
581        };
582        let parent = match self.parent_receipt_hash {
583            Some(h) => format!("\"{}\"", hx(&h)),
584            None => "null".into(),
585        };
586        format!(
587            "{{\"kind\":\"quantum-job\",\"backend_id\":{},\"calibration_ref\":{},\
588             \"circuit_hash\":\"{}\",\"grant\":{},\"joules_micro\":{},\"merkle_root\":\"{}\",\
589             \"parent_receipt_hash\":{},\"receipt_hash\":\"{}\",\"result_hash\":\"{}\",\
590             \"shots\":{},\"sig\":\"{}\",\"signer_id\":{},\"signer_pubkey\":\"{}\"}}",
591            serde_json::to_string(&self.backend_id).unwrap(),
592            cal,
593            hx(&self.circuit_hash),
594            self.grant.to_json(),
595            self.joules_micro,
596            hx(&self.merkle_root),
597            parent,
598            hx(&self.receipt_hash()),
599            hx(&self.result_hash),
600            self.shots,
601            hx(&self.sig),
602            serde_json::to_string(&self.signer_id).unwrap(),
603            hx(&self.signer_pubkey),
604        )
605    }
606
607    pub fn from_json(s: &str) -> Option<JobReceipt> {
608        let v: serde_json::Value = serde_json::from_str(s).ok()?;
609        let o = v.as_object()?;
610        let opt_hash = |k: &str| match o.get(k) {
611            Some(serde_json::Value::String(s)) => from_hex32(s),
612            _ => None,
613        };
614        Some(JobReceipt {
615            backend_id: o.get("backend_id")?.as_str()?.to_owned(),
616            circuit_hash: from_hex32(o.get("circuit_hash")?.as_str()?)?,
617            calibration_ref: opt_hash("calibration_ref"),
618            result_hash: from_hex32(o.get("result_hash")?.as_str()?)?,
619            shots: o.get("shots")?.as_u64()?,
620            merkle_root: from_hex32(o.get("merkle_root")?.as_str()?)?,
621            joules_micro: o.get("joules_micro")?.as_u64()?,
622            grant: GrantRef::from_json(o.get("grant")?)?,
623            parent_receipt_hash: opt_hash("parent_receipt_hash"),
624            signer_pubkey: from_hex32(o.get("signer_pubkey")?.as_str()?)?,
625            signer_id: o.get("signer_id")?.as_str()?.to_owned(),
626            sig: from_hex64(o.get("sig")?.as_str()?)?,
627        })
628    }
629}
630
631// ---------------------------------------------------------------------------
632// MitigationReceipt
633// ---------------------------------------------------------------------------
634
635/// A signed, joule-metered, grant-authorized record of ONE classical error-
636/// mitigation post-processing step: it binds the raw measured data and the
637/// (optional, hash-pinned) noise model that went IN to the mitigated estimate
638/// and its error bar that came OUT, plus the method and seed that produced it.
639///
640/// Zero-noise extrapolation, readout inversion, classical shadows, PEC/PEA
641/// aggregation — all are deterministic numerics; run in float, their result is
642/// reproducible only on the same build, and nothing records the step between the
643/// QPU and the reported number. A `MitigationReceipt` makes `(raw counts, noise-model hash, method,
644/// seed) → (mitigated estimate ± error, joules)` a byte-exact, content-addressed,
645/// budget-gated, auditable object: a quantum result you can take to an auditor.
646///
647/// Estimates are signed fixed-point at scale `2^20` (the WAI quantum unit); an
648/// expectation value of `1.0` is `1_048_576`. The `noise_model_hash` is
649/// deliberately just a hash: WAI does not re-derive a device's learned noise
650/// model — it pins the model's provenance so PEC/PEA/TEM estimators (which
651/// consume an externally-learned sparse Pauli–Lindblad / IC-POVM model) bind
652/// *which* model produced the number without claiming to have learned it.
653#[derive(Clone, Debug, PartialEq)]
654pub struct MitigationReceipt {
655    /// Where the raw data came from: `"ibm:torino"`, `"wai.quantum.circuit"`.
656    pub backend_id: String,
657    /// The mitigation method: `"zne.richardson"`, `"zne.linear"`,
658    /// `"readout.tensored"`, `"shadow.pauli"`.
659    pub method: String,
660    /// Human label of the estimated observable: `"ZZ"`, `"<H>"`. Informational.
661    pub observable: String,
662    /// Content hash of the canonical raw measured data (the counts table / the
663    /// noise-scaled expectation table / the shadow snapshots).
664    pub input_hash: [u8; 32],
665    /// Content hash of the noise model the method consumed, if any (the readout
666    /// assignment matrix, the learned sparse Pauli–Lindblad model). `None` for
667    /// methods that need no learned model (ZNE, classical shadows).
668    pub noise_model_hash: Option<[u8; 32]>,
669    /// The unmitigated expectation (fixed-point, scale 2^20), signed.
670    pub raw_estimate_fx: i64,
671    /// The mitigated expectation (fixed-point, scale 2^20), signed.
672    pub mitigated_estimate_fx: i64,
673    /// One-sigma / fit-residual error bar on the mitigated estimate (fixed-point).
674    pub error_bar_fx: i64,
675    /// Total shots behind the raw data (informational).
676    pub shots: u64,
677    /// Merkle root over `[input_hash, (noise_model_hash?), result_leaf]`, where
678    /// `result_leaf` binds `(method, raw, mitigated, error)`.
679    pub merkle_root: [u8; 32],
680    /// Measured energy of the post-processing (µJ). Attested by the signature.
681    pub joules_micro: u64,
682    pub grant: GrantRef,
683    pub parent_receipt_hash: Option<[u8; 32]>,
684    pub signer_pubkey: [u8; 32],
685    pub signer_id: String,
686    pub sig: [u8; 64],
687}
688
689impl MitigationReceipt {
690    fn result_leaf(&self) -> [u8; 32] {
691        let mut h = blake3::Hasher::new();
692        h.update(DOMAIN_MIT_RESULT);
693        h.update(self.method.as_bytes());
694        h.update(&[0]);
695        h.update(&self.raw_estimate_fx.to_be_bytes());
696        h.update(&self.mitigated_estimate_fx.to_be_bytes());
697        h.update(&self.error_bar_fx.to_be_bytes());
698        *h.finalize().as_bytes()
699    }
700
701    fn leaves(&self) -> Vec<[u8; 32]> {
702        let mut v = vec![self.input_hash];
703        if let Some(m) = self.noise_model_hash {
704            v.push(m);
705        }
706        v.push(self.result_leaf());
707        v
708    }
709
710    fn signing_payload(&self) -> Vec<u8> {
711        self.payload_with(None)
712    }
713
714    /// The signing payload: unlabelled (the legacy bytes), or labelled
715    /// (`crate::quantum_energy`) — the `0x02` domain, with the class after
716    /// the figure it labels.
717    fn payload_with(&self, label: Option<&crate::quantum_energy::EnergyClass>) -> Vec<u8> {
718        let mut o = crate::quantum_energy::labelled_domain(DOMAIN_MIT, label);
719        o.extend_from_slice(self.backend_id.as_bytes());
720        o.push(0);
721        o.extend_from_slice(self.method.as_bytes());
722        o.push(0);
723        o.extend_from_slice(self.observable.as_bytes());
724        o.push(0);
725        o.extend_from_slice(&self.input_hash);
726        match self.noise_model_hash {
727            Some(m) => {
728                o.push(1);
729                o.extend_from_slice(&m);
730            }
731            None => o.push(0),
732        }
733        o.extend_from_slice(&self.raw_estimate_fx.to_be_bytes());
734        o.extend_from_slice(&self.mitigated_estimate_fx.to_be_bytes());
735        o.extend_from_slice(&self.error_bar_fx.to_be_bytes());
736        o.extend_from_slice(&self.shots.to_be_bytes());
737        o.extend_from_slice(&self.merkle_root);
738        o.extend_from_slice(&self.joules_micro.to_be_bytes());
739        crate::quantum_energy::write_label(label, &mut o);
740        self.grant.append_to(&mut o);
741        match self.parent_receipt_hash {
742            Some(h) => {
743                o.push(1);
744                o.extend_from_slice(&h);
745            }
746            None => o.push(0),
747        }
748        o.extend_from_slice(&self.signer_pubkey);
749        o.extend_from_slice(self.signer_id.as_bytes());
750        o
751    }
752
753    /// Build and sign a mitigation receipt.
754    #[allow(clippy::too_many_arguments)]
755    pub fn seal(
756        signer: &SigningKey,
757        signer_id: impl Into<String>,
758        backend_id: impl Into<String>,
759        method: impl Into<String>,
760        observable: impl Into<String>,
761        input_hash: [u8; 32],
762        noise_model_hash: Option<[u8; 32]>,
763        raw_estimate_fx: i64,
764        mitigated_estimate_fx: i64,
765        error_bar_fx: i64,
766        shots: u64,
767        joules_micro: u64,
768        grant: GrantRef,
769        parent_receipt_hash: Option<[u8; 32]>,
770    ) -> MitigationReceipt {
771        let mut r = MitigationReceipt {
772            backend_id: backend_id.into(),
773            method: method.into(),
774            observable: observable.into(),
775            input_hash,
776            noise_model_hash,
777            raw_estimate_fx,
778            mitigated_estimate_fx,
779            error_bar_fx,
780            shots,
781            merkle_root: [0u8; 32],
782            joules_micro,
783            grant,
784            parent_receipt_hash,
785            signer_pubkey: signer.verifying_key().to_bytes(),
786            signer_id: signer_id.into(),
787            sig: [0u8; 64],
788        };
789        r.merkle_root = merkle_root(&r.leaves());
790        r.sig = signer.sign(&r.signing_payload()).to_bytes();
791        r
792    }
793
794    pub fn honors_budget(&self) -> bool {
795        self.joules_micro <= self.grant.joule_ceiling_micro
796    }
797
798    /// Every check [`Self::verify`] makes except the signature — shared with
799    /// the labelled form (`crate::quantum_energy::Labelled`).
800    fn body_verifies(&self) -> bool {
801        if merkle_root(&self.leaves()) != self.merkle_root {
802            return false;
803        }
804        if !self.honors_budget() {
805            return false;
806        }
807        true
808    }
809
810    /// Merkle recomputes (the mitigated number is bound to the exact inputs and
811    /// noise model that produced it), joules within the grant ceiling, signature
812    /// valid. Tampering with the raw data, the noise model, or the reported
813    /// estimate breaks verification.
814    pub fn verify(&self) -> bool {
815        if !self.body_verifies() {
816            return false;
817        }
818        let Ok(k) = VerifyingKey::from_bytes(&self.signer_pubkey) else {
819            return false;
820        };
821        k.verify(&self.signing_payload(), &Signature::from_bytes(&self.sig))
822            .is_ok()
823    }
824
825    /// Confirm the raw measured data binds to this receipt's `input_hash` — what
826    /// makes "mitigated ⟨ZZ⟩ = 0.87" checkable against the shots it came from.
827    pub fn input_matches(&self, input_bytes: &[u8]) -> bool {
828        content_hash(input_bytes) == self.input_hash
829    }
830
831    /// Confirm a supplied noise model binds to this receipt's `noise_model_hash`.
832    pub fn noise_model_matches(&self, model_bytes: &[u8]) -> bool {
833        self.noise_model_hash == Some(content_hash(model_bytes))
834    }
835
836    pub fn receipt_hash(&self) -> [u8; 32] {
837        let mut h = blake3::Hasher::new();
838        h.update(DOMAIN_MIT_ID);
839        h.update(&self.signing_payload());
840        h.update(&self.sig);
841        *h.finalize().as_bytes()
842    }
843
844    pub fn to_json(&self) -> String {
845        let nm = match self.noise_model_hash {
846            Some(m) => format!("\"{}\"", hx(&m)),
847            None => "null".into(),
848        };
849        let parent = match self.parent_receipt_hash {
850            Some(h) => format!("\"{}\"", hx(&h)),
851            None => "null".into(),
852        };
853        format!(
854            "{{\"kind\":\"quantum-mitigate\",\"backend_id\":{},\"error_bar_fx\":{},\
855             \"grant\":{},\"input_hash\":\"{}\",\"joules_micro\":{},\"merkle_root\":\"{}\",\
856             \"method\":{},\"mitigated_estimate_fx\":{},\"noise_model_hash\":{},\
857             \"observable\":{},\"parent_receipt_hash\":{},\"raw_estimate_fx\":{},\
858             \"receipt_hash\":\"{}\",\"shots\":{},\"sig\":\"{}\",\"signer_id\":{},\
859             \"signer_pubkey\":\"{}\"}}",
860            serde_json::to_string(&self.backend_id).unwrap(),
861            self.error_bar_fx,
862            self.grant.to_json(),
863            hx(&self.input_hash),
864            self.joules_micro,
865            hx(&self.merkle_root),
866            serde_json::to_string(&self.method).unwrap(),
867            self.mitigated_estimate_fx,
868            nm,
869            serde_json::to_string(&self.observable).unwrap(),
870            parent,
871            self.raw_estimate_fx,
872            hx(&self.receipt_hash()),
873            self.shots,
874            hx(&self.sig),
875            serde_json::to_string(&self.signer_id).unwrap(),
876            hx(&self.signer_pubkey),
877        )
878    }
879
880    pub fn from_json(s: &str) -> Option<MitigationReceipt> {
881        let v: serde_json::Value = serde_json::from_str(s).ok()?;
882        let o = v.as_object()?;
883        let opt_hash = |k: &str| match o.get(k) {
884            Some(serde_json::Value::String(s)) => from_hex32(s),
885            _ => None,
886        };
887        Some(MitigationReceipt {
888            backend_id: o.get("backend_id")?.as_str()?.to_owned(),
889            method: o.get("method")?.as_str()?.to_owned(),
890            observable: o.get("observable")?.as_str()?.to_owned(),
891            input_hash: from_hex32(o.get("input_hash")?.as_str()?)?,
892            noise_model_hash: opt_hash("noise_model_hash"),
893            raw_estimate_fx: o.get("raw_estimate_fx")?.as_i64()?,
894            mitigated_estimate_fx: o.get("mitigated_estimate_fx")?.as_i64()?,
895            error_bar_fx: o.get("error_bar_fx")?.as_i64()?,
896            shots: o.get("shots")?.as_u64()?,
897            merkle_root: from_hex32(o.get("merkle_root")?.as_str()?)?,
898            joules_micro: o.get("joules_micro")?.as_u64()?,
899            grant: GrantRef::from_json(o.get("grant")?)?,
900            parent_receipt_hash: opt_hash("parent_receipt_hash"),
901            signer_pubkey: from_hex32(o.get("signer_pubkey")?.as_str()?)?,
902            signer_id: o.get("signer_id")?.as_str()?.to_owned(),
903            sig: from_hex64(o.get("sig")?.as_str()?)?,
904        })
905    }
906}
907
908// ---------------------------------------------------------------------------
909// DecodeReceipt
910// ---------------------------------------------------------------------------
911
912/// A signed, joule-metered, grant-authorized record of ONE quantum
913/// error-correction DECODE: it binds the code (its parity-check matrix), the
914/// measured syndrome, and the decoder parameters that went IN, to the correction
915/// and convergence that came OUT — plus the classical **work** (message passes)
916/// and **joules** the decode cost.
917///
918/// A real-time QEC decoder is the classical bottleneck of a fault-tolerant
919/// machine: it must return a correction for every syndrome round inside the code
920/// cycle, and its energy budget is a hard system constraint. This receipt reports
921/// **joules-per-decode** and signs the correction the decoder emitted — `(check-matrix hash, syndrome hash, decoder params) →
922/// (correction hash, converged, work, joules)`, content-addressed and budget-
923/// gated. The decode is byte-exact (a deterministic fixed-point decoder), so the
924/// correction reproduces from the receipt and verifies with a public key.
925///
926/// `logical_success` is deliberately NOT a field: whether a correction leaves a
927/// logical error is knowable only in simulation (it needs the true error), not to
928/// a real decoder seeing only a syndrome. The receipt attests what a real decoder
929/// actually knows: it converged (the correction reproduces the syndrome) at this
930/// energy cost.
931#[derive(Clone, Debug, PartialEq)]
932pub struct DecodeReceipt {
933    /// The code decoded: `"toric:L5"`, `"bb:[[72,12]]"`.
934    pub code_id: String,
935    /// The decoder: `"relay-bp.min-sum"`.
936    pub decoder: String,
937    /// Content hash of the canonical parity-check matrix (the code's identity).
938    pub check_matrix_hash: [u8; 32],
939    /// Content hash of the input syndrome.
940    pub syndrome_hash: [u8; 32],
941    /// Content hash of the emitted correction.
942    pub correction_hash: [u8; 32],
943    /// The correction reproduces the syndrome (`H·correction == syndrome`).
944    pub converged: bool,
945    /// Relay legs / BP rounds the decode used (informational, part of the work).
946    pub rounds: u32,
947    /// Classical work: total messages passed. Deterministic and byte-portable —
948    /// the exact figure the modeled/measured joules is derived from.
949    pub work_messages: u64,
950    /// Measured (or modeled) energy of the decode (µJ). Attested by the signature.
951    pub joules_micro: u64,
952    pub grant: GrantRef,
953    pub parent_receipt_hash: Option<[u8; 32]>,
954    pub signer_pubkey: [u8; 32],
955    pub signer_id: String,
956    pub sig: [u8; 64],
957}
958
959impl DecodeReceipt {
960    fn leaves(&self) -> Vec<[u8; 32]> {
961        vec![
962            self.check_matrix_hash,
963            self.syndrome_hash,
964            self.correction_hash,
965        ]
966    }
967
968    fn signing_payload(&self) -> Vec<u8> {
969        self.payload_with(None)
970    }
971
972    /// The signing payload: unlabelled (the legacy bytes), or labelled
973    /// (`crate::quantum_energy`) — the `0x02` domain, with the class after
974    /// the figure it labels.
975    fn payload_with(&self, label: Option<&crate::quantum_energy::EnergyClass>) -> Vec<u8> {
976        let mut o = crate::quantum_energy::labelled_domain(DOMAIN_DEC, label);
977        o.extend_from_slice(self.code_id.as_bytes());
978        o.push(0);
979        o.extend_from_slice(self.decoder.as_bytes());
980        o.push(0);
981        o.extend_from_slice(&self.check_matrix_hash);
982        o.extend_from_slice(&self.syndrome_hash);
983        o.extend_from_slice(&self.correction_hash);
984        o.push(self.converged as u8);
985        o.extend_from_slice(&self.rounds.to_be_bytes());
986        o.extend_from_slice(&self.work_messages.to_be_bytes());
987        o.extend_from_slice(&self.merkle_root_calc());
988        o.extend_from_slice(&self.joules_micro.to_be_bytes());
989        crate::quantum_energy::write_label(label, &mut o);
990        self.grant.append_to(&mut o);
991        match self.parent_receipt_hash {
992            Some(h) => {
993                o.push(1);
994                o.extend_from_slice(&h);
995            }
996            None => o.push(0),
997        }
998        o.extend_from_slice(&self.signer_pubkey);
999        o.extend_from_slice(self.signer_id.as_bytes());
1000        o
1001    }
1002
1003    fn merkle_root_calc(&self) -> [u8; 32] {
1004        merkle_root(&self.leaves())
1005    }
1006
1007    /// Build and sign a decode receipt.
1008    #[allow(clippy::too_many_arguments)]
1009    pub fn seal(
1010        signer: &SigningKey,
1011        signer_id: impl Into<String>,
1012        code_id: impl Into<String>,
1013        decoder: impl Into<String>,
1014        check_matrix_hash: [u8; 32],
1015        syndrome_hash: [u8; 32],
1016        correction_hash: [u8; 32],
1017        converged: bool,
1018        rounds: u32,
1019        work_messages: u64,
1020        joules_micro: u64,
1021        grant: GrantRef,
1022        parent_receipt_hash: Option<[u8; 32]>,
1023    ) -> DecodeReceipt {
1024        let mut r = DecodeReceipt {
1025            code_id: code_id.into(),
1026            decoder: decoder.into(),
1027            check_matrix_hash,
1028            syndrome_hash,
1029            correction_hash,
1030            converged,
1031            rounds,
1032            work_messages,
1033            joules_micro,
1034            grant,
1035            parent_receipt_hash,
1036            signer_pubkey: signer.verifying_key().to_bytes(),
1037            signer_id: signer_id.into(),
1038            sig: [0u8; 64],
1039        };
1040        r.sig = signer.sign(&r.signing_payload()).to_bytes();
1041        r
1042    }
1043
1044    pub fn honors_budget(&self) -> bool {
1045        self.joules_micro <= self.grant.joule_ceiling_micro
1046    }
1047
1048    /// Every check [`Self::verify`] makes except the signature — shared with
1049    /// the labelled form (`crate::quantum_energy::Labelled`).
1050    fn body_verifies(&self) -> bool {
1051        if !self.honors_budget() {
1052            return false;
1053        }
1054        true
1055    }
1056
1057    /// Merkle recomputes (the correction binds to the exact code + syndrome),
1058    /// joules within the grant ceiling, signature valid.
1059    pub fn verify(&self) -> bool {
1060        if !self.body_verifies() {
1061            return false;
1062        }
1063        let Ok(k) = VerifyingKey::from_bytes(&self.signer_pubkey) else {
1064            return false;
1065        };
1066        k.verify(&self.signing_payload(), &Signature::from_bytes(&self.sig))
1067            .is_ok()
1068    }
1069
1070    /// Confirm the correction bytes bind to this receipt's `correction_hash`.
1071    pub fn correction_matches(&self, correction_bytes: &[u8]) -> bool {
1072        content_hash(correction_bytes) == self.correction_hash
1073    }
1074
1075    /// Confirm a supplied parity-check matrix binds to this receipt's code.
1076    pub fn code_matches(&self, matrix_bytes: &[u8]) -> bool {
1077        content_hash(matrix_bytes) == self.check_matrix_hash
1078    }
1079
1080    pub fn receipt_hash(&self) -> [u8; 32] {
1081        let mut h = blake3::Hasher::new();
1082        h.update(DOMAIN_DEC_ID);
1083        h.update(&self.signing_payload());
1084        h.update(&self.sig);
1085        *h.finalize().as_bytes()
1086    }
1087
1088    pub fn to_json(&self) -> String {
1089        let parent = match self.parent_receipt_hash {
1090            Some(h) => format!("\"{}\"", hx(&h)),
1091            None => "null".into(),
1092        };
1093        format!(
1094            "{{\"kind\":\"quantum-decode\",\"check_matrix_hash\":\"{}\",\"code_id\":{},\
1095             \"converged\":{},\"correction_hash\":\"{}\",\"decoder\":{},\"grant\":{},\
1096             \"joules_micro\":{},\"merkle_root\":\"{}\",\"parent_receipt_hash\":{},\
1097             \"receipt_hash\":\"{}\",\"rounds\":{},\"sig\":\"{}\",\"signer_id\":{},\
1098             \"signer_pubkey\":\"{}\",\"syndrome_hash\":\"{}\",\"work_messages\":{}}}",
1099            hx(&self.check_matrix_hash),
1100            serde_json::to_string(&self.code_id).unwrap(),
1101            self.converged,
1102            hx(&self.correction_hash),
1103            serde_json::to_string(&self.decoder).unwrap(),
1104            self.grant.to_json(),
1105            self.joules_micro,
1106            hx(&self.merkle_root_calc()),
1107            parent,
1108            hx(&self.receipt_hash()),
1109            self.rounds,
1110            hx(&self.sig),
1111            serde_json::to_string(&self.signer_id).unwrap(),
1112            hx(&self.signer_pubkey),
1113            hx(&self.syndrome_hash),
1114            self.work_messages,
1115        )
1116    }
1117
1118    pub fn from_json(s: &str) -> Option<DecodeReceipt> {
1119        let v: serde_json::Value = serde_json::from_str(s).ok()?;
1120        let o = v.as_object()?;
1121        let parent = match o.get("parent_receipt_hash") {
1122            Some(serde_json::Value::String(s)) => Some(from_hex32(s)?),
1123            _ => None,
1124        };
1125        Some(DecodeReceipt {
1126            code_id: o.get("code_id")?.as_str()?.to_owned(),
1127            decoder: o.get("decoder")?.as_str()?.to_owned(),
1128            check_matrix_hash: from_hex32(o.get("check_matrix_hash")?.as_str()?)?,
1129            syndrome_hash: from_hex32(o.get("syndrome_hash")?.as_str()?)?,
1130            correction_hash: from_hex32(o.get("correction_hash")?.as_str()?)?,
1131            converged: o.get("converged")?.as_bool()?,
1132            rounds: o.get("rounds")?.as_u64()? as u32,
1133            work_messages: o.get("work_messages")?.as_u64()?,
1134            joules_micro: o.get("joules_micro")?.as_u64()?,
1135            grant: GrantRef::from_json(o.get("grant")?)?,
1136            parent_receipt_hash: parent,
1137            signer_pubkey: from_hex32(o.get("signer_pubkey")?.as_str()?)?,
1138            signer_id: o.get("signer_id")?.as_str()?.to_owned(),
1139            sig: from_hex64(o.get("sig")?.as_str()?)?,
1140        })
1141    }
1142}
1143
1144// ---------------------------------------------------------------------------
1145// CompileReceipt
1146// ---------------------------------------------------------------------------
1147
1148/// A signed, joule-metered, grant-authorized record that a COMPILED circuit — a
1149/// routed / optimized / hardware-mapped circuit — **is equivalent to its source**.
1150///
1151/// A quantum compiler routes a circuit onto a device's coupling graph (inserting
1152/// SWAPs), fuses gates, and remaps qubits. Whether the compiled circuit still
1153/// computes the same thing is a correctness obligation that today is checked (if
1154/// at all) by an unsigned, non-reproducible tool. This receipt binds the source
1155/// circuit, the compiled circuit, and the coupling graph to a `equivalent` verdict
1156/// produced by a **deterministic, byte-exact** equivalence check (a Clifford
1157/// stabilizer-tableau comparison), sealed the same way as the rest of the family.
1158/// A compiled circuit you can put in an auditor's hands, reproduce from the git
1159/// repo, and check with a public key.
1160///
1161/// `equivalent` is what a real compiler DOES know (it can run the same
1162/// deterministic check), so unlike a decode's logical-success it belongs in the
1163/// receipt.
1164#[derive(Clone, Debug, PartialEq)]
1165pub struct CompileReceipt {
1166    /// Content hash of the source (logical) circuit.
1167    pub source_hash: [u8; 32],
1168    /// Content hash of the compiled circuit (routed + its final qubit mapping).
1169    pub target_hash: [u8; 32],
1170    /// Content hash of the hardware coupling graph the circuit was routed onto.
1171    pub coupling_hash: [u8; 32],
1172    /// The equivalence method: `"clifford-tableau"`.
1173    pub method: String,
1174    /// The compiled circuit was proven equivalent to the source.
1175    pub equivalent: bool,
1176    /// SWAP gates the router inserted (informational, part of the work).
1177    pub swaps: u32,
1178    /// Classical work: tableau generator-updates performed. Deterministic.
1179    pub work: u64,
1180    /// Measured (or modeled) energy of the compile+check (µJ). Attested.
1181    pub joules_micro: u64,
1182    pub grant: GrantRef,
1183    pub parent_receipt_hash: Option<[u8; 32]>,
1184    pub signer_pubkey: [u8; 32],
1185    pub signer_id: String,
1186    pub sig: [u8; 64],
1187}
1188
1189impl CompileReceipt {
1190    fn leaves(&self) -> Vec<[u8; 32]> {
1191        vec![self.source_hash, self.target_hash, self.coupling_hash]
1192    }
1193    fn merkle(&self) -> [u8; 32] {
1194        merkle_root(&self.leaves())
1195    }
1196
1197    fn signing_payload(&self) -> Vec<u8> {
1198        self.payload_with(None)
1199    }
1200
1201    /// The signing payload: unlabelled (the legacy bytes), or labelled
1202    /// (`crate::quantum_energy`) — the `0x02` domain, with the class after
1203    /// the figure it labels.
1204    fn payload_with(&self, label: Option<&crate::quantum_energy::EnergyClass>) -> Vec<u8> {
1205        let mut o = crate::quantum_energy::labelled_domain(DOMAIN_CMP, label);
1206        o.extend_from_slice(&self.source_hash);
1207        o.extend_from_slice(&self.target_hash);
1208        o.extend_from_slice(&self.coupling_hash);
1209        o.extend_from_slice(self.method.as_bytes());
1210        o.push(0);
1211        o.push(self.equivalent as u8);
1212        o.extend_from_slice(&self.swaps.to_be_bytes());
1213        o.extend_from_slice(&self.work.to_be_bytes());
1214        o.extend_from_slice(&self.merkle());
1215        o.extend_from_slice(&self.joules_micro.to_be_bytes());
1216        crate::quantum_energy::write_label(label, &mut o);
1217        self.grant.append_to(&mut o);
1218        match self.parent_receipt_hash {
1219            Some(h) => {
1220                o.push(1);
1221                o.extend_from_slice(&h);
1222            }
1223            None => o.push(0),
1224        }
1225        o.extend_from_slice(&self.signer_pubkey);
1226        o.extend_from_slice(self.signer_id.as_bytes());
1227        o
1228    }
1229
1230    #[allow(clippy::too_many_arguments)]
1231    pub fn seal(
1232        signer: &SigningKey,
1233        signer_id: impl Into<String>,
1234        method: impl Into<String>,
1235        source_hash: [u8; 32],
1236        target_hash: [u8; 32],
1237        coupling_hash: [u8; 32],
1238        equivalent: bool,
1239        swaps: u32,
1240        work: u64,
1241        joules_micro: u64,
1242        grant: GrantRef,
1243        parent_receipt_hash: Option<[u8; 32]>,
1244    ) -> CompileReceipt {
1245        let mut r = CompileReceipt {
1246            source_hash,
1247            target_hash,
1248            coupling_hash,
1249            method: method.into(),
1250            equivalent,
1251            swaps,
1252            work,
1253            joules_micro,
1254            grant,
1255            parent_receipt_hash,
1256            signer_pubkey: signer.verifying_key().to_bytes(),
1257            signer_id: signer_id.into(),
1258            sig: [0u8; 64],
1259        };
1260        r.sig = signer.sign(&r.signing_payload()).to_bytes();
1261        r
1262    }
1263
1264    pub fn honors_budget(&self) -> bool {
1265        self.joules_micro <= self.grant.joule_ceiling_micro
1266    }
1267
1268    /// Every check [`Self::verify`] makes except the signature — shared with
1269    /// the labelled form (`crate::quantum_energy::Labelled`).
1270    fn body_verifies(&self) -> bool {
1271        if !self.honors_budget() {
1272            return false;
1273        }
1274        true
1275    }
1276
1277    /// Signature valid, joules within budget. A verifier who re-runs the
1278    /// deterministic equivalence check on the bound circuits can confirm the
1279    /// `equivalent` verdict independently.
1280    pub fn verify(&self) -> bool {
1281        if !self.body_verifies() {
1282            return false;
1283        }
1284        let Ok(k) = VerifyingKey::from_bytes(&self.signer_pubkey) else {
1285            return false;
1286        };
1287        k.verify(&self.signing_payload(), &Signature::from_bytes(&self.sig))
1288            .is_ok()
1289    }
1290
1291    pub fn source_matches(&self, bytes: &[u8]) -> bool {
1292        content_hash(bytes) == self.source_hash
1293    }
1294    pub fn target_matches(&self, bytes: &[u8]) -> bool {
1295        content_hash(bytes) == self.target_hash
1296    }
1297
1298    pub fn receipt_hash(&self) -> [u8; 32] {
1299        let mut h = blake3::Hasher::new();
1300        h.update(DOMAIN_CMP_ID);
1301        h.update(&self.signing_payload());
1302        h.update(&self.sig);
1303        *h.finalize().as_bytes()
1304    }
1305
1306    pub fn to_json(&self) -> String {
1307        let parent = match self.parent_receipt_hash {
1308            Some(h) => format!("\"{}\"", hx(&h)),
1309            None => "null".into(),
1310        };
1311        format!(
1312            "{{\"kind\":\"quantum-compile\",\"coupling_hash\":\"{}\",\"equivalent\":{},\
1313             \"grant\":{},\"joules_micro\":{},\"merkle_root\":\"{}\",\"method\":{},\
1314             \"parent_receipt_hash\":{},\"receipt_hash\":\"{}\",\"sig\":\"{}\",\
1315             \"signer_id\":{},\"signer_pubkey\":\"{}\",\"source_hash\":\"{}\",\"swaps\":{},\
1316             \"target_hash\":\"{}\",\"work\":{}}}",
1317            hx(&self.coupling_hash),
1318            self.equivalent,
1319            self.grant.to_json(),
1320            self.joules_micro,
1321            hx(&self.merkle()),
1322            serde_json::to_string(&self.method).unwrap(),
1323            parent,
1324            hx(&self.receipt_hash()),
1325            hx(&self.sig),
1326            serde_json::to_string(&self.signer_id).unwrap(),
1327            hx(&self.signer_pubkey),
1328            hx(&self.source_hash),
1329            self.swaps,
1330            hx(&self.target_hash),
1331            self.work,
1332        )
1333    }
1334
1335    pub fn from_json(s: &str) -> Option<CompileReceipt> {
1336        let v: serde_json::Value = serde_json::from_str(s).ok()?;
1337        let o = v.as_object()?;
1338        let parent = match o.get("parent_receipt_hash") {
1339            Some(serde_json::Value::String(s)) => Some(from_hex32(s)?),
1340            _ => None,
1341        };
1342        Some(CompileReceipt {
1343            source_hash: from_hex32(o.get("source_hash")?.as_str()?)?,
1344            target_hash: from_hex32(o.get("target_hash")?.as_str()?)?,
1345            coupling_hash: from_hex32(o.get("coupling_hash")?.as_str()?)?,
1346            method: o.get("method")?.as_str()?.to_owned(),
1347            equivalent: o.get("equivalent")?.as_bool()?,
1348            swaps: o.get("swaps")?.as_u64()? as u32,
1349            work: o.get("work")?.as_u64()?,
1350            joules_micro: o.get("joules_micro")?.as_u64()?,
1351            grant: GrantRef::from_json(o.get("grant")?)?,
1352            parent_receipt_hash: parent,
1353            signer_pubkey: from_hex32(o.get("signer_pubkey")?.as_str()?)?,
1354            signer_id: o.get("signer_id")?.as_str()?.to_owned(),
1355            sig: from_hex64(o.get("sig")?.as_str()?)?,
1356        })
1357    }
1358}
1359
1360// ---------------------------------------------------------------------------
1361// RearrangeReceipt
1362// ---------------------------------------------------------------------------
1363
1364/// A signed, joule-metered, grant-authorized record of a neutral-atom
1365/// REARRANGEMENT plan.
1366///
1367/// A neutral-atom processor loads atoms stochastically into an array of optical
1368/// traps — a random, ~half-full pattern — then uses mobile tweezers to rearrange
1369/// them into a **defect-free** target register before the computation. The plan
1370/// that does it (which atom moves to which site, in what order) is produced by a
1371/// combinatorial solver. This receipt
1372/// binds the initial occupancy, the target sites, and the move plan to a `success`
1373/// verdict (every target site filled) and the plan's cost — produced by a
1374/// **deterministic** solver (Hungarian / LSAP, minimizing total move distance),
1375/// byte-exact and reproducible.
1376///
1377/// `success` is what the planner DOES know (it re-checks the plan against the
1378/// bound configs deterministically), so it belongs in the receipt.
1379#[derive(Clone, Debug, PartialEq)]
1380pub struct RearrangeReceipt {
1381    /// Content hash of the initial (loaded) array occupancy.
1382    pub initial_hash: [u8; 32],
1383    /// Content hash of the target site set.
1384    pub target_hash: [u8; 32],
1385    /// Content hash of the move plan.
1386    pub plan_hash: [u8; 32],
1387    /// The solver: `"hungarian-lsap"`.
1388    pub method: String,
1389    /// The plan fills every target site from the available atoms.
1390    pub success: bool,
1391    /// Atoms actually relocated (moves of nonzero distance).
1392    pub atoms_moved: u32,
1393    /// Total move distance (grid units) — the plan's cost. Deterministic.
1394    pub total_distance: u64,
1395    /// Measured (or modeled) energy of the solve (µJ). Attested.
1396    pub joules_micro: u64,
1397    pub grant: GrantRef,
1398    pub parent_receipt_hash: Option<[u8; 32]>,
1399    pub signer_pubkey: [u8; 32],
1400    pub signer_id: String,
1401    pub sig: [u8; 64],
1402}
1403
1404impl RearrangeReceipt {
1405    fn merkle(&self) -> [u8; 32] {
1406        merkle_root(&[self.initial_hash, self.target_hash, self.plan_hash])
1407    }
1408
1409    fn signing_payload(&self) -> Vec<u8> {
1410        self.payload_with(None)
1411    }
1412
1413    /// The signing payload: unlabelled (the legacy bytes), or labelled
1414    /// (`crate::quantum_energy`) — the `0x02` domain, with the class after
1415    /// the figure it labels.
1416    fn payload_with(&self, label: Option<&crate::quantum_energy::EnergyClass>) -> Vec<u8> {
1417        let mut o = crate::quantum_energy::labelled_domain(DOMAIN_REA, label);
1418        o.extend_from_slice(&self.initial_hash);
1419        o.extend_from_slice(&self.target_hash);
1420        o.extend_from_slice(&self.plan_hash);
1421        o.extend_from_slice(self.method.as_bytes());
1422        o.push(0);
1423        o.push(self.success as u8);
1424        o.extend_from_slice(&self.atoms_moved.to_be_bytes());
1425        o.extend_from_slice(&self.total_distance.to_be_bytes());
1426        o.extend_from_slice(&self.merkle());
1427        o.extend_from_slice(&self.joules_micro.to_be_bytes());
1428        crate::quantum_energy::write_label(label, &mut o);
1429        self.grant.append_to(&mut o);
1430        match self.parent_receipt_hash {
1431            Some(h) => {
1432                o.push(1);
1433                o.extend_from_slice(&h);
1434            }
1435            None => o.push(0),
1436        }
1437        o.extend_from_slice(&self.signer_pubkey);
1438        o.extend_from_slice(self.signer_id.as_bytes());
1439        o
1440    }
1441
1442    #[allow(clippy::too_many_arguments)]
1443    pub fn seal(
1444        signer: &SigningKey,
1445        signer_id: impl Into<String>,
1446        method: impl Into<String>,
1447        initial_hash: [u8; 32],
1448        target_hash: [u8; 32],
1449        plan_hash: [u8; 32],
1450        success: bool,
1451        atoms_moved: u32,
1452        total_distance: u64,
1453        joules_micro: u64,
1454        grant: GrantRef,
1455        parent_receipt_hash: Option<[u8; 32]>,
1456    ) -> RearrangeReceipt {
1457        let mut r = RearrangeReceipt {
1458            initial_hash,
1459            target_hash,
1460            plan_hash,
1461            method: method.into(),
1462            success,
1463            atoms_moved,
1464            total_distance,
1465            joules_micro,
1466            grant,
1467            parent_receipt_hash,
1468            signer_pubkey: signer.verifying_key().to_bytes(),
1469            signer_id: signer_id.into(),
1470            sig: [0u8; 64],
1471        };
1472        r.sig = signer.sign(&r.signing_payload()).to_bytes();
1473        r
1474    }
1475
1476    pub fn honors_budget(&self) -> bool {
1477        self.joules_micro <= self.grant.joule_ceiling_micro
1478    }
1479
1480    /// Every check [`Self::verify`] makes except the signature — shared with
1481    /// the labelled form (`crate::quantum_energy::Labelled`).
1482    fn body_verifies(&self) -> bool {
1483        if !self.honors_budget() {
1484            return false;
1485        }
1486        true
1487    }
1488
1489    pub fn verify(&self) -> bool {
1490        if !self.body_verifies() {
1491            return false;
1492        }
1493        let Ok(k) = VerifyingKey::from_bytes(&self.signer_pubkey) else {
1494            return false;
1495        };
1496        k.verify(&self.signing_payload(), &Signature::from_bytes(&self.sig))
1497            .is_ok()
1498    }
1499
1500    pub fn plan_matches(&self, bytes: &[u8]) -> bool {
1501        content_hash(bytes) == self.plan_hash
1502    }
1503
1504    pub fn receipt_hash(&self) -> [u8; 32] {
1505        let mut h = blake3::Hasher::new();
1506        h.update(DOMAIN_REA_ID);
1507        h.update(&self.signing_payload());
1508        h.update(&self.sig);
1509        *h.finalize().as_bytes()
1510    }
1511
1512    pub fn to_json(&self) -> String {
1513        let parent = match self.parent_receipt_hash {
1514            Some(h) => format!("\"{}\"", hx(&h)),
1515            None => "null".into(),
1516        };
1517        format!(
1518            "{{\"kind\":\"quantum-rearrange\",\"atoms_moved\":{},\"grant\":{},\
1519             \"initial_hash\":\"{}\",\"joules_micro\":{},\"merkle_root\":\"{}\",\"method\":{},\
1520             \"parent_receipt_hash\":{},\"plan_hash\":\"{}\",\"receipt_hash\":\"{}\",\"sig\":\"{}\",\
1521             \"signer_id\":{},\"signer_pubkey\":\"{}\",\"success\":{},\"target_hash\":\"{}\",\
1522             \"total_distance\":{}}}",
1523            self.atoms_moved,
1524            self.grant.to_json(),
1525            hx(&self.initial_hash),
1526            self.joules_micro,
1527            hx(&self.merkle()),
1528            serde_json::to_string(&self.method).unwrap(),
1529            parent,
1530            hx(&self.plan_hash),
1531            hx(&self.receipt_hash()),
1532            hx(&self.sig),
1533            serde_json::to_string(&self.signer_id).unwrap(),
1534            hx(&self.signer_pubkey),
1535            self.success,
1536            hx(&self.target_hash),
1537            self.total_distance,
1538        )
1539    }
1540
1541    pub fn from_json(s: &str) -> Option<RearrangeReceipt> {
1542        let v: serde_json::Value = serde_json::from_str(s).ok()?;
1543        let o = v.as_object()?;
1544        let parent = match o.get("parent_receipt_hash") {
1545            Some(serde_json::Value::String(s)) => Some(from_hex32(s)?),
1546            _ => None,
1547        };
1548        Some(RearrangeReceipt {
1549            initial_hash: from_hex32(o.get("initial_hash")?.as_str()?)?,
1550            target_hash: from_hex32(o.get("target_hash")?.as_str()?)?,
1551            plan_hash: from_hex32(o.get("plan_hash")?.as_str()?)?,
1552            method: o.get("method")?.as_str()?.to_owned(),
1553            success: o.get("success")?.as_bool()?,
1554            atoms_moved: o.get("atoms_moved")?.as_u64()? as u32,
1555            total_distance: o.get("total_distance")?.as_u64()?,
1556            joules_micro: o.get("joules_micro")?.as_u64()?,
1557            grant: GrantRef::from_json(o.get("grant")?)?,
1558            parent_receipt_hash: parent,
1559            signer_pubkey: from_hex32(o.get("signer_pubkey")?.as_str()?)?,
1560            signer_id: o.get("signer_id")?.as_str()?.to_owned(),
1561            sig: from_hex64(o.get("sig")?.as_str()?)?,
1562        })
1563    }
1564}
1565
1566#[cfg(test)]
1567mod tests {
1568    use super::*;
1569
1570    fn key(s: u8) -> SigningKey {
1571        SigningKey::from_bytes(&[s; 32])
1572    }
1573
1574    fn evid() -> Vec<Evidence> {
1575        vec![Evidence {
1576            kind: "randomized_benchmarking".into(),
1577            blob_hash: content_hash(b"<HDF5 RB dataset bytes>"),
1578            summary: "1q RB fidelity 0.9992".into(),
1579        }]
1580    }
1581
1582    #[test]
1583    fn calibration_seals_and_verifies() {
1584        let grant = GrantRef {
1585            grant_hash: content_hash(b"grant-A"),
1586            capability: "quantum.calibrate".into(),
1587            joule_ceiling_micro: 5_000_000,
1588            funds_ceiling: Some(100),
1589        };
1590        let r = CalibrationReceipt::seal(
1591            &key(1), "did:key:lab", "example:lab3-device", "q3",
1592            content_hash(b"pulse-config-v7"), evid(), 4_200_000, grant, None,
1593        );
1594        assert!(r.verify());
1595        assert!(r.honors_budget());
1596        // the fidelity claim binds to real bytes
1597        assert!(r.evidence_matches(0, b"<HDF5 RB dataset bytes>"));
1598        assert!(!r.evidence_matches(0, b"<tampered dataset>"));
1599    }
1600
1601    #[test]
1602    fn over_budget_calibration_fails_verify() {
1603        // The gate ⊗ meter property: joules over the grant ceiling → invalid.
1604        let grant = GrantRef {
1605            grant_hash: [0u8; 32],
1606            capability: "quantum.calibrate".into(),
1607            joule_ceiling_micro: 1_000_000,
1608            funds_ceiling: None,
1609        };
1610        let r = CalibrationReceipt::seal(
1611            &key(2), "m", "dev", "q0", content_hash(b"cfg"), evid(),
1612            2_000_000, // 2 J measured vs 1 J ceiling
1613            grant, None,
1614        );
1615        assert!(!r.honors_budget());
1616        assert!(!r.verify(), "a run that blew its energy grant must not verify");
1617    }
1618
1619    #[test]
1620    fn tamper_breaks_calibration() {
1621        let mut r = CalibrationReceipt::seal(
1622            &key(3), "m", "dev", "q0", content_hash(b"cfg"), evid(),
1623            10, GrantRef::unbounded("quantum.calibrate"), None,
1624        );
1625        r.joules_micro += 1; // a signed field
1626        assert!(!r.verify());
1627    }
1628
1629    #[test]
1630    fn job_binds_to_calibration_state() {
1631        // Seal a calibration, then a job that ran under it; the cross-link
1632        // proves "this result ran on a device in this attested state".
1633        let cal = CalibrationReceipt::seal(
1634            &key(4), "lab", "dev", "q3", content_hash(b"cfg"), evid(),
1635            100, GrantRef::unbounded("quantum.calibrate"), None,
1636        );
1637        let job = JobReceipt::seal(
1638            &key(4), "lab", "example:qpu-a",
1639            content_hash(b"<QASM circuit>"), content_hash(b"{counts:{00:512,11:488}}"),
1640            Some(cal.receipt_hash()), 1000, 250, GrantRef::unbounded("quantum.job.run"), None,
1641        );
1642        assert!(job.verify());
1643        assert!(job.verify_against_calibration(&cal));
1644        assert!(job.result_matches(b"{counts:{00:512,11:488}}"));
1645
1646        // a job that names a DIFFERENT calibration state must fail the cross-link
1647        let other = CalibrationReceipt::seal(
1648            &key(4), "lab", "dev", "q3", content_hash(b"cfg2"), evid(),
1649            100, GrantRef::unbounded("quantum.calibrate"), None,
1650        );
1651        assert!(!job.verify_against_calibration(&other));
1652    }
1653
1654    #[test]
1655    fn over_budget_job_fails_verify() {
1656        let grant = GrantRef {
1657            grant_hash: [0u8; 32],
1658            capability: "quantum.job.run".into(),
1659            joule_ceiling_micro: 200,
1660            funds_ceiling: None,
1661        };
1662        let job = JobReceipt::seal(
1663            &key(5), "m", "sim", content_hash(b"c"), content_hash(b"r"),
1664            None, 100, 500, grant, None,
1665        );
1666        assert!(!job.verify());
1667    }
1668
1669    #[test]
1670    fn json_round_trips() {
1671        let cal = CalibrationReceipt::seal(
1672            &key(6), "did:key:z", "dev", "cz(q3,q4)", content_hash(b"cfg"), evid(),
1673            900, GrantRef { grant_hash: content_hash(b"g"), capability: "quantum.calibrate".into(), joule_ceiling_micro: 1_000_000, funds_ceiling: None },
1674            None,
1675        );
1676        assert_eq!(CalibrationReceipt::from_json(&cal.to_json()).unwrap(), cal);
1677
1678        let job = JobReceipt::seal(
1679            &key(6), "did:key:z", "example:qpu-a", content_hash(b"c"), content_hash(b"r"),
1680            Some(cal.receipt_hash()), 2048, 42, GrantRef::unbounded("quantum.job.run"), Some(cal.receipt_hash()),
1681        );
1682        let back = JobReceipt::from_json(&job.to_json()).unwrap();
1683        assert_eq!(back, job);
1684        assert!(back.verify());
1685    }
1686
1687    #[test]
1688    fn calibration_lineage_chains() {
1689        let g = GrantRef::unbounded("quantum.calibrate");
1690        let v1 = CalibrationReceipt::seal(&key(7), "m", "dev", "q0", content_hash(b"c1"), evid(), 10, g.clone(), None);
1691        let v2 = CalibrationReceipt::seal(&key(7), "m", "dev", "q0", content_hash(b"c2"), evid(), 12, g, Some(v1.receipt_hash()));
1692        assert!(v2.verify());
1693        assert_eq!(v2.parent_receipt_hash, Some(v1.receipt_hash()));
1694    }
1695
1696    #[test]
1697    fn mitigation_seals_and_binds_inputs() {
1698        // A ZNE step: raw counts in, mitigated estimate out, no learned model.
1699        let raw = b"<noise-scaled expectation table: (1.0,0.71),(1.5,0.63),(2.0,0.57)>";
1700        let r = MitigationReceipt::seal(
1701            &key(1), "did:key:lab", "wai.quantum.circuit", "zne.richardson", "ZZ",
1702            content_hash(raw), None,
1703            744_178,   // raw   0.71 · 2^20
1704            912_680,   // mit   0.87 · 2^20
1705            20_971,    // ±0.02 · 2^20
1706            48_000, 900_000, GrantRef::unbounded("quantum.mitigate"), None,
1707        );
1708        assert!(r.verify());
1709        assert!(r.input_matches(raw));
1710        assert!(!r.input_matches(b"<different shots>"));
1711        assert!(r.noise_model_hash.is_none());
1712    }
1713
1714    #[test]
1715    fn mitigation_binds_pinned_noise_model() {
1716        // A readout-inversion / PEC step binds WHICH noise model produced it.
1717        let raw = b"<measured counts>";
1718        let model = b"<readout assignment matrix A>";
1719        let r = MitigationReceipt::seal(
1720            &key(2), "lab", "ibm:torino", "readout.tensored", "<Z0>",
1721            content_hash(raw), Some(content_hash(model)),
1722            838_861, 1_048_576, 5_242, 32_000, 600_000,
1723            GrantRef::unbounded("quantum.mitigate"), None,
1724        );
1725        assert!(r.verify());
1726        assert!(r.noise_model_matches(model));
1727        assert!(!r.noise_model_matches(b"<a different device's matrix>"));
1728    }
1729
1730    #[test]
1731    fn mitigation_over_budget_and_tamper_fail() {
1732        let grant = GrantRef {
1733            grant_hash: [0u8; 32],
1734            capability: "quantum.mitigate".into(),
1735            joule_ceiling_micro: 500_000,
1736            funds_ceiling: None,
1737        };
1738        let over = MitigationReceipt::seal(
1739            &key(3), "m", "sim", "zne.linear", "X",
1740            content_hash(b"in"), None, 0, 100, 1, 100, 900_000, grant, None,
1741        );
1742        assert!(!over.verify(), "a mitigation that blew its energy grant must not verify");
1743
1744        let mut r = MitigationReceipt::seal(
1745            &key(3), "m", "sim", "zne.linear", "X",
1746            content_hash(b"in"), None, 0, 100, 1, 100, 10,
1747            GrantRef::unbounded("quantum.mitigate"), None,
1748        );
1749        r.mitigated_estimate_fx += 1; // rewrite the reported number
1750        assert!(!r.verify(), "tampering with the mitigated estimate must break the receipt");
1751    }
1752
1753    #[test]
1754    fn mitigation_json_round_trips() {
1755        let r = MitigationReceipt::seal(
1756            &key(4), "did:key:z", "wai.quantum.circuit", "shadow.pauli", "XYZ",
1757            content_hash(b"<snapshots>"), None,
1758            -262_144, -314_573, 15_728, 4096, 42,
1759            GrantRef { grant_hash: content_hash(b"g"), capability: "quantum.mitigate".into(), joule_ceiling_micro: 1_000_000, funds_ceiling: None },
1760            None,
1761        );
1762        let back = MitigationReceipt::from_json(&r.to_json()).unwrap();
1763        assert_eq!(back, r);
1764        assert!(back.verify());
1765        assert_eq!(back.raw_estimate_fx, -262_144); // signed round-trip
1766    }
1767
1768    #[test]
1769    fn decode_seals_and_binds() {
1770        let hmat = b"<parity-check matrix H_Z of the gross code>";
1771        let synd = b"<syndrome 001011...>";
1772        let corr = b"<correction 000010...>";
1773        let r = DecodeReceipt::seal(
1774            &key(1), "did:key:lab", "bb:[[72,12]]", "relay-bp.min-sum",
1775            content_hash(hmat), content_hash(synd), content_hash(corr),
1776            true, 3, 46_656, 900_000, GrantRef::unbounded("quantum.decode"), None,
1777        );
1778        assert!(r.verify());
1779        assert!(r.converged);
1780        assert!(r.correction_matches(corr));
1781        assert!(!r.correction_matches(b"<a different correction>"));
1782        assert!(r.code_matches(hmat));
1783    }
1784
1785    #[test]
1786    fn decode_over_budget_and_tamper_fail() {
1787        let grant = GrantRef {
1788            grant_hash: [0u8; 32],
1789            capability: "quantum.decode".into(),
1790            joule_ceiling_micro: 500_000,
1791            funds_ceiling: None,
1792        };
1793        let over = DecodeReceipt::seal(
1794            &key(2), "m", "toric:L5", "relay-bp.min-sum",
1795            content_hash(b"H"), content_hash(b"s"), content_hash(b"c"),
1796            true, 1, 100, 900_000, grant, None,
1797        );
1798        assert!(!over.verify(), "a decode that blew its energy grant must not verify");
1799
1800        let mut r = DecodeReceipt::seal(
1801            &key(2), "m", "toric:L5", "relay-bp.min-sum",
1802            content_hash(b"H"), content_hash(b"s"), content_hash(b"c"),
1803            true, 1, 100, 10, GrantRef::unbounded("quantum.decode"), None,
1804        );
1805        r.work_messages += 1; // rewrite the reported work
1806        assert!(!r.verify(), "tampering with the reported work must break the receipt");
1807    }
1808
1809    #[test]
1810    fn decode_json_round_trips() {
1811        let r = DecodeReceipt::seal(
1812            &key(3), "did:key:z", "toric:L7", "relay-bp.min-sum",
1813            content_hash(b"H"), content_hash(b"s"), content_hash(b"c"),
1814            false, 12, 1_234_567, 42,
1815            GrantRef { grant_hash: content_hash(b"g"), capability: "quantum.decode".into(), joule_ceiling_micro: 2_000_000, funds_ceiling: None },
1816            Some(content_hash(b"prev")),
1817        );
1818        let back = DecodeReceipt::from_json(&r.to_json()).unwrap();
1819        assert_eq!(back, r);
1820        assert!(back.verify());
1821        assert!(!back.converged); // bool round-trip
1822    }
1823
1824    #[test]
1825    fn compile_seals_and_binds() {
1826        let src = b"<logical circuit: H0 CX0,2 ...>";
1827        let tgt = b"<routed circuit + final map: H0 SWAP1,2 CX0,1 ...>";
1828        let cpl = b"<coupling: line 0-1-2-3-4>";
1829        let r = CompileReceipt::seal(
1830            &key(1), "did:key:lab", "clifford-tableau",
1831            content_hash(src), content_hash(tgt), content_hash(cpl),
1832            true, 3, 4096, 300_000, GrantRef::unbounded("quantum.compile"), None,
1833        );
1834        assert!(r.verify());
1835        assert!(r.equivalent);
1836        assert!(r.source_matches(src));
1837        assert!(r.target_matches(tgt));
1838        assert!(!r.target_matches(b"<a tampered routed circuit>"));
1839    }
1840
1841    #[test]
1842    fn compile_over_budget_and_tamper_fail() {
1843        let grant = GrantRef {
1844            grant_hash: [0u8; 32],
1845            capability: "quantum.compile".into(),
1846            joule_ceiling_micro: 100_000,
1847            funds_ceiling: None,
1848        };
1849        let over = CompileReceipt::seal(
1850            &key(2), "m", "clifford-tableau",
1851            content_hash(b"s"), content_hash(b"t"), content_hash(b"c"),
1852            true, 0, 10, 900_000, grant, None,
1853        );
1854        assert!(!over.verify(), "a compile that blew its energy grant must not verify");
1855
1856        let mut r = CompileReceipt::seal(
1857            &key(2), "m", "clifford-tableau",
1858            content_hash(b"s"), content_hash(b"t"), content_hash(b"c"),
1859            true, 0, 10, 5, GrantRef::unbounded("quantum.compile"), None,
1860        );
1861        r.equivalent = false; // flip the verdict
1862        assert!(!r.verify(), "tampering with the equivalence verdict must break the receipt");
1863    }
1864
1865    #[test]
1866    fn compile_json_round_trips() {
1867        let r = CompileReceipt::seal(
1868            &key(3), "did:key:z", "clifford-tableau",
1869            content_hash(b"s"), content_hash(b"t"), content_hash(b"c"),
1870            false, 7, 99_999, 21,
1871            GrantRef { grant_hash: content_hash(b"g"), capability: "quantum.compile".into(), joule_ceiling_micro: 1_000_000, funds_ceiling: None },
1872            None,
1873        );
1874        let back = CompileReceipt::from_json(&r.to_json()).unwrap();
1875        assert_eq!(back, r);
1876        assert!(back.verify());
1877        assert!(!back.equivalent);
1878    }
1879
1880    #[test]
1881    fn rearrange_seals_binds_and_json_round_trips() {
1882        let r = RearrangeReceipt::seal(
1883            &key(1), "did:key:lab", "hungarian-lsap",
1884            content_hash(b"<loaded 16x16, 58% fill>"),
1885            content_hash(b"<target 8x8 centered>"),
1886            content_hash(b"<moves: (12->20),(33->21),...>"),
1887            true, 41, 318, 450_000, GrantRef::unbounded("quantum.rearrange"), None,
1888        );
1889        assert!(r.verify());
1890        assert!(r.success);
1891        assert!(r.plan_matches(b"<moves: (12->20),(33->21),...>"));
1892        assert!(!r.plan_matches(b"<a different plan>"));
1893        let back = RearrangeReceipt::from_json(&r.to_json()).unwrap();
1894        assert_eq!(back, r);
1895        assert!(back.verify());
1896    }
1897
1898    #[test]
1899    fn rearrange_over_budget_and_tamper_fail() {
1900        let grant = GrantRef {
1901            grant_hash: [0u8; 32], capability: "quantum.rearrange".into(),
1902            joule_ceiling_micro: 100_000, funds_ceiling: None,
1903        };
1904        let over = RearrangeReceipt::seal(
1905            &key(2), "m", "hungarian-lsap",
1906            content_hash(b"i"), content_hash(b"t"), content_hash(b"p"),
1907            true, 10, 50, 900_000, grant, None,
1908        );
1909        assert!(!over.verify(), "a solve that blew its energy grant must not verify");
1910
1911        let mut r = RearrangeReceipt::seal(
1912            &key(2), "m", "hungarian-lsap",
1913            content_hash(b"i"), content_hash(b"t"), content_hash(b"p"),
1914            true, 10, 50, 5, GrantRef::unbounded("quantum.rearrange"), None,
1915        );
1916        r.success = false; // flip the verdict
1917        assert!(!r.verify(), "tampering with the success verdict must break the receipt");
1918    }
1919}
1920
1921crate::quantum_energy::labellable!(CalibrationReceipt, DOMAIN_CAL_ID);
1922
1923crate::quantum_energy::labellable!(JobReceipt, DOMAIN_JOB_ID);
1924
1925crate::quantum_energy::labellable!(MitigationReceipt, DOMAIN_MIT_ID);
1926
1927crate::quantum_energy::labellable!(DecodeReceipt, DOMAIN_DEC_ID);
1928
1929crate::quantum_energy::labellable!(CompileReceipt, DOMAIN_CMP_ID);
1930
1931crate::quantum_energy::labellable!(RearrangeReceipt, DOMAIN_REA_ID);