use crate::quantum::{circuit_hash, Circuit, Gateset, Measure, QuantumError};
use crate::merkle::merkle_root;
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
const DOMAIN_RECEIPT: &[u8] = b"wai:quantum-receipt\x01";
const DOMAIN_RECEIPT_ID: &[u8] = b"wai:quantum-receipt-id\x01";
fn hx(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
fn from_hex32(s: &str) -> Option<[u8; 32]> {
if s.len() != 64 || !s.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
let mut out = [0u8; 32];
for (i, c) in s.as_bytes().chunks(2).enumerate() {
out[i] = u8::from_str_radix(std::str::from_utf8(c).ok()?, 16).ok()?;
}
Some(out)
}
fn from_hex64(s: &str) -> Option<[u8; 64]> {
if s.len() != 128 || !s.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
let mut out = [0u8; 64];
for (i, c) in s.as_bytes().chunks(2).enumerate() {
out[i] = u8::from_str_radix(std::str::from_utf8(c).ok()?, 16).ok()?;
}
Some(out)
}
pub fn work_amp_updates(n_qubits: u8, n_ops: u32) -> u64 {
(n_ops as u64).saturating_mul(1u64 << n_qubits.min(63))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MeasureRecord {
pub seed: u64,
pub shots: u64,
pub histogram_hash: [u8; 32],
}
#[derive(Clone, Debug, PartialEq)]
pub struct QuantumReceipt {
pub circuit_hash: [u8; 32],
pub statevector_hash: [u8; 32],
pub n_qubits: u8,
pub n_ops: u32,
pub measurement: Option<MeasureRecord>,
pub merkle_root: [u8; 32],
pub work_amp_updates: u64,
pub joules_micro: u64,
pub parent_receipt_hash: Option<[u8; 32]>,
pub signer_pubkey: [u8; 32],
pub signer_id: String,
pub sig: [u8; 64],
}
impl QuantumReceipt {
fn leaves(&self) -> Vec<[u8; 32]> {
let mut v = vec![self.circuit_hash, self.statevector_hash];
if let Some(m) = &self.measurement {
v.push(m.histogram_hash);
}
v
}
fn signing_payload(&self) -> Vec<u8> {
self.payload_with(None)
}
fn payload_with(&self, label: Option<&crate::quantum_energy::EnergyClass>) -> Vec<u8> {
let mut o = crate::quantum_energy::labelled_domain(DOMAIN_RECEIPT, label);
o.extend_from_slice(&self.circuit_hash);
o.extend_from_slice(&self.statevector_hash);
o.push(self.n_qubits);
o.extend_from_slice(&self.n_ops.to_be_bytes());
o.extend_from_slice(&self.merkle_root);
o.extend_from_slice(&self.work_amp_updates.to_be_bytes());
o.extend_from_slice(&self.joules_micro.to_be_bytes());
crate::quantum_energy::write_label(label, &mut o);
match &self.measurement {
Some(m) => {
o.push(1);
o.extend_from_slice(&m.seed.to_be_bytes());
o.extend_from_slice(&m.shots.to_be_bytes());
o.extend_from_slice(&m.histogram_hash);
}
None => o.push(0),
}
match self.parent_receipt_hash {
Some(h) => {
o.push(1);
o.extend_from_slice(&h);
}
None => o.push(0),
}
o.extend_from_slice(&self.signer_pubkey);
o.extend_from_slice(self.signer_id.as_bytes());
o
}
pub fn seal(
signer: &SigningKey,
signer_id: impl Into<String>,
circuit: &Circuit,
measure: Option<Measure>,
joules_micro: u64,
parent_receipt_hash: Option<[u8; 32]>,
) -> Result<QuantumReceipt, QuantumError> {
let sv = circuit.simulate()?;
let statevector_hash = sv.statevector_hash();
let measurement = measure.map(|m| MeasureRecord {
seed: m.seed,
shots: m.shots,
histogram_hash: sv.histogram_hash(m.seed, m.shots),
});
let mut r = QuantumReceipt {
circuit_hash: circuit_hash(circuit)?,
statevector_hash,
n_qubits: circuit.n_qubits,
n_ops: circuit.ops.len() as u32,
measurement,
merkle_root: [0u8; 32],
work_amp_updates: work_amp_updates(circuit.n_qubits, circuit.ops.len() as u32),
joules_micro,
parent_receipt_hash,
signer_pubkey: signer.verifying_key().to_bytes(),
signer_id: signer_id.into(),
sig: [0u8; 64],
};
r.merkle_root = merkle_root(&r.leaves());
r.sig = signer.sign(&r.signing_payload()).to_bytes();
Ok(r)
}
fn body_verifies(&self) -> bool {
if merkle_root(&self.leaves()) != self.merkle_root {
return false;
}
if self.work_amp_updates != work_amp_updates(self.n_qubits, self.n_ops) {
return false;
}
true
}
pub fn verify(&self) -> bool {
if !self.body_verifies() {
return false;
}
let Ok(k) = VerifyingKey::from_bytes(&self.signer_pubkey) else {
return false;
};
k.verify(&self.signing_payload(), &Signature::from_bytes(&self.sig))
.is_ok()
}
pub fn verify_reconstruction(&self, circuit: &Circuit) -> bool {
self.reconstruction_matches(circuit) && self.verify()
}
pub fn gateset_of(&self, circuit: &Circuit) -> Option<Gateset> {
let mut twin = circuit.clone();
Gateset::ALL.into_iter().find(|&gs| {
twin.gateset = gs;
circuit_hash(&twin).ok() == Some(self.circuit_hash)
})
}
pub fn gateset_note(&self, circuit: &Circuit) -> Option<String> {
let gs = self.gateset_of(circuit).filter(|&gs| gs != circuit.gateset)?;
let (name, variant) = match gs {
Gateset::V1 => ("circuit/1", "V1"),
Gateset::V2 => ("circuit2", "V2"),
};
Some(format!("this is a {name} receipt; rebuild with Gateset::{variant}"))
}
pub(crate) fn reconstruction_matches(&self, circuit: &Circuit) -> bool {
if circuit_hash(circuit).ok() != Some(self.circuit_hash) {
return false;
}
if circuit.n_qubits != self.n_qubits || circuit.ops.len() as u32 != self.n_ops {
return false;
}
let Ok(sv) = circuit.simulate() else {
return false;
};
if sv.statevector_hash() != self.statevector_hash {
return false;
}
if let Some(m) = &self.measurement
&& sv.histogram_hash(m.seed, m.shots) != m.histogram_hash
{
return false;
}
true
}
pub fn receipt_hash(&self) -> [u8; 32] {
let mut h = blake3::Hasher::new();
h.update(DOMAIN_RECEIPT_ID);
h.update(&self.signing_payload());
h.update(&self.sig);
*h.finalize().as_bytes()
}
pub fn to_json(&self) -> String {
let parent = match self.parent_receipt_hash {
Some(h) => format!("\"{}\"", hx(&h)),
None => "null".into(),
};
let measurement = match &self.measurement {
Some(m) => format!(
"{{\"histogram_hash\":\"{}\",\"seed\":{},\"shots\":{}}}",
hx(&m.histogram_hash),
m.seed,
m.shots
),
None => "null".into(),
};
format!(
"{{\"kind\":\"quantum-circuit\",\"circuit_hash\":\"{}\",\"joules_micro\":{},\
\"measurement\":{},\"n_ops\":{},\"n_qubits\":{},\"parent_receipt_hash\":{},\
\"receipt_hash\":\"{}\",\"root_hash\":\"{}\",\"sig\":\"{}\",\"signer_id\":{},\
\"signer_pubkey\":\"{}\",\"statevector_hash\":\"{}\",\"work_amp_updates\":{}}}",
hx(&self.circuit_hash),
self.joules_micro,
measurement,
self.n_ops,
self.n_qubits,
parent,
hx(&self.receipt_hash()),
hx(&self.merkle_root),
hx(&self.sig),
serde_json::to_string(&self.signer_id).unwrap(),
hx(&self.signer_pubkey),
hx(&self.statevector_hash),
self.work_amp_updates,
)
}
pub fn from_json(s: &str) -> Option<QuantumReceipt> {
let v: serde_json::Value = serde_json::from_str(s).ok()?;
let o = v.as_object()?;
let u = |k: &str| o.get(k).and_then(|x| x.as_u64());
let measurement = match o.get("measurement") {
Some(serde_json::Value::Object(m)) => Some(MeasureRecord {
seed: m.get("seed")?.as_u64()?,
shots: m.get("shots")?.as_u64()?,
histogram_hash: from_hex32(m.get("histogram_hash")?.as_str()?)?,
}),
_ => None,
};
let parent = match o.get("parent_receipt_hash") {
Some(serde_json::Value::String(s)) => Some(from_hex32(s)?),
_ => None,
};
Some(QuantumReceipt {
circuit_hash: from_hex32(o.get("circuit_hash")?.as_str()?)?,
statevector_hash: from_hex32(o.get("statevector_hash")?.as_str()?)?,
n_qubits: u8::try_from(u("n_qubits")?).ok()?,
n_ops: u32::try_from(u("n_ops")?).ok()?,
measurement,
merkle_root: from_hex32(o.get("root_hash")?.as_str()?)?,
work_amp_updates: u("work_amp_updates")?,
joules_micro: u("joules_micro")?,
parent_receipt_hash: parent,
signer_pubkey: from_hex32(o.get("signer_pubkey")?.as_str()?)?,
signer_id: o.get("signer_id")?.as_str()?.to_owned(),
sig: from_hex64(o.get("sig")?.as_str()?)?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::quantum::Circuit;
fn key(s: u8) -> SigningKey {
SigningKey::from_bytes(&[s; 32])
}
#[test]
fn seals_verifies_and_reconstructs() {
let mut c = Circuit::new(3);
c.h(0).cx(0, 1).cx(1, 2); let r =
QuantumReceipt::seal(&key(1), "did:key:test", &c, None, 117_000, None).unwrap();
assert!(r.verify());
assert!(r.verify_reconstruction(&c));
assert_eq!(r.work_amp_updates, 24);
assert_eq!(r.n_qubits, 3);
assert_eq!(r.n_ops, 3);
assert_eq!(r.joules_micro, 117_000);
}
#[test]
fn measurement_histogram_is_vouched() {
let mut c = Circuit::new(2);
c.h(0).cx(0, 1); let m = Measure { seed: 0xC0FFEE, shots: 10_000 };
let r = QuantumReceipt::seal(&key(2), "m", &c, Some(m), 0, None).unwrap();
assert!(r.verify());
assert!(r.verify_reconstruction(&c));
let mr = r.measurement.expect("measurement present");
assert_eq!(mr.seed, 0xC0FFEE);
assert_eq!(mr.shots, 10_000);
}
#[test]
fn tamper_breaks_verification() {
let mut c = Circuit::new(2);
c.h(0).cx(0, 1);
let mut r = QuantumReceipt::seal(&key(3), "m", &c, None, 42, None).unwrap();
r.joules_micro = 43;
assert!(!r.verify());
}
#[test]
fn a_lie_about_the_statevector_is_caught_by_resimulation() {
let mut c = Circuit::new(2);
c.h(0).cx(0, 1);
let honest = QuantumReceipt::seal(&key(4), "m", &c, None, 0, None).unwrap();
let mut other = Circuit::new(2);
other.h(0); assert!(!honest.verify_reconstruction(&other));
}
#[test]
fn json_round_trips() {
let mut c = Circuit::new(3);
c.h(0).cx(0, 1).cx(1, 2);
let m = Measure { seed: 7, shots: 512 };
let r = QuantumReceipt::seal(&key(5), "did:key:z6Mk", &c, Some(m), 900, None).unwrap();
let back = QuantumReceipt::from_json(&r.to_json()).expect("parse");
assert_eq!(r, back);
assert!(back.verify());
}
#[test]
fn chains_to_a_parent() {
let mut c = Circuit::new(2);
c.h(0).cx(0, 1);
let root = QuantumReceipt::seal(&key(6), "m", &c, None, 10, None).unwrap();
let mut c2 = Circuit::new(2);
c2.h(0).cx(0, 1).z(1); let child =
QuantumReceipt::seal(&key(6), "m", &c2, None, 12, Some(root.receipt_hash()))
.unwrap();
assert!(child.verify());
assert_eq!(child.parent_receipt_hash, Some(root.receipt_hash()));
}
fn ghz3(gs: Gateset) -> Circuit {
let mut c = Circuit::with_gateset(3, gs);
c.h(0).cx(0, 1).cx(1, 2);
c
}
#[test]
fn a_receipt_reconstructs_only_under_its_own_gateset() {
let m = Measure { seed: 7, shots: 1024 };
for gs in Gateset::ALL {
let r = QuantumReceipt::seal(&key(7), "m", &ghz3(gs), Some(m), 0, None).unwrap();
assert!(r.verify_reconstruction(&ghz3(gs)));
assert_eq!(r.gateset_of(&ghz3(gs)), Some(gs));
assert_eq!(r.gateset_note(&ghz3(gs)), None);
for other in Gateset::ALL.into_iter().filter(|&o| o != gs) {
let twin = ghz3(other);
assert!(!r.verify_reconstruction(&twin), "{gs:?} receipt against its {other:?} twin");
assert_eq!(r.gateset_of(&twin), Some(gs));
assert!(r.gateset_note(&twin).is_some());
}
}
let v1 = QuantumReceipt::seal(&key(7), "m", &ghz3(Gateset::V1), None, 0, None).unwrap();
let v2_twin = ghz3(Gateset::V2);
assert!(!v1.verify_reconstruction(&v2_twin));
assert_eq!(v1.gateset_of(&v2_twin), Some(Gateset::V1));
assert_eq!(v1.gateset_note(&v2_twin).as_deref(), Some("this is a circuit/1 receipt; rebuild with Gateset::V1"));
let v2 = QuantumReceipt::seal(&key(7), "m", &v2_twin, None, 0, None).unwrap();
assert_eq!(v2.gateset_note(&ghz3(Gateset::V1)).as_deref(), Some("this is a circuit2 receipt; rebuild with Gateset::V2"));
let clifford = |gs| {
let mut c = Circuit::with_gateset(2, gs);
c.x(0).cx(0, 1);
c
};
assert_eq!(clifford(Gateset::V1).simulate().unwrap(), clifford(Gateset::V2).simulate().unwrap());
let r = QuantumReceipt::seal(&key(7), "m", &clifford(Gateset::V1), None, 0, None).unwrap();
assert!(r.verify_reconstruction(&clifford(Gateset::V1)));
assert!(!r.verify_reconstruction(&clifford(Gateset::V2)));
let mut unrelated = Circuit::with_gateset(3, Gateset::V1);
unrelated.h(1);
assert_eq!(v1.gateset_of(&unrelated), None);
assert_eq!(v1.gateset_note(&unrelated), None);
let mut repeated = Circuit::with_gateset(3, Gateset::V1);
repeated.ops.push(crate::quantum::Gate { base: crate::quantum::BaseGate::X, controls: vec![0, 0], target: 1, param: 0 });
let r = QuantumReceipt::seal(&key(7), "m", &repeated, None, 0, None).unwrap();
let mut repeated_v2 = repeated.clone();
repeated_v2.gateset = Gateset::V2;
assert!(!r.verify_reconstruction(&repeated_v2));
assert_eq!(r.gateset_of(&repeated_v2), Some(Gateset::V1));
}
#[test]
fn a_cross_gateset_forgery_is_rejected() {
let forger = key(9);
let (seed, shots) = (7, 1024);
for (named, simulated) in [(Gateset::V1, Gateset::V2), (Gateset::V2, Gateset::V1)] {
let mut r = QuantumReceipt::seal(&forger, "m", &ghz3(named), Some(Measure { seed, shots }), 0, None).unwrap();
let sv = ghz3(simulated).simulate().unwrap();
assert_ne!(sv.statevector_hash(), r.statevector_hash);
r.statevector_hash = sv.statevector_hash();
r.measurement = Some(MeasureRecord { seed, shots, histogram_hash: sv.histogram_hash(seed, shots) });
r.merkle_root = merkle_root(&r.leaves());
r.sig = forger.sign(&r.signing_payload()).to_bytes();
assert!(r.verify(), "the forgery is well signed");
for gs in Gateset::ALL {
assert!(!r.verify_reconstruction(&ghz3(gs)), "named {named:?}, simulated {simulated:?}, checked {gs:?}");
}
}
}
#[test]
fn json_refuses_counts_that_do_not_fit() {
let r = QuantumReceipt::seal(&key(5), "m", &ghz3(Gateset::V2), None, 0, None).unwrap();
let j = r.to_json();
assert!(j.contains("\"n_qubits\":3,") && j.contains("\"n_ops\":3,"), "{j}");
assert!(QuantumReceipt::from_json(&j.replace("\"n_qubits\":3,", "\"n_qubits\":258,")).is_none());
assert!(QuantumReceipt::from_json(&j.replace("\"n_qubits\":3,", "\"n_qubits\":256,")).is_none());
assert!(QuantumReceipt::from_json(&j.replace("\"n_ops\":3,", "\"n_ops\":4294967299,")).is_none());
let widest = j.replace("\"n_qubits\":3,", "\"n_qubits\":255,").replace("\"n_ops\":3,", "\"n_ops\":4294967295,");
let back = QuantumReceipt::from_json(&widest).expect("fits");
assert_eq!((back.n_qubits, back.n_ops), (255, u32::MAX));
}
}
crate::quantum_energy::labellable!(QuantumReceipt, DOMAIN_RECEIPT_ID);