//! What a sweep of the review's families found beyond the review (extensions/quantum-wasmq.md): //! bundles a host could not hold or finish, which fuel does not meter; a job signed by any //! key whose result this profile never writes; records that replay though no run made them. use serde_json::{Value, json}; use wai_quantum::ed25519_dalek::SigningKey; use wai_wasmq::envelope::{Bundle, Callout, Module, Run, open, pack}; use wai_wasmq::run::{Call, EngineKind, replay, run}; fn wasm(wat: &str) -> Vec { wat::parse_str(wat).unwrap() } fn kinds() -> Vec { wai_wasmq::engine::available().iter().map(|s| EngineKind::parse(s).unwrap()).collect() } const IDENTITY: &str = r#"(module (func (export "f") (param i64) (result i64) local.get 0))"#; const SPIN: &str = r#"(module (func (export "f") (param i64) (result i64) loop br 0 end i64.const 0))"#; fn one(program: String, module: Vec, shots: u64) -> Bundle { Bundle { program, inputs: Default::default(), modules: vec![Module { name: "m".into(), bytes: module, priors: vec![] }], callouts: vec![Callout { name: "f".into(), module: 0, export: "f".into() }], run: Run::new(shots, 1), } } /// A program of `qubits` qubits and `bits` bits in all, with `body` after a call. fn program(qubits: u64, bits: u64, body: &str) -> String { let rest = if bits > 1 { format!("bit[{}] c;\n", bits - 1) } else { String::new() }; format!("OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[1]) -> bit[1];\nqubit[{qubits}] q;\nbit[1] d;\n{rest}d = f(d);\n{body}") } fn code(b: &Bundle) -> Option<&'static str> { pack(b).err().map(|e| e.code) } // --------------------------------------------------------------------------- // What fuel does not meter: a host must be able to hold and finish what it opens // --------------------------------------------------------------------------- /// `n` modules of `pages` pages each, one callout for each. fn modules_of(n: usize, pages: u64) -> Bundle { let mut p = String::from("OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[1] q;\nbit[1] c;\n"); let bytes = wasm(&format!(r#"(module (memory (export "memory") {pages} {pages}) (func (export "f") (param i64) (result i64) local.get 0))"#)); let (mut modules, mut callouts) = (vec![], vec![]); for i in 0..n { p.push_str(&format!("extern f{i}(bit[1]) -> bit[1];\n")); } for i in 0..n { p.push_str(&format!("c = f{i}(c);\n")); modules.push(Module { name: format!("m{i}"), bytes: bytes.clone(), priors: vec![] }); callouts.push(Callout { name: format!("f{i}"), module: i, export: "f".into() }); } Bundle { program: p, inputs: Default::default(), modules, callouts, run: Run::new(1, 1) } } #[test] fn the_code_among_a_bundle_s_modules_is_bounded() { // The interpreter holds a module an instruction at a time, some fifty bytes each, and // the native engine compiles each prepared body whole: thirty modules of the most bytes // each, one envelope, would hold gigabytes on any engine (§3.2). let of = |bytes: usize| { let pad = format!("(data (i32.const 0) \"{}\")", "a".repeat(bytes)); format!(r#"(module (memory 8 8) {pad} (func (export "f") (param i64) (result i64) local.get 0) (func (export "g") (param i64) (result i64) local.get 0))"#) }; let two = |bytes: usize| { let mut b = Bundle { program: "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[1]) -> bit[1];\nextern g(bit[1]) -> bit[1];\nqubit[1] q;\nbit[1] d;\nd = f(d);\nd = g(d);\n".into(), inputs: Default::default(), modules: vec![Module { name: "a".into(), bytes: wasm(&of(bytes)), priors: vec![] }, Module { name: "b".into(), bytes: wasm(&of(bytes)), priors: vec![] }], callouts: vec![Callout { name: "f".into(), module: 0, export: "f".into() }, Callout { name: "g".into(), module: 1, export: "g".into() }], run: Run::new(1, 1), }; b.run.per_init = 1 << 24; b }; let half = wai_wasmq::envelope::MAX_CODE_BYTES / 2; assert_eq!(code(&two(half - 200)), None, "two modules of half the bytes each"); assert_eq!(code(&two(half + 200)), Some("W010"), "two modules each within a module's bytes, past a bundle's together"); } #[test] fn the_modules_of_a_bundle_hold_no_more_memory_among_them_than_one_may() { // Twenty modules of 256 MiB sealed `ok` on a native host, and on wasm32, whose address // space cannot hold them, ended with no receipt: two outcomes for one envelope. assert_eq!(code(&modules_of(20, 4096)), Some("W003"), "twenty modules of the most memory one may have"); assert_eq!(code(&modules_of(2, 2049)), Some("W003"), "4098 pages among two"); assert_eq!(code(&modules_of(2, 2048)), None, "4096 pages among two"); } /// One bit of the program's register measured. const ONE_MEASURED: &str = "c[0] = measure q[0];\n"; #[test] fn a_bundle_whose_result_a_host_could_not_hold_is_refused() { // A register of a million bits and 4096 shots: under 900 bytes, a job of 4 GB and 15 GB // of memory on a native host, and on wasm32 the process aborts. assert_eq!(code(&one(program(17, 1_000_000, ONE_MEASURED), wasm(IDENTITY), 4096)), Some("W017")); // Shots times bits, at most 2^28. assert_eq!(code(&one(program(1, 1 << 16, ONE_MEASURED), wasm(IDENTITY), 1 << 12)), None, "2^28 bits of results"); assert_eq!(code(&one(program(1, (1 << 16) + 1, ONE_MEASURED), wasm(IDENTITY), 1 << 12)), Some("W017"), "one bit more in each shot"); assert_eq!(code(&one(program(1, 1 << 20, ONE_MEASURED), wasm(IDENTITY), 1 << 8)), None, "2^28 bits, of the most bits a program holds"); assert_eq!(code(&one(program(1, 1 << 20, ONE_MEASURED), wasm(IDENTITY), (1 << 8) + 1)), Some("W017"), "a shot more"); // And a program holds at most 2^20 bits: past that its reader refuses it. assert_eq!(code(&one(program(1, (1 << 20) + 1, ONE_MEASURED), wasm(IDENTITY), 1)), Some("W016"), "a bit more in a program"); } #[test] fn a_run_no_host_could_finish_is_refused_though_it_spends_no_fuel() { // A measurement at the most qubits takes the host some milliseconds and no fuel: these // would take a host far longer than anyone would wait, and it would seal nothing until then. let measures = "h q;\nfor uint i in [0:99999] { c[0] = measure q[0]; }\n"; assert_eq!(code(&one(program(32768, 2, measures), wasm(IDENTITY), 1 << 20)), Some("W017")); // A fresh tableau at the most qubits, each shot. assert_eq!(code(&one(program(32768, 2, ONE_MEASURED), wasm(IDENTITY), 1 << 20)), Some("W017"), "the tableaux"); // A module's memory is made again each shot, and once for a run of one instance. let mut b = one(program(1, 2, ""), wasm(r#"(module (memory (export "memory") 4096 4096) (func (export "f") (param i64) (result i64) local.get 0))"#), 1 << 20); assert_eq!(code(&b), Some("W017"), "the memory of a million instances"); b.run.state = "job".into(); assert_eq!(code(&b), None, "the memory of one instance, for a million shots"); b.run.state = "shot".into(); b.run.shots = 1 << 10; assert_eq!(code(&b), None, "the memory of a thousand instances"); // Even once, a hundred thousand of them at the most qubits. assert_eq!(code(&one(program(32768, 2, measures), wasm(IDENTITY), 1)), Some("W017"), "these measurements, once"); // And programs of the same kinds at a size a host finishes pack. let fewer = "h q;\nfor uint i in [0:1999] { c[0] = measure q[0]; }\n"; assert_eq!(code(&one(program(32768, 2, fewer), wasm(IDENTITY), 1)), None, "two thousand measurements, once"); assert_eq!(code(&one(program(32768, 2, ONE_MEASURED), wasm(IDENTITY), 1 << 11)), None, "these tableaux, two thousand times"); // A call is the host's work too, whatever fuel it uses. let calls = "for uint i in [0:1999] { d = f(d); }\nc[0] = measure q[0];\n"; assert_eq!(code(&one(program(1, 2, calls), wasm(IDENTITY), 1 << 20)), Some("W017"), "two thousand calls, a million times"); assert_eq!(code(&one(program(1, 2, calls), wasm(IDENTITY), 1 << 10)), None, "two thousand calls, a thousand times"); } #[test] fn memory_a_module_grows_is_paid_for_in_fuel_as_memory_it_starts_with_is_in_the_work() { // Declared at no pages and grown to the most in `init`, each instance zeroes as much as // one that starts with it: fuel pays for each page grown as the work counts each page // a module starts with, so a run's budget bounds the one as the work bounds the other. let grows = r#"(module (memory (export "memory") 0 4096) (func (export "init") i32.const 4096 memory.grow drop) (func (export "f") (param i64) (result i64) local.get 0))"#; let starts = r#"(module (memory (export "memory") 4096 4096) (func (export "f") (param i64) (result i64) local.get 0))"#; assert_eq!(code(&one(program(1, 2, ONE_MEASURED), wasm(starts), 1 << 20)), Some("W017"), "a million instances that start with the most memory"); let o = open(&pack(&one(program(1, 2, ONE_MEASURED), wasm(grows), 4)).unwrap()).unwrap(); let key = SigningKey::from_bytes(&[7; 32]); for k in kinds() { let (job, _) = run(&o, k, &key, false).unwrap(); assert!(job["result"]["fuel"]["instances"].as_u64().unwrap() >= 4 * 4096 * wai_wasmq::meter::PAGE_FUEL, "{k:?}: {}", job["result"]["fuel"]); } // A million of them pack: their budget, which the work counts, runs out first. let b = one(program(1, 2, ONE_MEASURED), wasm(grows), 1 << 20); assert_eq!(code(&b), None); assert!(u128::from(b.run.per_job) < 1 << 20 << 22, "the run's budget pays for fewer than a million instances' growth"); } #[test] fn a_run_s_fuel_is_bounded_with_its_work() { // Fuel is the modules' work, and a host runs all of it: a budget of 2^53 is months of // a loop that never ends, before the fuel fault is sealed. let mut b = one(program(1, 2, ONE_MEASURED), wasm(SPIN), 1); b.run.per_call = (1 << 53) - 1; b.run.per_job = (1 << 53) - 1; assert_eq!(code(&b), Some("W017"), "the most fuel a receipt can bind"); // The budgets a run is given by default pack, and so does the most fuel the work allows. assert_eq!(code(&one(program(1, 2, ONE_MEASURED), wasm(SPIN), 1)), None, "the default budgets"); // Work of exactly the limit: gates of one each, so that the rest is whole units of fuel. use wai_wasmq::envelope::{FUEL_WORK, MAX_HOST_WORK, host_work}; let facts = [wai_wasmq::profile::check(&wasm(SPIN)).unwrap()]; let with = |gates: u128| one(program(1, 2, &format!("{ONE_MEASURED}{}", "x q[0];\n".repeat(gates as usize))), wasm(SPIN), 1); let work = |b: &Bundle| { let (h, _) = wai_wasmq::envelope::read(&b.program, &b.inputs).unwrap(); host_work(&h, &facts, b) }; let without_fuel = |b: &Bundle| work(&Bundle { run: Run { per_job: 0, ..b.run.clone() }, ..b.clone() }); let mut most = with((MAX_HOST_WORK - without_fuel(&with(0))) % FUEL_WORK); most.run.per_job = ((MAX_HOST_WORK - without_fuel(&most)) / FUEL_WORK) as u64; most.run.per_call = most.run.per_job; assert_eq!(work(&most), MAX_HOST_WORK, "work of exactly the limit"); assert_eq!(code(&most), None, "the most fuel the work allows"); most.run.per_job += 1; assert_eq!(code(&most), Some("W017"), "one more"); } #[test] fn a_callout_s_work_counts_every_call_into_its_module() { // A list argument is a call of cabi_realloc, the export is one, and cabi_post another: a // callout of three list arguments with a post call is five calls of the host's. let m = wasm(r#"(module (memory (export "memory") 1 1) (func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) i32.const 1024) (func (export "f") (param i32 i32 i32 i32 i32 i32) (result i32) i32.const 2048) (func (export "cabi_post_f") (param i32)))"#); let src = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[65], bit[65], bit[65]) -> bit[65];\nqubit[1] q;\nbit[65] a;\nbit[65] r;\nr = f(a, a, a);\n"; let b = one(src.into(), m, 1); let (h, _) = wai_wasmq::envelope::read(&b.program, &b.inputs).unwrap(); let facts = [wai_wasmq::profile::check(&b.modules[0].bytes).unwrap()]; let one_call = wai_wasmq::envelope::host_work(&h, &facts, &b); let mut twice = b.clone(); twice.program = src.replace("r = f(a, a, a);", "r = f(a, a, a);\nr = f(a, a, a);"); let (h2, _) = wai_wasmq::envelope::read(&twice.program, &twice.inputs).unwrap(); let call = wai_wasmq::envelope::host_work(&h2, &facts, &twice) - one_call; assert_eq!(call, 5 * wai_wasmq::envelope::CALL_WORK + 4 * 65 * wai_wasmq::envelope::BIT_WORK + 1, "five calls of the host's and 260 bits, and the step"); // A prior is two: cabi_realloc and wasmq-prior. let p = wasm(TAKES_PRIORS); let mut with = one(program(1, 1, ""), p.clone(), 1); let facts = [wai_wasmq::profile::check(&p).unwrap()]; let (h, _) = wai_wasmq::envelope::read(&with.program, &with.inputs).unwrap(); let none = wai_wasmq::envelope::host_work(&h, &facts, &with); with.modules[0].priors.push(("p".into(), vec![0; 64])); assert_eq!(wai_wasmq::envelope::host_work(&h, &facts, &with) - none, 2 * wai_wasmq::envelope::CALL_WORK); } #[test] fn the_work_a_run_asks_of_its_host_is_the_text_s_sum() { use wai_wasmq::envelope::{BIT_WORK, CALL_WORK, CODE_WORK, FUEL_WORK, MEASURE_WORK}; // §4.8, by hand: 64 qubits take r = 2 words a qubit. For a shot: a tableau of // 2 * 64 * 2; the call, one of the host's with its two bits and its step; a gate of 2, // twice; the measurement, 32 * 64 * 2; the jump, 1. For an instance: making it, one // call, its module's bytes, and three pages of 8192. For the run: its module's bytes, // compiled, and the fuel it may be given. let src = program(64, 2, "x q[0];\nc[0] = measure q[0];\nif (c[0]) x q[1];\n"); let m = wasm(r#"(module (memory (export "memory") 3 9) (func (export "f") (param i64) (result i64) local.get 0))"#); let (h, _) = wai_wasmq::envelope::read(&src, &Default::default()).unwrap(); let work = |b: &Bundle| wai_wasmq::envelope::host_work(&h, &[wai_wasmq::profile::check(&b.modules[0].bytes).unwrap()], b); let shot = 2 * 64 * 2 + (CALL_WORK + 2 * BIT_WORK + 1) + 2 + MEASURE_WORK * 64 * 2 + 1 + 2; let len = m.len() as u128; let instance = CALL_WORK + len + 3 * 8192; let mut b = one(src.clone(), m, 5); let run = CODE_WORK * len + FUEL_WORK * u128::from(b.run.per_job); assert_eq!(work(&b), 5 * shot + 5 * instance + run, "an instance for each shot"); b.run.state = "job".into(); assert_eq!(work(&b), 5 * shot + instance + run, "one instance for the run"); // A seed, an init and a prior are four more calls: a prior is cabi_realloc and wasmq-prior. let m = wasm(r#"(module (memory (export "memory") 3 9) (func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) i32.const 1024) (func (export "wasmq-prior") (param i32 i32 i32)) (func (export "wasmq-seed") (param i64)) (func (export "init")) (func (export "f") (param i64) (result i64) local.get 0))"#); let len = m.len() as u128; let mut b = one(src, m, 5); b.modules[0].priors.push(("p".into(), vec![0; 17])); assert_eq!(work(&b), 5 * shot + 5 * (5 * CALL_WORK + len + 3 * 8192) + CODE_WORK * len + FUEL_WORK * u128::from(b.run.per_job)); } #[test] fn the_work_limit_admits_its_largest_and_refuses_one_more() { // A million shots of an instance of 480 pages, and of gates of one word each, as many // as the work leaves room for. let m = wasm(r#"(module (memory (export "memory") 480 480) (func (export "f") (param i64) (result i64) local.get 0))"#); let with = |gates: u64| one(program(1, 2, &format!("c[0] = measure q[0];\nfor uint i in [0:{}] {{ x q[0]; }}\n", gates - 1)), m.clone(), 1 << 20); let facts = [wai_wasmq::profile::check(&m).unwrap()]; let work = |g: u64| { let b = with(g); let (h, _) = wai_wasmq::envelope::read(&b.program, &b.inputs).unwrap(); wai_wasmq::envelope::host_work(&h, &facts, &b) }; let per_gate = work(2) - work(1); assert_eq!(per_gate, 1 << 20, "a gate of one word, in each of a million shots"); let most = 1 + ((wai_wasmq::envelope::MAX_HOST_WORK - work(1)) / per_gate) as u64; assert!(most > 1000 && work(most) <= wai_wasmq::envelope::MAX_HOST_WORK && work(most + 1) > wai_wasmq::envelope::MAX_HOST_WORK); assert_eq!(code(&with(most)), None, "the most the work admits"); assert_eq!(code(&with(most + 1)), Some("W017"), "a gate more in each shot"); } #[test] fn the_steps_of_a_program_are_bounded_at_their_limit() { // A call and its loop's body: a step each. The program's first call makes one more. let of = |calls: u64| one(program(1, 2, &format!("for uint i in [0:{}] {{ d = f(d); }}\nc[0] = measure q[0];\n", calls - 1)), wasm(IDENTITY), 1); let room = (1 << 21) - 2; assert_eq!(code(&of(room)), None, "2^21 steps"); assert_eq!(code(&of(room + 1)), Some("W016"), "one more"); // Steps of jumps alone past the bound: an `if` and an `else` with nothing in them are // two jumps, which no step of a gate, a measurement or a call counts as it is made. let jumps = |calls: u64| one(program(1, 2, &format!("for uint i in [0:{}] {{ d = f(d); }}\nc[0] = measure q[0];\nif (d == 1) {{ }} else {{ }}\n", calls - 1)), wasm(IDENTITY), 1); let (h, _) = wai_wasmq::envelope::read(&jumps(room - 2).program, &Default::default()).unwrap(); assert_eq!(h.steps.len(), 1 << 21, "the empty branches are two steps"); let past = jumps(room - 1); let e = pack(&past).unwrap_err(); assert!(e.code == "W016" && e.message.contains("steps; it is held to"), "{e}"); } /// A module that takes priors, so that a long prior is an envelope's only defect. const TAKES_PRIORS: &str = r#"(module (memory (export "memory") 1 1) (func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) i32.const 1024) (func (export "wasmq-prior") (param i32 i32 i32)) (func (export "f") (param i64) (result i64) local.get 0))"#; fn with_prior(bytes: usize) -> Bundle { let mut b = one(program(1, 1, ""), wasm(TAKES_PRIORS), 1); b.modules[0].priors.push(("long".into(), vec![0; bytes])); b } #[test] fn an_envelope_past_what_a_host_must_hold_is_refused() { // A reader holds an envelope's bytes and its frames at once, so what one host opens // another must be able to. // Frames of the whole limit are refused by the check itself, before they are written. let whole = with_prior(1 << 27); assert_eq!(wai_wasmq::envelope::check(&whole).err().map(|e| e.code), Some("W013"), "frames of the whole limit"); assert_eq!(code(&whole), Some("W013")); // Frames just within it, and with the contract and the manifest past it: the reader's. let rest = with_prior(0); let others = rest.program.len() + rest.modules[0].bytes.len(); let just_within = with_prior((1 << 27) - 1 - others); assert!(wai_wasmq::envelope::check(&just_within).is_ok(), "the frames alone fit"); assert_eq!(code(&just_within), Some("W013"), "the envelope they make does not"); let env = pack(&with_prior((1 << 27) - (1 << 16))).unwrap(); assert!(env.len() <= 1 << 27); assert!(open(&env).is_ok(), "an envelope within the limit"); } /// A module of the most memory and the most code a bundle holds: the exports a prior /// needs, `f`, and functions of one-byte instructions up to the bundle's bytes of module, /// which the interpreter holds an instruction at a time. fn the_most_code() -> Vec { let head = r#"(memory (export "memory") 4096 4096) (func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) i32.const 1024) (func (export "wasmq-prior") (param i32 i32 i32)) (func (export "f") (param i64) (result i64) local.get 0)"#; let filler = format!("(func i32.const 0 {}drop)", "i32.eqz ".repeat(wai_wasmq::profile::MAX_BODY_BYTES as usize - 8)); let mut fillers = 0; loop { let m = wasm(&format!("(module {head} {})", filler.repeat(fillers + 1))); if m.len() > wai_wasmq::envelope::MAX_CODE_BYTES { let m = wasm(&format!("(module {head} {})", filler.repeat(fillers))); assert!(m.len() > wai_wasmq::envelope::MAX_CODE_BYTES - (20 << 10), "{} bytes", m.len()); return m; } fillers += 1; } } /// A bundle at every limit a run holds in memory at once: the most memory and code, a /// prior of the rest an envelope holds, the most qubits and bits, and a determined /// measurement; with `calls` calls that take and give two bits each. fn the_largest(calls: u64) -> wai_wasmq::Opened { let src = format!( "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[2]) -> bit[2];\nqubit[32768] q;\nbit[2] d;\nbit[{}] c;\nd = f(d);\nh q[0];\nc[0] = measure q[0];\nreset q[0];\nfor uint i in [0:{}] {{ d = f(d); }}\n", (1 << 20) - 2, calls - 1 ); let m = the_most_code(); let room = wai_wasmq::envelope::MAX_ENVELOPE_BYTES - m.len() - src.len() - (8 << 10); let mut b = one(src, m, 2); b.modules[0].priors.push(("long".into(), vec![1; room])); // Fuel counts a prior's bytes: the budget lets it through, so that the run goes on to // hold everything else at once, as it would not if it stopped at the prior. b.run.per_init = 1 << 28; open(&pack(&b).unwrap()).unwrap() } #[test] fn the_largest_bundle_the_limits_admit_runs_on_every_engine() { let o = the_largest(1); let key = SigningKey::from_bytes(&[7; 32]); let jobs: Vec = kinds().into_iter().map(|k| run(&o, k, &key, false).unwrap().0).collect(); assert_eq!(jobs[0]["result"]["status"], "ok", "{}", jobs[0]["result"]["fault"]); assert!(jobs.iter().all(|j| j == &jobs[0]), "the same job on every engine"); } #[test] fn the_largest_bundle_with_the_most_steps_fits_a_32_bit_host() { // And with the most steps and the most bits of calls as well, on the interpreter, which // is the engine wasm32 has. let calls = (1u64 << 21) - 8; assert!(4 * (calls + 1) <= wai_wasmq::envelope::MAX_CALLOUT_BITS && 4 * (calls + 1) > wai_wasmq::envelope::MAX_CALLOUT_BITS - 64); let o = the_largest(calls); let (job, _) = run(&o, EngineKind::Interpreter, &SigningKey::from_bytes(&[7; 32]), false).unwrap(); assert_eq!(job["result"]["status"], "ok", "{}", job["result"]["fault"]); } /// [`the_largest`] by the second version's `exact` method (§11.3), its state the most qubits /// the method holds: the most memory and code, a prior of the rest an envelope holds, the most /// bits, a `t` gate and a random measurement on the whole-number state vector, and a reset. pub fn the_largest_exact(qubits: u32) -> wai_wasmq::Opened { let src = format!( "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[2]) -> bit[2];\nqubit[{qubits}] q;\nbit[2] d;\nbit[{}] c;\nd = f(d);\nh q[0];\nt q[0];\nh q[0];\nc[0] = measure q[0];\nreset q[0];\nd = f(d);\n", (1 << 20) - 2, ); let m = the_most_code(); let room = wai_wasmq::envelope::MAX_ENVELOPE_BYTES - m.len() - src.len() - (8 << 10); let mut b = one(src, m, 2); b.run.version = 2; b.run.method = wai_wasmq::envelope::EXACT.into(); b.modules[0].priors.push(("long".into(), vec![1; room])); b.run.per_init = 1 << 28; open(&pack(&b).unwrap()).unwrap() } #[test] fn the_largest_exact_bundle_fits_a_32_bit_host() { // On the interpreter, the engine wasm32 has: a state of 2^24 amplitudes (256 MiB) beside // a module's 256 MiB of memory, an envelope of 128 MiB and 2^20 bits a shot (§11.3). let o = the_largest_exact(wai_wasmq::envelope::EXACT_MAX_QUBITS); let (job, _) = run(&o, EngineKind::Interpreter, &SigningKey::from_bytes(&[7; 32]), false).unwrap(); assert_eq!(job["result"]["status"], "ok", "{}", job["result"]["fault"]); assert_eq!(job["result"]["method"], "exact"); } #[test] fn the_most_result_a_run_may_have_runs_on_every_engine() { // 256 shots of the most bits a program holds, nine of them random: 2^28 bits of results, // in hundreds of outcomes of a million bits each. let measures: String = (0..9).map(|k| format!("c[{k}] = measure q[{k}];\n")).collect(); let b = one(program(9, 1 << 20, &format!("h q;\n{measures}")), wasm(IDENTITY), 1 << 8); let o = open(&pack(&b).unwrap()).unwrap(); let key = SigningKey::from_bytes(&[7; 32]); let jobs: Vec = kinds().into_iter().map(|k| run(&o, k, &key, false).unwrap().0).collect(); assert!(jobs[0]["result"]["counts"].as_object().unwrap().len() > 100); assert!(jobs.iter().all(|j| j == &jobs[0]), "the same job on every engine"); } #[test] fn every_jump_of_a_program_goes_forward_so_a_run_ends() { // Fuel meters no step of the program, so a run's end rests on this: the reader unrolls // every loop, a `while` included, and a step only ever jumps ahead. let src = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[1]) -> bit[1];\nqubit[1] q;\nbit[1] c;\nc[0] = measure q[0];\n@wai.max_iterations 5\nwhile (c == 0) { x q[0]; c = f(c); c[0] = measure q[0]; }\nfor uint i in [0:3] { if (c == 1) x q[0]; else h q[0]; }\n"; let h = wai_quantum::qasm::read_callouts(src, &Default::default()).unwrap(); let mut jumps = 0; for (pc, s) in h.steps.iter().enumerate() { if let wai_quantum::qasm::Step::Instr(wai_quantum::qasm::Instr::JumpUnless(_, to) | wai_quantum::qasm::Instr::Jump(to)) = s { jumps += 1; assert!(*to > pc, "step {pc} jumps back to {to}"); } } assert!(jumps > 0, "this test can fail: the program has jumps"); } // --------------------------------------------------------------------------- // A job sealed by any key: its result is one this profile writes, or verify says not // --------------------------------------------------------------------------- fn a_job(module: &str, per_call: u64) -> (wai_wasmq::Opened, Value) { let mut b = one(program(3, 3, "h q;\nc[0] = measure q[0];\nc[1] = measure q[1];\n"), wasm(module), 8); b.run.per_call = per_call; let o = open(&pack(&b).unwrap()).unwrap(); let (body, _) = run(&o, EngineKind::Interpreter, &SigningKey::from_bytes(&[7; 32]), false).unwrap(); (o, body) } /// The job as it would be sealed again under another key, its body agreeing with its result. fn sealed_again(job: &Value) -> Value { let mut result = job["result"].clone(); if let Some(o) = result.as_object_mut() { o.remove("receipt"); } let receipt = wai_quantum::engine::receipt::seal(&SigningKey::from_bytes(&[9; 32]), &job["request"], &result); if result.is_object() { result["receipt"] = receipt; } let status = if result["status"] == "ok" { "done" } else { "fault" }; json!({ "status": status, "method": result["method"], "request": job["request"], "result": result }) } /// Each edit of an `ok` job's result or request, and of a faulted one's, that makes it one /// this profile never writes. type Edit = (&'static str, fn(&mut Value)); const EDITS_OF_OK: &[Edit] = &[ ("another profile", |j| j["result"]["profile"] = json!("wai.quantum.wasmq/2")), ("a method the contract does not give", |j| j["result"]["method"] = json!("statevector")), ("shots the contract does not give", |j| { j["result"]["shots"] = json!(9); j["result"]["shots_run"] = json!(9); let k = j["result"]["counts"].as_object().unwrap().keys().next().unwrap().clone(); let n = j["result"]["counts"][&k].as_u64().unwrap(); j["result"]["counts"][&k] = json!(n + 1); }), ("a seed the contract does not give", |j| j["result"]["seed"] = json!(2)), ("more qubits than a program may have", |j| j["result"]["qubits"] = json!(40000)), ("qubits that are not a number", |j| j["result"]["qubits"] = json!("three")), ("a count that is not a number", |j| j["result"]["counts"]["1"] = json!("many")), ("a count of nothing", |j| j["result"]["counts"]["1"] = json!(0)), ("an outcome that is not bits", |j| { let c = j["result"]["counts"].as_object_mut().unwrap(); let (k, n) = c.iter().next().map(|(k, n)| (k.clone(), n.clone())).unwrap(); c.remove(&k); c.insert("2".repeat(k.len()), n); }), ("outcomes of two widths", |j| { let c = j["result"]["counts"].as_object_mut().unwrap(); let (k, n) = c.iter().next().map(|(k, n)| (k.clone(), n.clone())).unwrap(); c.remove(&k); c.insert(format!("{k}0"), n); }), ("counts that do not sum to the shots run", |j| { let k = j["result"]["counts"].as_object().unwrap().keys().next().unwrap().clone(); j["result"]["counts"] = json!({ k: 7 }); }), ("counts that overflow to the shots run", |j| { j["result"]["counts"] = json!({ "0": u64::MAX, "1": 9 }); }), ("a member the text does not define", |j| j["result"]["note"] = json!("checked by a person of standing")), ("no transcript", |j| { j["result"].as_object_mut().unwrap().remove("transcript_blake3"); }), ("no accuracy", |j| { j["result"].as_object_mut().unwrap().remove("accuracy"); }), ("no energy", |j| { j["result"].as_object_mut().unwrap().remove("energy"); }), ("no count of the callouts", |j| { j["result"].as_object_mut().unwrap().remove("callouts"); }), ("another accuracy", |j| j["result"]["accuracy"] = json!({ "class": "approximate" })), ("an energy reading", |j| j["result"]["energy"] = json!({ "joules": 0.001 })), ("a transcript that is no digest", |j| j["result"]["transcript_blake3"] = json!("abc")), ("callouts that are not the calls counted", |j| { let n = j["result"]["callouts"].as_u64().unwrap(); j["result"]["callouts"] = json!(n + 1); }), ("fuel used that is not the fuel counted", |j| { let n = j["result"]["fuel"]["used"].as_u64().unwrap(); j["result"]["fuel"]["used"] = json!(n + 1); }), ("instances' fuel that is not the fuel counted", |j| { let n = j["result"]["fuel"]["instances"].as_u64().unwrap(); j["result"]["fuel"]["instances"] = json!(n + 1); }), ("a budget the contract does not give", |j| j["result"]["fuel"]["budget"]["per_call"] = json!(1)), ("a member of the fuel the text does not define", |j| j["result"]["fuel"]["note"] = json!(1)), ("fuel for a callout the contract does not name", |j| j["result"]["fuel"]["callouts"]["g"] = json!({ "calls": 1, "fuel": 0, "max": 0 })), ("a call counted that was not", |j| { let n = j["result"]["fuel"]["callouts"]["f"]["calls"].as_u64().unwrap(); j["result"]["fuel"]["callouts"]["f"]["calls"] = json!(n + 1); }), ("a largest call past the callout's fuel", |j| { let n = j["result"]["fuel"]["callouts"]["f"]["fuel"].as_u64().unwrap(); j["result"]["fuel"]["callouts"]["f"]["max"] = json!(n + 1); }), ("a member of a callout's fuel the text does not define", |j| j["result"]["fuel"]["callouts"]["f"]["note"] = json!(1)), ("a member of a module the text does not define", |j| j["result"]["modules"][0]["note"] = json!(1)), ("an ok run that names a fault", |j| j["result"]["fault"] = json!({ "reason": "unreachable", "shot": 0, "phase": "call", "module": "m", "fuel": 0, "callout": "f" })), ("a member of the request the text does not define", |j| j["request"]["note"] = json!(1)), ("an envelope that is no digest", |j| j["request"]["envelope_blake3"] = json!("abc")), ]; const EDITS_OF_FAULT: &[Edit] = &[ ("a reason §6.2 does not name", |j| j["result"]["fault"]["reason"] = json!("cosmic_ray")), ("a fault in a shot other than the one that stopped", |j| j["result"]["fault"]["shot"] = json!(5)), ("a phase §6.2 does not name", |j| j["result"]["fault"]["phase"] = json!("nowhere")), ("a module the contract does not name", |j| j["result"]["fault"]["module"] = json!("nobody")), ("a callout the contract does not name", |j| j["result"]["fault"]["callout"] = json!("nobody")), ("a call's fault with no callout", |j| { j["result"]["fault"].as_object_mut().unwrap().remove("callout"); }), ("a callout named where the phase is not a call", |j| j["result"]["fault"]["phase"] = json!("init")), ("a budget of no one", |j| j["result"]["fault"]["budget"] = json!("nobody's")), ("a fuel fault with no budget", |j| { j["result"]["fault"].as_object_mut().unwrap().remove("budget"); }), ("a budget on a fault of another reason", |j| j["result"]["fault"]["reason"] = json!("unreachable")), ("a fault's fuel that is not a number", |j| j["result"]["fault"]["fuel"] = json!("some")), ("a member of a fault the text does not define", |j| j["result"]["fault"]["detail"] = json!("free text under the signature")), ("a fault after every shot finished", |j| { // The fault's shot follows them, so that nothing but this is out of place. j["result"]["shots_run"] = json!(8); j["result"]["counts"] = json!({ "0": 8 }); j["result"]["fault"]["shot"] = json!(8); }), ]; /// Each edit of an `ok` job's contract that makes it one this profile never writes, with /// the result agreeing with it wherever the result repeats it (§2.2, §6.4). const EDITS_OF_CONTRACT: &[Edit] = &[ ("a method this version does not have, in the contract and the result", |j| { j["request"]["wasmq"]["run"]["method"] = json!("statevector"); j["result"]["method"] = json!("statevector"); }), ("no shots, in the contract and the result", |j| { j["request"]["wasmq"]["run"]["shots"] = json!(0); j["result"]["shots"] = json!(0); j["result"]["shots_run"] = json!(0); j["result"]["counts"] = json!({}); }), ("more shots than a run takes, in the contract and the result", |j| { j["request"]["wasmq"]["run"]["shots"] = json!((1u64 << 20) + 1); j["result"]["shots"] = json!((1u64 << 20) + 1); }), ("a seed that is no number, in the contract and the result", |j| { j["request"]["wasmq"]["run"]["seed"] = json!(null); j["result"]["seed"] = json!(null); }), ("a contract of another profile", |j| j["request"]["wasmq"]["profile"] = json!("wai.quantum.wasmq/2")), ("a contract with a member the text does not define", |j| j["request"]["wasmq"]["note"] = json!(1)), ("a contract with no program", |j| { j["request"]["wasmq"].as_object_mut().unwrap().remove("program"); }), ("a program that is no digest", |j| j["request"]["wasmq"]["program"]["blake3"] = json!("abc")), ("a program's inputs that are not integers", |j| j["request"]["wasmq"]["program"]["inputs"] = json!({ "n": "two" })), ("instances of no kind", |j| j["request"]["wasmq"]["run"]["state"] = json!("forever")), ("a run with a member the text does not define", |j| j["request"]["wasmq"]["run"]["note"] = json!(1)), ("a budget that is no number, in the contract and the result", |j| { j["request"]["wasmq"]["run"]["fuel"]["per_instance"] = json!("lots"); j["result"]["fuel"]["budget"]["per_instance"] = json!("lots"); }), ("a module that is no digest, in the contract and the result", |j| { j["request"]["wasmq"]["modules"][0]["blake3"] = json!(null); j["result"]["modules"][0]["blake3"] = json!(null); }), ("a module of no length", |j| j["request"]["wasmq"]["modules"][0]["bytes"] = json!(0)), ("a callout of a module the contract does not hold", |j| j["request"]["wasmq"]["callouts"][0]["module"] = json!(5)), ("a callout of no export", |j| j["request"]["wasmq"]["callouts"][0]["export"] = json!(7)), ("two callouts for one extern", |j| { let c = j["request"]["wasmq"]["callouts"][0].clone(); j["request"]["wasmq"]["callouts"].as_array_mut().unwrap().push(c); }), ("instances' fuel past what the instances could be given", |j| { // Two lifecycle calls a module (its seed and its init) and one for each prior, for // each instance: one for each shot, and one more for a shot that stopped. let per = j["request"]["wasmq"]["run"]["fuel"]["per_instance"].as_u64().unwrap(); let shots = j["result"]["shots"].as_u64().unwrap(); let over = per * 2 * (shots + 1) + 1; j["result"]["fuel"]["instances"] = json!(over); let used = j["result"]["fuel"]["used"].as_u64().unwrap(); j["result"]["fuel"]["used"] = json!(used + over); j["request"]["wasmq"]["run"]["fuel"]["per_job"] = json!(used + over); j["result"]["fuel"]["budget"]["per_job"] = json!(used + over); }), ("a callout's fuel past its calls times its largest", |j| { let f = &j["result"]["fuel"]["callouts"]["f"]; let (calls, max, fuel) = (f["calls"].as_u64().unwrap(), f["max"].as_u64().unwrap(), f["fuel"].as_u64().unwrap()); let more = calls * max + 1 - fuel; j["result"]["fuel"]["callouts"]["f"]["fuel"] = json!(calls * max + 1); let used = j["result"]["fuel"]["used"].as_u64().unwrap(); j["result"]["fuel"]["used"] = json!(used + more); }), ("fuel used past the run's budget, in the contract and the result", |j| { let used = j["result"]["fuel"]["used"].as_u64().unwrap(); j["request"]["wasmq"]["run"]["fuel"]["per_job"] = json!(used - 1); j["result"]["fuel"]["budget"]["per_job"] = json!(used - 1); }), ("a call past the call's budget, in the contract and the result", |j| { let max = j["result"]["fuel"]["callouts"]["f"]["max"].as_u64().unwrap(); j["request"]["wasmq"]["run"]["fuel"]["per_call"] = json!(max - 1); j["result"]["fuel"]["budget"]["per_call"] = json!(max - 1); }), ("callouts' fuel that is no object, and no calls", |j| { j["result"]["callouts"] = json!(0); j["result"]["fuel"]["callouts"] = json!(0); j["result"]["fuel"]["used"] = j["result"]["fuel"]["instances"].clone(); }), ]; /// Edits of a fault job that make it one this profile never writes. const EDITS_OF_FAULT_FUEL: &[Edit] = &[("a fault's fuel past the budget of its phase", |j| { // One more than the call's budget, and one more used, counted to the instances: nothing // but the fault's own fuel is out of place. let per = j["request"]["wasmq"]["run"]["fuel"]["per_call"].as_u64().unwrap(); j["result"]["fault"]["fuel"] = json!(per + 1); for at in ["used", "instances"] { let n = j["result"]["fuel"][at].as_u64().unwrap(); j["result"]["fuel"][at] = json!(n + 1); } })]; /// Edits of a sealed job's receipt, after it was sealed. const EDITS_OF_RECEIPT: &[Edit] = &[ ("a receipt of another algorithm", |j| j["result"]["receipt"]["alg"] = json!("none")), ("a receipt that names no algorithm", |j| { j["result"]["receipt"].as_object_mut().unwrap().remove("alg"); }), ("a receipt of the first form", |j| { j["result"]["receipt"].as_object_mut().unwrap().remove("canon"); }), ("a key that is not hexadecimal", |j| j["result"]["receipt"]["key"] = json!("€€")), ("a key in capitals", |j| { let k = j["result"]["receipt"]["key"].as_str().unwrap().to_uppercase(); j["result"]["receipt"]["key"] = json!(k); }), ("a signature that is not hexadecimal", |j| j["result"]["receipt"]["signature"] = json!("€".repeat(64))), ]; #[test] fn a_job_whose_contract_or_receipt_this_profile_never_writes_is_not_one_of_its_jobs() { use std::panic::{AssertUnwindSafe, catch_unwind}; let (_, ok) = a_job(IDENTITY, 500); let (_, fault) = a_job(SPIN, 500); let mut let_through = Vec::new(); let mut judge = |name: &'static str, job: &Value| { let v = catch_unwind(AssertUnwindSafe(|| wai_wasmq::verify(job, None, EngineKind::Interpreter))).unwrap_or_else(|_| panic!("{name}: verify panicked")); if v.as_ref().is_ok_and(|v| v.ok()) { let_through.push(name); } }; for (job, edits) in [(&ok, EDITS_OF_CONTRACT), (&fault, EDITS_OF_FAULT_FUEL)] { for (name, edit) in edits { let mut edited = job.clone(); edit(&mut edited); assert_ne!(&edited, job, "{name} changes the job"); judge(name, &sealed_again(&edited)); } } let sealed = sealed_again(&ok); assert!(wai_wasmq::verify(&sealed, None, EngineKind::Interpreter).unwrap().ok(), "the positive control"); for (name, edit) in EDITS_OF_RECEIPT { let mut edited = sealed.clone(); edit(&mut edited); judge(name, &edited); } assert!(let_through.is_empty(), "verify calls these jobs of this profile: {let_through:#?}"); } #[test] fn a_job_whose_result_this_profile_never_writes_is_not_one_of_its_jobs_under_any_key() { let (_, ok) = a_job(IDENTITY, 500); let (_, fault) = a_job(SPIN, 500); assert_eq!(fault["result"]["fault"]["reason"], "fuel_exhausted"); let mut let_through = Vec::new(); for (job, edits) in [(&ok, EDITS_OF_OK), (&fault, EDITS_OF_FAULT)] { // The positive control: sealed again unchanged, it is a job of this profile. let v = wai_wasmq::verify(&sealed_again(job), None, EngineKind::Interpreter).unwrap(); assert!(v.signature && v.well_formed.is_empty(), "sealed again unchanged: {:?}", v.well_formed); for (name, edit) in edits { let mut edited = job.clone(); edit(&mut edited); assert_ne!(&edited, job, "{name} changes the job"); let v = wai_wasmq::verify(&sealed_again(&edited), None, EngineKind::Interpreter).unwrap(); assert!(v.signature, "{name}: a seal under any key is a valid seal, so this is the form's to catch"); if v.well_formed.is_empty() { let_through.push(*name); } } } assert!(let_through.is_empty(), "verify calls these jobs of this profile: {let_through:#?}"); } // --------------------------------------------------------------------------- // A record replays as one a run could have made // --------------------------------------------------------------------------- fn a_record() -> (wai_wasmq::Opened, Vec) { let o = open(&pack(&one(program(3, 1, "h q;\n"), wasm(IDENTITY), 8)).unwrap()).unwrap(); let (_, log) = run(&o, EngineKind::Interpreter, &SigningKey::from_bytes(&[7; 32]), true).unwrap(); (o, log.unwrap()) } #[test] fn a_record_of_a_shot_the_run_does_not_have_or_out_of_order_does_not_replay() { let (o, log) = a_record(); assert_eq!(log.len(), 8); assert!(replay(&o, &log, EngineKind::Interpreter).unwrap().is_empty(), "the positive control"); let mut past = log.clone(); past[7].shot = 8; assert!(!replay(&o, &past, EngineKind::Interpreter).unwrap().is_empty(), "a shot past the run's eight"); let mut far = log.clone(); far[7].shot = u64::MAX; assert!(!replay(&o, &far, EngineKind::Interpreter).unwrap().is_empty(), "a shot no run has"); let mut swapped = log.clone(); swapped.swap(2, 5); assert!(!replay(&o, &swapped, EngineKind::Interpreter).unwrap().is_empty(), "shots out of order"); } #[test] fn the_transcript_s_digest_is_the_text_s_layout() { // §6.4, written out here from the text, for a run that ends ok and one that faults: the // receipt's digest is this one, computed from the record alone. fn layout(calls: &[Call], func: u32, fault: Option<(u64, &str, u64)>) -> String { let mut h = blake3::Hasher::new(); h.update(b"wai:wasmq-transcript\x01"); for c in calls { h.update(&c.shot.to_le_bytes()); h.update(&func.to_le_bytes()); h.update(&(c.input.len() as u32).to_le_bytes()); h.update(&c.input); h.update(&(c.output.len() as u32).to_le_bytes()); h.update(&c.output); h.update(&c.fuel.to_le_bytes()); } if let Some((shot, reason, fuel)) = fault { h.update(b"fault"); h.update(&shot.to_le_bytes()); h.update(reason.as_bytes()); h.update(&fuel.to_le_bytes()); } h.finalize().to_hex().to_string() } let key = SigningKey::from_bytes(&[7; 32]); for (module, per_call) in [(IDENTITY, 500), (SPIN, 500)] { let mut b = one(program(3, 3, "h q;\nc[0] = measure q[0];\n"), wasm(module), 8); b.run.per_call = per_call; let o = open(&pack(&b).unwrap()).unwrap(); let (job, log) = run(&o, EngineKind::Interpreter, &key, true).unwrap(); let r = &job["result"]; let fault = r.get("fault").map(|f| (f["shot"].as_u64().unwrap(), f["reason"].as_str().unwrap(), f["fuel"].as_u64().unwrap())); assert_eq!(r["transcript_blake3"].as_str().unwrap(), layout(&log.unwrap(), 0, fault), "{module}"); } }