use crate::quantum::{circuit_hash, Circuit, 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> {
let mut o = DOMAIN_RECEIPT.to_vec();
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());
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)
}
pub fn verify(&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;
}
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 {
if circuit_hash(circuit) != 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;
}
self.verify()
}
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: u("n_qubits")? as u8,
n_ops: u("n_ops")? as u32,
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()));
}
}