1use crate::quantum_ops::{content_hash, CompileReceipt, GrantRef};
29use ed25519_dalek::SigningKey;
30
31fn splitmix64(state: &mut u64) -> u64 {
32 *state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
33 let mut z = *state;
34 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
35 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
36 z ^ (z >> 31)
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum Gate {
46 H(u32),
47 S(u32),
48 Cx(u32, u32),
49 Swap(u32, u32),
50}
51
52impl Gate {
53 fn tag(&self) -> u8 {
54 match self {
55 Gate::H(_) => 1,
56 Gate::S(_) => 2,
57 Gate::Cx(_, _) => 3,
58 Gate::Swap(_, _) => 4,
59 }
60 }
61 fn qubits(&self) -> (u32, u32) {
62 match *self {
63 Gate::H(q) | Gate::S(q) => (q, q),
64 Gate::Cx(a, b) | Gate::Swap(a, b) => (a, b),
65 }
66 }
67}
68
69pub fn circuit_bytes(gates: &[Gate], n: u32) -> Vec<u8> {
71 let mut b = Vec::with_capacity(gates.len() * 9 + 8);
72 b.extend_from_slice(b"wai:qc-circuit\x01");
73 b.extend_from_slice(&n.to_le_bytes());
74 for g in gates {
75 let (p, q) = g.qubits();
76 b.push(g.tag());
77 b.extend_from_slice(&p.to_le_bytes());
78 b.extend_from_slice(&q.to_le_bytes());
79 }
80 b
81}
82
83pub fn routed_bytes(gates: &[Gate], n: u32, final_map: &[u32]) -> Vec<u8> {
85 let mut b = circuit_bytes(gates, n);
86 b.extend_from_slice(b"\x01map\x01");
87 for &m in final_map {
88 b.extend_from_slice(&m.to_le_bytes());
89 }
90 b
91}
92
93#[derive(Clone, Debug, PartialEq, Eq)]
102pub struct Tableau {
103 pub n: u32,
104 rows: Vec<(u32, u32, u8)>,
106 pub work: u64,
108}
109
110#[inline]
111fn bit(v: u32, q: u32) -> u32 {
112 (v >> q) & 1
113}
114#[inline]
115fn setbit(v: u32, q: u32, b: u32) -> u32 {
116 (v & !(1 << q)) | (b << q)
117}
118
119impl Tableau {
120 pub fn identity(n: u32) -> Tableau {
122 let mut rows = Vec::with_capacity(2 * n as usize);
123 for i in 0..n {
124 rows.push((1 << i, 0, 0)); }
126 for i in 0..n {
127 rows.push((0, 1 << i, 0)); }
129 Tableau { n, rows, work: 0 }
130 }
131
132 fn apply(&mut self, g: &Gate) {
133 match *g {
134 Gate::H(q) => {
135 for r in self.rows.iter_mut() {
136 let (x, z, s) = *r;
137 let xq = bit(x, q);
138 let zq = bit(z, q);
139 let ns = s ^ (xq & zq) as u8;
140 *r = (setbit(x, q, zq), setbit(z, q, xq), ns);
141 }
142 }
143 Gate::S(q) => {
144 for r in self.rows.iter_mut() {
145 let (x, z, s) = *r;
146 let xq = bit(x, q);
147 let zq = bit(z, q);
148 let ns = s ^ (xq & zq) as u8;
149 *r = (x, z ^ (xq << q), ns);
150 }
151 }
152 Gate::Cx(c, t) => {
153 for r in self.rows.iter_mut() {
154 let (x, z, s) = *r;
155 let xc = bit(x, c);
156 let zc = bit(z, c);
157 let xt = bit(x, t);
158 let zt = bit(z, t);
159 let ns = s ^ (xc & zt & (xt ^ zc ^ 1)) as u8;
160 *r = (x ^ (xc << t), z ^ (zt << c), ns);
161 }
162 }
163 Gate::Swap(a, b) => {
164 for r in self.rows.iter_mut() {
165 let (mut x, mut z, s) = *r;
166 let xa = bit(x, a);
167 let xb = bit(x, b);
168 x = setbit(setbit(x, a, xb), b, xa);
169 let za = bit(z, a);
170 let zb = bit(z, b);
171 z = setbit(setbit(z, a, zb), b, za);
172 *r = (x, z, s);
173 }
174 }
175 }
176 self.work += self.rows.len() as u64;
177 }
178
179 pub fn of(gates: &[Gate], n: u32) -> Tableau {
181 let mut t = Tableau::identity(n);
182 for g in gates {
183 t.apply(g);
184 }
185 t
186 }
187
188 pub fn equivalent(&self, other: &Tableau) -> bool {
190 self.n == other.n && self.rows == other.rows
191 }
192}
193
194#[derive(Clone, Debug)]
200pub struct Coupling {
201 pub n: u32,
202 adj: Vec<Vec<u32>>,
203}
204
205impl Coupling {
206 pub fn from_edges(n: u32, edges: &[(u32, u32)]) -> Coupling {
207 let mut adj = vec![Vec::new(); n as usize];
208 for &(a, b) in edges {
209 adj[a as usize].push(b);
210 adj[b as usize].push(a);
211 }
212 Coupling { n, adj }
213 }
214 pub fn line(n: u32) -> Coupling {
216 let e: Vec<(u32, u32)> = (0..n.saturating_sub(1)).map(|i| (i, i + 1)).collect();
217 Coupling::from_edges(n, &e)
218 }
219 pub fn ring(n: u32) -> Coupling {
221 let mut e: Vec<(u32, u32)> = (0..n.saturating_sub(1)).map(|i| (i, i + 1)).collect();
222 if n > 2 {
223 e.push((n - 1, 0));
224 }
225 Coupling::from_edges(n, &e)
226 }
227 fn coupled(&self, a: u32, b: u32) -> bool {
228 self.adj[a as usize].contains(&b)
229 }
230 fn path(&self, a: u32, b: u32) -> Vec<u32> {
233 if a == b {
234 return vec![a];
235 }
236 let n = self.n as usize;
237 let mut prev = vec![u32::MAX; n];
238 let mut seen = vec![false; n];
239 let mut q = std::collections::VecDeque::new();
240 q.push_back(a);
241 seen[a as usize] = true;
242 while let Some(u) = q.pop_front() {
243 if u == b {
244 break;
245 }
246 for &v in &self.adj[u as usize] {
247 if !seen[v as usize] {
248 seen[v as usize] = true;
249 prev[v as usize] = u;
250 q.push_back(v);
251 }
252 }
253 }
254 let mut path = vec![b];
255 let mut cur = b;
256 while cur != a {
257 cur = prev[cur as usize];
258 if cur == u32::MAX {
259 return Vec::new(); }
261 path.push(cur);
262 }
263 path.reverse();
264 path
265 }
266
267 pub fn bytes(&self) -> Vec<u8> {
268 let mut b = Vec::new();
269 b.extend_from_slice(b"wai:qc-coupling\x01");
270 b.extend_from_slice(&self.n.to_le_bytes());
271 for (a, nb) in self.adj.iter().enumerate() {
272 for &c in nb {
273 if (a as u32) < c {
274 b.extend_from_slice(&(a as u32).to_le_bytes());
275 b.extend_from_slice(&c.to_le_bytes());
276 }
277 }
278 }
279 b
280 }
281}
282
283#[derive(Clone, Debug)]
285pub struct RouteResult {
286 pub routed: Vec<Gate>,
288 pub final_map: Vec<u32>,
290 pub swaps: u32,
292}
293
294pub fn route(gates: &[Gate], coupling: &Coupling) -> RouteResult {
297 let n = coupling.n;
298 let mut phys: Vec<u32> = (0..n).collect();
300 let mut logq: Vec<u32> = (0..n).collect();
301 let mut routed = Vec::new();
302 let mut swaps = 0u32;
303
304 let do_swap = |routed: &mut Vec<Gate>, phys: &mut [u32], logq: &mut [u32], p: u32, q: u32| {
305 routed.push(Gate::Swap(p, q));
306 let (lp, lq) = (logq[p as usize], logq[q as usize]);
307 logq[p as usize] = lq;
308 logq[q as usize] = lp;
309 phys[lp as usize] = q;
310 phys[lq as usize] = p;
311 };
312
313 for g in gates {
314 match *g {
315 Gate::H(l) => routed.push(Gate::H(phys[l as usize])),
316 Gate::S(l) => routed.push(Gate::S(phys[l as usize])),
317 Gate::Cx(a, b) | Gate::Swap(a, b) => {
318 let (pa, pb) = (phys[a as usize], phys[b as usize]);
319 if !coupling.coupled(pa, pb) {
320 let path = coupling.path(pa, pb);
321 for w in 0..path.len().saturating_sub(2) {
323 do_swap(&mut routed, &mut phys, &mut logq, path[w], path[w + 1]);
324 swaps += 1;
325 }
326 }
327 let (pa, pb) = (phys[a as usize], phys[b as usize]);
328 match *g {
329 Gate::Cx(_, _) => routed.push(Gate::Cx(pa, pb)),
330 _ => do_swap(&mut routed, &mut phys, &mut logq, pa, pb),
331 }
332 }
333 }
334 }
335 RouteResult { routed, final_map: phys, swaps }
336}
337
338fn restore_swaps(final_map: &[u32], n: u32) -> Vec<Gate> {
341 let mut logq = vec![0u32; n as usize];
343 for (l, &p) in final_map.iter().enumerate() {
344 logq[p as usize] = l as u32;
345 }
346 let mut phys: Vec<u32> = final_map.to_vec();
347 let mut out = Vec::new();
348 for p in 0..n as usize {
349 while logq[p] != p as u32 {
351 let want = p as u32; let cur_phys = phys[want as usize];
353 out.push(Gate::Swap(p as u32, cur_phys));
355 let (lp, lc) = (logq[p], logq[cur_phys as usize]);
356 logq[p] = lc;
357 logq[cur_phys as usize] = lp;
358 phys[lp as usize] = cur_phys;
359 phys[lc as usize] = p as u32;
360 }
361 }
362 out
363}
364
365pub fn verify_routing(source: &[Gate], route: &RouteResult, n: u32) -> (bool, u64) {
368 let mut full = route.routed.clone();
369 full.extend(restore_swaps(&route.final_map, n));
370 let t_routed = Tableau::of(&full, n);
371 let t_source = Tableau::of(source, n);
372 let work = t_routed.work + t_source.work;
373 (t_source.equivalent(&t_routed), work)
374}
375
376pub fn random_clifford(n: u32, depth: u32, seed: u64) -> Vec<Gate> {
383 let mut st = seed.wrapping_mul(0x2545_F491).wrapping_add(1);
384 let mut g = Vec::with_capacity(depth as usize);
385 for _ in 0..depth {
386 match splitmix64(&mut st) % 3 {
387 0 => g.push(Gate::H((splitmix64(&mut st) % n as u64) as u32)),
388 1 => g.push(Gate::S((splitmix64(&mut st) % n as u64) as u32)),
389 _ => {
390 let a = (splitmix64(&mut st) % n as u64) as u32;
391 let mut b = (splitmix64(&mut st) % n as u64) as u32;
392 if b == a {
393 b = (b + 1) % n;
394 }
395 g.push(Gate::Cx(a, b));
396 }
397 }
398 }
399 g
400}
401
402#[allow(clippy::too_many_arguments)]
406pub fn compile_and_seal(
407 signer: &SigningKey,
408 signer_id: &str,
409 source: &[Gate],
410 coupling: &Coupling,
411 joules_micro: u64,
412 grant: GrantRef,
413) -> (RouteResult, bool, CompileReceipt) {
414 let n = coupling.n;
415 let r = route(source, coupling);
416 let (equivalent, work) = verify_routing(source, &r, n);
417 let receipt = CompileReceipt::seal(
418 signer,
419 signer_id,
420 "clifford-tableau",
421 content_hash(&circuit_bytes(source, n)),
422 content_hash(&routed_bytes(&r.routed, n, &r.final_map)),
423 content_hash(&coupling.bytes()),
424 equivalent,
425 r.swaps,
426 work,
427 joules_micro,
428 grant,
429 None,
430 );
431 (r, equivalent, receipt)
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 fn key(s: u8) -> SigningKey {
439 SigningKey::from_bytes(&[s; 32])
440 }
441
442 #[test]
443 fn hh_is_identity() {
444 let t = Tableau::of(&[Gate::H(0), Gate::H(0)], 2);
445 assert!(t.equivalent(&Tableau::identity(2)));
446 }
447
448 #[test]
449 fn s_to_the_fourth_is_identity() {
450 let t = Tableau::of(&[Gate::S(0), Gate::S(0), Gate::S(0), Gate::S(0)], 1);
451 assert!(t.equivalent(&Tableau::identity(1)));
452 }
453
454 #[test]
455 fn swap_equals_three_cnots() {
456 let a = Tableau::of(&[Gate::Swap(0, 1)], 2);
457 let b = Tableau::of(&[Gate::Cx(0, 1), Gate::Cx(1, 0), Gate::Cx(0, 1)], 2);
458 assert!(a.equivalent(&b));
459 }
460
461 #[test]
462 fn distinct_circuits_are_not_equivalent() {
463 let a = Tableau::of(&[Gate::H(0)], 2);
464 let b = Tableau::of(&[Gate::S(0)], 2);
465 assert!(!a.equivalent(&b));
466 }
467
468 #[test]
469 fn routing_preserves_equivalence() {
470 let n = 5;
473 let coupling = Coupling::line(n);
474 for seed in 0..40u64 {
475 let src = random_clifford(n, 40, seed.wrapping_mul(0x9E37));
476 let r = route(&src, &coupling);
477 let (equiv, _) = verify_routing(&src, &r, n);
478 assert!(equiv, "routed circuit must be equivalent (seed {seed})");
479 for g in &r.routed {
481 if let Gate::Cx(a, b) | Gate::Swap(a, b) = *g {
482 assert!(coupling.coupled(a, b), "routed 2q gate must be local");
483 }
484 }
485 }
486 }
487
488 #[test]
489 fn tamper_breaks_equivalence() {
490 let n = 5;
492 let coupling = Coupling::line(n);
493 let src = random_clifford(n, 40, 0xBEEF);
494 let mut r = route(&src, &coupling);
495 r.routed.pop();
497 let (equiv, _) = verify_routing(&src, &r, n);
498 assert!(!equiv, "a tampered routed circuit must NOT verify as equivalent");
499 }
500
501 #[test]
502 fn compile_seals_verifying_receipt() {
503 let coupling = Coupling::ring(6);
504 let src = random_clifford(6, 60, 7);
505 let (r, equiv, rec) = compile_and_seal(
506 &key(1), "did:key:lab", &src, &coupling, 300_000, GrantRef::unbounded("quantum.compile"),
507 );
508 assert!(equiv);
509 assert!(rec.verify());
510 assert!(rec.equivalent);
511 assert!(rec.source_matches(&circuit_bytes(&src, 6)));
512 assert!(rec.target_matches(&routed_bytes(&r.routed, 6, &r.final_map)));
513 }
514
515 #[test]
516 fn routing_is_deterministic() {
517 let coupling = Coupling::line(6);
518 let src = random_clifford(6, 50, 123);
519 let a = route(&src, &coupling);
520 let b = route(&src, &coupling);
521 assert_eq!(a.routed, b.routed);
522 assert_eq!(a.final_map, b.final_map);
523 }
524}