1use crate::merkle::merkle_root;
34use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
35
36pub const CAP_QUANTUM_CALIBRATION: &str = "wai.quantum.calibration";
39pub const CAP_QUANTUM_JOB: &str = "wai.quantum.job";
42pub const CAP_QUANTUM_MITIGATE: &str = "wai.quantum.mitigate";
46pub const CAP_QUANTUM_DECODE: &str = "wai.quantum.decode";
50pub const CAP_QUANTUM_COMPILE: &str = "wai.quantum.compile";
53pub const CAP_QUANTUM_REARRANGE: &str = "wai.quantum.rearrange";
56pub 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
100pub fn content_hash(bytes: &[u8]) -> [u8; 32] {
104 *blake3::hash(bytes).as_bytes()
105}
106
107#[derive(Clone, Debug, PartialEq, Eq)]
112pub struct GrantRef {
113 pub grant_hash: [u8; 32],
115 pub capability: String,
117 pub joule_ceiling_micro: u64,
119 pub funds_ceiling: Option<u64>,
121}
122
123impl GrantRef {
124 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); 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#[derive(Clone, Debug, PartialEq, Eq)]
181pub struct Evidence {
182 pub kind: String,
184 pub blob_hash: [u8; 32],
186 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#[derive(Clone, Debug, PartialEq)]
209pub struct CalibrationReceipt {
210 pub device_id: String,
212 pub target: String,
214 pub config_hash: [u8; 32],
216 pub evidence: Vec<Evidence>,
218 pub merkle_root: [u8; 32],
220 pub joules_micro: u64,
222 pub grant: GrantRef,
224 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 #[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 pub fn honors_budget(&self) -> bool {
295 self.joules_micro <= self.grant.joule_ceiling_micro
296 }
297
298 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 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 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#[derive(Clone, Debug, PartialEq)]
409pub struct JobReceipt {
410 pub backend_id: String,
412 pub circuit_hash: [u8; 32],
414 pub calibration_ref: Option<[u8; 32]>,
418 pub result_hash: [u8; 32],
420 pub shots: u64,
422 pub merkle_root: [u8; 32],
424 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 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 pub fn verify_against_calibration(&self, cal: &CalibrationReceipt) -> bool {
528 self.calibration_ref == Some(cal.receipt_hash()) && cal.verify() && self.verify()
529 }
530
531 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#[derive(Clone, Debug, PartialEq)]
622pub struct MitigationReceipt {
623 pub backend_id: String,
625 pub method: String,
628 pub observable: String,
630 pub input_hash: [u8; 32],
633 pub noise_model_hash: Option<[u8; 32]>,
637 pub raw_estimate_fx: i64,
639 pub mitigated_estimate_fx: i64,
641 pub error_bar_fx: i64,
643 pub shots: u64,
645 pub merkle_root: [u8; 32],
648 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 #[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 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 pub fn input_matches(&self, input_bytes: &[u8]) -> bool {
779 content_hash(input_bytes) == self.input_hash
780 }
781
782 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#[derive(Clone, Debug, PartialEq)]
884pub struct DecodeReceipt {
885 pub code_id: String,
887 pub decoder: String,
889 pub check_matrix_hash: [u8; 32],
891 pub syndrome_hash: [u8; 32],
893 pub correction_hash: [u8; 32],
895 pub converged: bool,
897 pub rounds: u32,
899 pub work_messages: u64,
902 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 #[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 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 pub fn correction_matches(&self, correction_bytes: &[u8]) -> bool {
1007 content_hash(correction_bytes) == self.correction_hash
1008 }
1009
1010 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#[derive(Clone, Debug, PartialEq)]
1100pub struct CompileReceipt {
1101 pub source_hash: [u8; 32],
1103 pub target_hash: [u8; 32],
1105 pub coupling_hash: [u8; 32],
1107 pub method: String,
1109 pub equivalent: bool,
1111 pub swaps: u32,
1113 pub work: u64,
1115 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 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#[derive(Clone, Debug, PartialEq)]
1298pub struct RearrangeReceipt {
1299 pub initial_hash: [u8; 32],
1301 pub target_hash: [u8; 32],
1303 pub plan_hash: [u8; 32],
1305 pub method: String,
1307 pub success: bool,
1309 pub atoms_moved: u32,
1311 pub total_distance: u64,
1313 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 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 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, 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; assert!(!r.verify());
1528 }
1529
1530 #[test]
1531 fn job_binds_to_calibration_state() {
1532 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 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 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, 912_680, 20_971, 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 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; 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); }
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; 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); }
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; 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; assert!(!r.verify(), "tampering with the success verdict must break the receipt");
1819 }
1820}