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