open-standards/wai/wai-wasmq/tests/review.rs
Transaction Science 2593c1f945
Some checks failed
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 7s
wai-quantum wasmQ / does this change touch it (push) Successful in 10s
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 28s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Failing after 54s
wai-quantum wasmQ / the reference modules rebuild to their modules of record · linux (push) Successful in 40s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Failing after 1m16s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Has been cancelled
joule-moq-room room service / joule-moq-room · does this change touch it (push) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (push) Has been cancelled
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Has been cancelled
wai-app and Create / wai-app · does this change touch it (push) Has been cancelled
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Has been cancelled
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Has been cancelled
wai-audit collector and player integration / does this change touch it (push) Has been cancelled
wai-audit collector and player integration / collector · linux (push) Has been cancelled
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Has been cancelled
WAI operation catalogue / does this change touch it (push) Has been cancelled
WAI operation catalogue / the catalogue and every door · linux (push) Has been cancelled
wai-fleet service / does this change touch it (push) Has been cancelled
wai-fleet service / service · linux (push) Has been cancelled
wai-fleet service / service · macos (push) Has been cancelled
wai-ledger service / does this change touch it (push) Has been cancelled
wai-ledger service / service · linux (push) Has been cancelled
wai-ledger service / service · macos (push) Has been cancelled
wai-quantum for JavaScript / does this change touch it (push) Has been cancelled
wai-quantum for JavaScript / javascript · linux (push) Has been cancelled
wai-quantum studio and desktop / does this change touch it (push) Has been cancelled
wai-quantum studio and desktop / studio · browser (push) Has been cancelled
wai-quantum studio and desktop / desktop app · macos (push) Has been cancelled
wai-quantum studio and desktop / desktop app · linux (push) Has been cancelled
wai-quantum studio and desktop / desktop app · windows (push) Has been cancelled
wai-sink-android native sink library / does this change touch it (push) Has been cancelled
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Has been cancelled
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Has been cancelled
wai-sink-swift native sink package / does this change touch it (push) Has been cancelled
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Has been cancelled
WAI Studio / does this change touch it (push) Has been cancelled
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Has been cancelled
WAI Studio / Studio in Chromium and Firefox · macos (push) Has been cancelled
WAI Studio / Studio in WebKit · linux (push) Has been cancelled
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · macos (push) Successful in 2m13s
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Successful in 2m33s
wai-quantum wasmQ / interpreter and vectors under wasm32-wasip2 (wasmtime) (push) Successful in 2m38s
wai-quantum energy-landscape jobs / the same bits · macos (push) Successful in 4m11s
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · linux (push) Successful in 4m51s
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Successful in 6m22s
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Successful in 7m45s
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Successful in 9m14s
wai-quantum command line / does this change touch it (push) Has been cancelled
wai-quantum command line / command line · linux (push) Has been cancelled
wai-quantum command line / command line · macos (push) Has been cancelled
wai-quantum contract / does this change touch it (push) Has been cancelled
wai-quantum contract / API compatibility · linux (push) Has been cancelled
wai-quantum contract / schemas and the committed tool lists · linux (push) Has been cancelled
wai-quantum contract / the policy is linked, and the deprecations page reads (push) Has been cancelled
wai-quantum docs, course and locales / does this change touch it (push) Has been cancelled
wai-quantum docs, course and locales / every command and snippet in the docs · linux (push) Has been cancelled
wai-quantum docs, course and locales / the course and the command line's languages · linux (push) Has been cancelled
wai-quantum docs, course and locales / the Studio's words in every locale · linux (push) Has been cancelled
wai-quantum editors / does this change touch it (push) Has been cancelled
wai-quantum editors / editors · linux (push) Has been cancelled
wai-quantum editors / editors · macos (push) Has been cancelled
wai-quantum resource estimation / does this change touch it (push) Has been cancelled
wai-quantum resource estimation / estimation and calibration · linux, release (push) Has been cancelled
wai-quantum other formats / does this change touch it (push) Has been cancelled
wai-quantum other formats / readers, writers and the set · linux (push) Has been cancelled
wai-quantum other formats / the set under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-quantum OpenQASM reader / does this change touch it (push) Has been cancelled
wai-quantum OpenQASM reader / reader and corpus · linux (push) Has been cancelled
wai-quantum OpenQASM reader / corpus under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-quantum pulse and dynamics / does this change touch it (push) Has been cancelled
wai-quantum pulse and dynamics / reader, simulator and pulse jobs · linux, release (push) Has been cancelled
wai-quantum pulse and dynamics / the same bits · linux-arm64 (push) Has been cancelled
wai-quantum pulse and dynamics / the same bits · macos (push) Has been cancelled
wai-quantum pulse and dynamics / reader, simulator and pulse jobs under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-quantum for Python / does this change touch it (push) Has been cancelled
wai-quantum for Python / python · linux (push) Has been cancelled
wai-quantum for Python / python · macos (push) Has been cancelled
wai-quantum error correction and mitigation / does this change touch it (push) Has been cancelled
wai-quantum error correction and mitigation / codes, decoders, texts and mitigation · linux, release (push) Has been cancelled
wai-quantum extension points / does this change touch it (push) Has been cancelled
wai-quantum extension points / the registry and the example · linux (push) Has been cancelled
wai-quantum extension points / the registry and the example · linux-arm64 (push) Has been cancelled
wai-quantum extension points / the registry under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-quantum service / does this change touch it (push) Has been cancelled
wai-quantum service / service · linux (push) Has been cancelled
wai-quantum service / service · macos (push) Has been cancelled
wai-quantum service / service energy · linux packages (push) Has been cancelled
wai-quantum variational and algorithm library / does this change touch it (push) Has been cancelled
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux, release (push) Has been cancelled
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux-arm64, release (push) Has been cancelled
wai-quantum verdicts and repairs / does this change touch it (push) Has been cancelled
wai-quantum verdicts and repairs / verdicts, repairs and the intent set · linux (push) Has been cancelled
wai-quantum verdicts and repairs / the intent set and the verdict under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-quantum views in agent hosts / does this change touch it (push) Has been cancelled
wai-quantum views in agent hosts / views · the page and its engine (push) Has been cancelled
wai-quantum views in agent hosts / views · a scripted host (push) Has been cancelled
wai-quantum-landscape-fpga reference design / does this change touch it (push) Has been cancelled
wai-quantum-landscape-fpga reference design / the member against the model of the logic, and the logic against the model · linux, release (push) Has been cancelled
wai-quantum-landscape-fpga reference design / the board's code on the board's processor's architecture · linux-arm64, release (push) Has been cancelled
wai: wasmQ, an OpenQASM 3 program and the WebAssembly its externs call, run the same everywhere, metered and receipted
Re-landed from PR #659 (branch wai/quantum-wasmq, head 39fdb517a) as one
commit on main, under the land-it model. Five review rounds; the last had
no high or critical finding, and its mediums were fixed on the branch.

A hybrid quantum program runs classical code between its gates. OpenQASM 3
declares such a function with `extern` and leaves its body and linking
outside the language. wasmQ (wai.quantum.wasmq) packs the program and the
WebAssembly that defines its externs into one content-addressed WAI
envelope; a run gives the same result bytes and fuel on every host and
seals the engine's job receipt.

- extensions/quantum-wasmq.md (normative) and quantum-wasmq.wit.
- wai/wai-wasmq: the profile (Lime1, nothing imported, bounded memory,
  refusal codes W001-W017); what a run asks of its host, bounded from the
  bundle alone before anything runs; fuel defined on the module as written;
  a weighted stack; two engines (the pinned wasmtime 50.0.1 and an
  interpreter of the crate's own that also runs on wasm32); the envelope,
  runner, receipts, transcript replay, reference programs and vectors.
- wai-quantum: engine::wasmq (built with quantum_receipt), the job form
  beside its receipts; the estimate verifier answers a request no host
  re-runs with a verdict carrying rerun_error, combined here with main's
  reaction clock (clock_of).
- wai/quantum-conformance/wasmq: envelopes, refusals and receipts.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 13:22:09 -05:00

665 lines
34 KiB
Rust

//! What a review of the profile found, each as a test that failed before its fix
//! (extensions/quantum-wasmq.md): engines that reached different results for one envelope,
//! members of a job no signature covered, input that panicked a reader instead of being
//! refused, and bundles that packed and then could not run.
use serde_json::{Value, json};
use wai_quantum::ed25519_dalek::SigningKey;
use wai_quantum::qasm::{ExternArg, ExternDecl};
use wai_wasmq::abi::{self, Stop};
use wai_wasmq::engine::{Engine, Interpreter, Trap};
use wai_wasmq::envelope::{Bundle, Callout, Module, Run, open, pack, write_unchecked};
use wai_wasmq::interp::Metering;
use wai_wasmq::run::{EngineKind, run};
use wai_wasmq::{meter, profile};
fn wasm(wat: &str) -> Vec<u8> {
wat::parse_str(wat).unwrap()
}
fn leb(mut v: u64) -> Vec<u8> {
let mut out = Vec::new();
loop {
let b = (v & 0x7f) as u8;
v >>= 7;
if v == 0 {
out.push(b);
return out;
}
out.push(b | 0x80);
}
}
fn section(id: u8, body: &[u8]) -> Vec<u8> {
let mut s = vec![id];
s.extend(leb(body.len() as u64));
s.extend_from_slice(body);
s
}
fn raw(sections: &[Vec<u8>]) -> Vec<u8> {
let mut m = b"\0asm\x01\0\0\0".to_vec();
for s in sections {
m.extend_from_slice(s);
}
m
}
/// One type `() -> ()`.
fn unit_type() -> Vec<u8> {
section(1, &[0x01, 0x60, 0x00, 0x00])
}
/// `n` functions of type 0, each an empty body.
fn functions(n: u64) -> (Vec<u8>, Vec<u8>) {
let mut f = leb(n);
f.extend(std::iter::repeat_n(0u8, n as usize));
let mut c = leb(n);
for _ in 0..n {
c.extend_from_slice(&[0x02, 0x00, 0x0b]);
}
(section(3, &f), section(10, &c))
}
fn kinds() -> Vec<EngineKind> {
wai_wasmq::engine::available().iter().map(|s| EngineKind::parse(s).unwrap()).collect()
}
/// Every engine of this build on `module`: the interpreter on the module as written and on
/// its prepared form, and the native engine where the build has it.
fn engines(module: &[u8]) -> Vec<Box<dyn Engine>> {
let module: &'static [u8] = Box::leak(module.to_vec().into_boxed_slice());
let prepared: &'static [u8] = Box::leak(meter::prepare(module).unwrap().into_boxed_slice());
#[allow(unused_mut)] // the native engine is added where this build has it
let mut v: Vec<Box<dyn Engine>> = vec![Box::new(Interpreter::new(module, Metering::Own).unwrap()), Box::new(Interpreter::new(prepared, Metering::Module).unwrap())];
#[cfg(all(feature = "wasmtime", not(target_arch = "wasm32")))]
v.push(Box::new(wai_wasmq::engine::Native::new(prepared).unwrap()));
v
}
const SCALAR: &str = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[8]) -> bit[8];\nqubit[8] q;\nbit[8] b;\nbit[8] r;\nh q;\nb = measure q;\nr = f(b);\n";
const LIST: &str = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[72]) -> bit[72];\nqubit[72] q;\nbit[72] b;\nbit[72] r;\nh q;\nb = measure q;\nr = f(b);\n";
fn bundle(program: &str, module: Vec<u8>, priors: Vec<(String, Vec<u8>)>) -> Bundle {
Bundle { program: program.into(), inputs: Default::default(), modules: vec![Module { name: "m".into(), bytes: module, priors }], callouts: vec![Callout { name: "f".into(), module: 0, export: "f".into() }], run: Run::new(4, 1) }
}
fn code_of<T>(r: Result<T, profile::Refusal>) -> &'static str {
match r {
Ok(_) => "accepted",
Err(e) => e.code,
}
}
/// How a call ended, in words that compare.
fn outcome<T: std::fmt::Debug>(r: Result<T, Stop>) -> String {
match r {
Ok(v) => format!("ok {v:?}"),
Err(Stop::Fault(f)) => format!("fault {}", f.reason),
Err(Stop::Host(_)) => "host".into(),
}
}
/// Runs an opened bundle on every engine of the build: each job's canonical bytes, or why
/// there is none.
fn everywhere(o: &wai_wasmq::Opened) -> Vec<String> {
let key = SigningKey::from_bytes(&[7; 32]);
kinds()
.into_iter()
.map(|k| match run(o, k, &key, false) {
Ok((v, _)) => format!("{}", String::from_utf8_lossy(&wai_quantum::engine::receipt::canonical(&v))),
Err(e) => format!("no receipt on {k:?}: {e}"),
})
.collect()
}
// ---------------------------------------------------------------------------
// 1. One envelope, one receipt: the memory a host reaches
// ---------------------------------------------------------------------------
/// A list-form callout whose module declares a memory and does not export it.
const UNEXPORTED: &str = r#"(module (memory 1 1)
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) i32.const 1024)
(func (export "f") (param i32 i32) (result i32)
i32.const 2048 local.get 0 i32.store i32.const 2052 local.get 1 i32.store i32.const 2048))"#;
#[test]
fn every_engine_reaches_a_module_s_memory_the_same_way() {
let decl = ExternDecl { name: "f".into(), args: vec![ExternArg::Bit(72)], ret: Some(72) };
let input = vec![true; 72];
for (what, module) in [("not exported", wasm(UNEXPORTED)), ("exported", wasm(&UNEXPORTED.replace("(memory 1 1)", "(memory (export \"memory\") 1 1)")))] {
// How the call ended, and the fuel it left: an engine that got further than
// another before it stopped has used more.
let got: Vec<(String, i64)> = engines(&module)
.iter()
.map(|e| {
let mut i = e.instantiate().unwrap();
i.set_fuel(1 << 30);
let r = outcome(abi::call(i.as_mut(), &decl, "f", &input, false));
(r, i.fuel())
})
.collect();
assert!(got.windows(2).all(|w| w[0] == w[1]), "a memory that is {what}: the engines differ: {got:?}");
assert!(got[0].0.starts_with(if what == "exported" { "ok " } else { "fault bad_result" }), "{got:?}");
}
}
#[test]
fn a_bundle_whose_memory_the_host_cannot_reach_is_refused_on_reading() {
// A list-form callout.
assert_eq!(code_of(pack(&bundle(LIST, wasm(UNEXPORTED), vec![]))), "W015");
// A prior, with a scalar callout.
let priors = r#"(module (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))"#;
assert_eq!(code_of(pack(&bundle(SCALAR, wasm(priors), vec![("p".into(), b"prior".to_vec())]))), "W015");
// The positive control: the same two modules with the memory exported pack and run.
for (program, module, pr) in [
(LIST, UNEXPORTED.replace("(memory 1 1)", "(memory (export \"memory\") 1 1)"), vec![]),
(SCALAR, priors.replace("(memory 1 1)", "(memory (export \"memory\") 1 1)"), vec![("p".to_string(), b"prior".to_vec())]),
] {
let env = pack(&bundle(program, wasm(&module), pr)).unwrap();
let jobs = everywhere(&open(&env).unwrap());
assert!(jobs.windows(2).all(|w| w[0] == w[1]) && jobs[0].contains("\"status\":\"done\""), "{jobs:?}");
}
}
// ---------------------------------------------------------------------------
// 2. Nothing a reader takes from a job is outside the signature
// ---------------------------------------------------------------------------
fn a_job(module: &str, per_call: u64) -> (wai_wasmq::Opened, Value) {
let mut b = bundle(SCALAR, wasm(module), vec![]);
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)
}
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))"#;
/// A program past the controls the reader writes in standard gates is refused, and the
/// refusal offers nothing a hybrid program cannot use: its run is on the tableau alone.
#[test]
fn a_refusal_for_controls_offers_no_method_a_hybrid_program_cannot_use() {
let program = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[1]) -> bit[1];\nqubit[18] q;\nbit[1] d;\nctrl(17) @ x q[0], q[1], q[2], q[3], q[4], q[5], q[6], q[7], q[8], q[9], q[10], q[11], q[12], q[13], q[14], q[15], q[16], q[17];\nd = f(d);\n";
let b = Bundle { program: program.into(), inputs: Default::default(), modules: vec![Module { name: "m".into(), bytes: wasm(IDENTITY), priors: vec![] }], callouts: vec![Callout { name: "f".into(), module: 0, export: "f".into() }], run: Run::new(4, 1) };
let e = pack(&b).expect_err("17 controls");
assert!(e.code == "W016" && e.message.contains("Q208"), "{e}");
assert!(!e.message.contains("state vector") && !e.message.contains("bit-exact"), "{e}");
}
/// Each budget is at most 2^39 (§2.2, §4.8), as every verifier holds a contract to it: at
/// the bound a run packs, runs and verifies, and one past it is refused when packed, so the
/// writer never seals a job its verifiers refuse.
#[test]
fn a_budget_past_what_a_run_may_be_given_is_refused_when_packed() {
use wai_wasmq::envelope::MAX_BUDGET;
assert_eq!(MAX_BUDGET, 1 << 39);
type Set = fn(&mut Bundle, u64);
let sets: [(&str, Set); 3] = [("per call", |b, v| b.run.per_call = v), ("per instance", |b, v| b.run.per_init = v), ("per run", |b, v| b.run.per_job = v)];
for (what, set) in sets {
let mut b = bundle(SCALAR, wasm(IDENTITY), vec![]);
set(&mut b, MAX_BUDGET + 1);
let e = pack(&b).expect_err(what);
assert!(e.code == "W017" && e.message.contains("past 2^39"), "{what}: {e}");
if what == "per run" {
continue; // a run given 2^39 asks more than a host has (§4.8), and is refused for its work
}
set(&mut b, MAX_BUDGET);
let o = open(&pack(&b).unwrap_or_else(|e| panic!("{what}: {e}"))).unwrap();
let (body, _) = run(&o, EngineKind::Interpreter, &SigningKey::from_bytes(&[7; 32]), false).unwrap();
let v = wai_wasmq::verify(&body, Some(&o), EngineKind::Interpreter).unwrap();
assert!(v.ok(), "{what} at 2^39: {:?}", v.well_formed);
}
}
/// A job whose contract a JSON writer carried and wrote otherwise (4.0 for 4) is the same
/// sealed content, its receipt the same: given its envelope, the envelope is the one it binds
/// and runs again to the same result, as every reader of the job finds.
#[test]
fn a_contract_written_otherwise_is_the_same_bundle() {
let (o, done) = a_job(IDENTITY, 500);
let mut respelt = done.clone();
let shots = respelt["request"]["wasmq"]["run"]["shots"].as_u64().unwrap();
respelt["request"]["wasmq"]["run"]["shots"] = json!(shots as f64);
assert_ne!(respelt, done, "a number written another way is another value to a reader that keeps its spelling");
let v = wai_wasmq::verify(&respelt, Some(&o), EngineKind::Interpreter).unwrap();
assert!(v.signature && v.well_formed.is_empty(), "{:?}", v.well_formed);
assert_eq!((v.bundle, v.reproduced), (Some(true), Some(true)));
}
#[test]
fn a_job_whose_status_or_method_was_edited_outside_the_signature_is_judged_by_what_is_sealed_and_noted() {
// The verdict is the sealed content's, on every surface alike (§6.5): the host's
// unsealed words decide nothing, and where they differ from what is sealed a reader is
// told, beside the verdict. The outcome a check reports is the sealed result's.
let (o, fault) = a_job(SPIN, 500);
assert_eq!(fault["status"], "fault");
let v = wai_wasmq::verify(&fault, Some(&o), EngineKind::Interpreter).unwrap();
assert!(v.ok() && v.notes.is_empty(), "the positive control: {:?}", v.notes);
let mut edited = fault.clone();
edited["status"] = json!("done");
let v = wai_wasmq::verify(&edited, Some(&o), EngineKind::Interpreter).unwrap();
assert!(v.ok(), "a fault edited to done is the sealed fault's verdict");
assert_eq!(v.outcome.as_deref(), Some("fault"), "the outcome is the sealed result's");
assert_eq!(v.notes, vec![r#"the host's unsealed status says "done"; the sealed result says "fault""#.to_string()]);
let (o, done) = a_job(IDENTITY, 500);
for (member, value, note) in [
("status", json!("fault"), r#"the host's unsealed status says "fault"; the sealed result says "done""#),
("method", json!("statevector"), r#"the host's unsealed method says "statevector"; the sealed result says "stabilizer""#),
("note", json!("a member nothing signs"), "the host's body holds `note`, which nothing seals"),
] {
let mut edited = done.clone();
edited[member] = value;
for bundle in [Some(&o), None] {
let v = wai_wasmq::verify(&edited, bundle, EngineKind::Interpreter).unwrap();
assert!(v.ok(), "the body's `{member}` changed: {:?}", v.well_formed);
assert_eq!(v.notes, vec![note.to_string()], "the body's `{member}` changed");
}
}
let mut bare = done.clone();
bare.as_object_mut().unwrap().remove("status");
assert_eq!(wai_wasmq::verify(&bare, None, EngineKind::Interpreter).unwrap().notes, vec!["the host's body has no `status`; the sealed result gives it".to_string()]);
}
#[test]
fn a_job_names_its_signer_and_a_job_sealed_again_names_another() {
// A receipt shows that a key sealed a result; who holds the key is the reader's to
// compare. So a check names the key, and a result changed and sealed again under
// another key shows as another signer.
let (o, job) = a_job(IDENTITY, 500);
let genuine = wai_wasmq::verify(&job, None, EngineKind::Interpreter).unwrap();
let demo = SigningKey::from_bytes(&[7; 32]);
let hex = |b: &[u8]| b.iter().map(|x| format!("{x:02x}")).collect::<String>();
assert_eq!(genuine.signer.as_deref(), Some(hex(demo.verifying_key().as_bytes()).as_str()));
assert_eq!(genuine.outcome.as_deref(), Some("ok"));
// The forgery: other counts with the same sum, sealed with a key of the forger's.
let mut result = job["result"].clone();
result.as_object_mut().unwrap().remove("receipt");
result.as_object_mut().unwrap().remove("energy");
result["counts"] = json!({ "0000000000000000": 4 });
let other = SigningKey::from_bytes(&[9; 32]);
let forged = wai_wasmq::run::seal(&o, result, &other);
let v = wai_wasmq::verify(&forged, None, EngineKind::Interpreter).unwrap();
assert!(v.signature, "a seal under any key is a valid seal: that is what a receipt is");
assert_eq!(v.signer.as_deref(), Some(hex(other.verifying_key().as_bytes()).as_str()));
assert_ne!(v.signer, genuine.signer, "the forgery shows as another signer");
// And with the bundle, the forged result is not the one a run gives.
assert_eq!(wai_wasmq::verify(&forged, Some(&o), EngineKind::Interpreter).unwrap().reproduced, Some(false));
}
// ---------------------------------------------------------------------------
// 4. A module is refused, never a panic, and is held to the feature set
// ---------------------------------------------------------------------------
#[test]
fn a_malformed_module_is_refused_not_a_panic() {
let cases: [&[u8]; 2] = [
// One type, one function of a type that does not exist, one body.
&[0x00, 0x61, 0x73, 0x6d, 0x01, 0, 0, 0, 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, 0x03, 0x02, 0x01, 0x05, 0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b],
// No type section at all.
&[0x00, 0x61, 0x73, 0x6d, 0x01, 0, 0, 0, 0x03, 0x02, 0x01, 0x05, 0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b],
];
for bytes in cases {
let r = std::panic::catch_unwind(|| profile::check(bytes));
assert_eq!(r.map(code_of).unwrap_or("panicked"), "W001", "{bytes:02x?}");
}
// A function section that declares more functions than any reader holds, with one body.
let mut f = leb(1_000_001);
f.push(0);
let m = raw(&[unit_type(), section(3, &f), section(10, &[0x01, 0x02, 0x00, 0x0b])]);
let r = std::panic::catch_unwind(|| profile::check(&m));
assert!(matches!(r.map(code_of), Ok("W001" | "W010")), "a function count past every reader's limit");
}
#[test]
fn a_body_that_does_not_end_or_is_not_in_the_feature_set_s_encoding_is_refused() {
let memory = section(5, &[0x01, 0x01, 0x01, 0x01]);
let one = section(3, &[0x01, 0x00]);
let cases: [(&str, Vec<u8>); 4] = [
("the positive control: i32.const 0, drop, end", raw(&[unit_type(), one.clone(), section(10, &[0x01, 0x05, 0x00, 0x41, 0x00, 0x1a, 0x0b])])),
("a body with no end", raw(&[unit_type(), one.clone(), section(10, &[0x01, 0x04, 0x00, 0x41, 0x00, 0x1a])])),
("i32.load with a memory index in its alignment", raw(&[unit_type(), one.clone(), memory.clone(), section(10, &[0x01, 0x09, 0x00, 0x41, 0x00, 0x28, 0x42, 0x00, 0x00, 0x1a, 0x0b])])),
("memory.grow with an overlong memory index", raw(&[unit_type(), one.clone(), memory.clone(), section(10, &[0x01, 0x08, 0x00, 0x41, 0x00, 0x40, 0x80, 0x00, 0x1a, 0x0b])])),
];
for (i, (what, m)) in cases.iter().enumerate() {
let r = std::panic::catch_unwind(|| profile::check(m)).map(code_of).unwrap_or("panicked");
assert_eq!(r, if i == 0 { "accepted" } else { "W001" }, "{what}");
}
}
#[test]
fn the_function_limit_is_one_a_module_can_reach() {
let at = |n: u64| {
let (f, c) = functions(n);
code_of(profile::check(&raw(&[unit_type(), f, c])))
};
assert_eq!(at(1 << 16), "accepted", "2^16 functions");
assert_eq!(at((1 << 16) + 1), "W010", "one more");
}
// ---------------------------------------------------------------------------
// 5. A fault is the same fault on every engine
// ---------------------------------------------------------------------------
/// The largest `n` below `past` for which `ok` holds, `ok` holding up to some `n` and no further.
fn largest(past: usize, ok: impl Fn(usize) -> bool) -> usize {
let (mut lo, mut hi) = (1, past);
assert!(ok(lo) && !ok(hi));
while hi - lo > 1 {
let mid = (lo + hi) / 2;
if ok(mid) { lo = mid } else { hi = mid }
}
lo
}
fn call_all(module: &[u8], name: &str, arg: u64) -> Vec<(Result<Vec<u64>, Trap>, i64)> {
engines(module)
.iter()
.map(|e| {
let mut i = e.instantiate().unwrap();
i.set_fuel(1 << 40);
i.set_stack(0);
let r = i.call(name, &[arg]);
(r, i.fuel())
})
.collect()
}
#[test]
fn an_indirect_call_where_the_stack_ends_faults_the_same_way_on_every_engine() {
// `$r` recurses `n` times and then calls through the table with an index outside it.
let m = wasm(
r#"(module (type $t (func (param i64) (result i64))) (table 1 funcref)
(func $r (param i64) (result i64)
local.get 0 i64.eqz
if (result i64) i64.const 0 i32.const 7 call_indirect (type $t)
else local.get 0 i64.const 1 i64.sub call $r end)
(func (export "f") (param i64) (result i64) local.get 0 call $r))"#,
);
// The smallest recursion at which the interpreter stops for the stack.
let own = Interpreter::new(&m, Metering::Own).unwrap();
let stops = |n: u64| {
let mut i = own.instantiate().unwrap();
i.set_fuel(1 << 40);
i.set_stack(0);
i.call("f", &[n]) == Err(Trap::Stack)
};
let (mut lo, mut hi) = (0u64, 1 << 24);
assert!(!stops(lo) && stops(hi));
while hi - lo > 1 {
let mid = (lo + hi) / 2;
if stops(mid) { hi = mid } else { lo = mid }
}
for n in hi.saturating_sub(4)..=hi + 2 {
let got = call_all(&m, "f", n);
assert!(got.windows(2).all(|w| w[0] == w[1]), "recursing {n} times (the stack ends at {hi}): {:?}", got.iter().map(|g| &g.0).collect::<Vec<_>>());
}
}
#[test]
fn every_way_an_indirect_call_traps_is_the_same_on_every_engine() {
let m = wasm(
r#"(module (type $t (func (param i64) (result i64))) (type $u (func (result i64)))
(table 4 funcref) (elem (i32.const 0) $ok $other)
(func $ok (type $t) local.get 0 i64.const 1 i64.add)
(func $other (type $u) i64.const 9)
(func (export "f") (param i64) (result i64) i64.const 5 local.get 0 i32.wrap_i64 call_indirect (type $t)))"#,
);
for (index, want) in [(0u64, Ok(vec![6])), (1, Err(Trap::IndirectCallType)), (2, Err(Trap::IndirectCallNull)), (4, Err(Trap::TableOutOfBounds)), (u32::MAX as u64, Err(Trap::TableOutOfBounds))] {
let got = call_all(&m, "f", index);
assert!(got.windows(2).all(|w| w[0] == w[1]), "index {index}: {got:?}");
assert_eq!(got[0].0, want, "index {index}");
}
}
#[test]
fn an_argument_is_read_as_a_value_of_its_parameter_s_type_on_every_engine() {
// Found by this crate's own sweep of the family, not by the review: the interpreter
// took a 64-bit argument for a 32-bit parameter whole, where the native engine takes
// its low 32 bits. No run reaches it (a host passes values of the type), but the
// engines are held to one answer for any call.
let m = wasm(r#"(module (func (export "i") (param i32) (result i32) local.get 0) (func (export "f") (param f32) (result f32) local.get 0))"#);
for name in ["i", "f"] {
let got = call_all(&m, name, (1 << 40) + 5);
assert!(got.windows(2).all(|w| w[0] == w[1]), "{name}: {got:?}");
assert_eq!(got[0].0, Ok(vec![5]), "{name}");
}
}
// ---------------------------------------------------------------------------
// 6. The native engine holds every call the profile admits
// ---------------------------------------------------------------------------
/// The reviewer's module: a function of forty parameters that recurses to the limit and
/// then holds tens of thousands of values across calls in two dozen locals.
fn rotating(steps: usize, depth: u64) -> Vec<u8> {
let mut body = format!("local.get 0 i64.const {depth} i64.lt_u if local.get 0 i64.const 1 i64.add ");
for p in 1..40 {
body.push_str(&format!("local.get {p} "));
}
body.push_str("call $f drop end local.get 0 i64.const 99999999 i64.eq if ");
for k in 0..steps {
body.push_str(&format!("i32.const 0 i64.load local.set {} call $g local.get {} local.get 64 i64.add local.set 64 ", 40 + k % 24, 40 + (k + 1) % 24));
}
body.push_str("end local.get 64 ");
for p in 0..40 {
body.push_str(&format!("local.get {p} i64.add "));
}
let consts: String = (1..40).map(|p| format!("i64.const {p} ")).collect();
wasm(&format!(
"(module (memory 1 1) (func $g i32.const 0 i32.const 0 i64.load i64.const 1 i64.add i64.store) (func $f (param {}) (result i64) (local {}) {body}) (func (export \"f\") (param i64) (result i64) local.get 0 {consts} call $f))",
"i64 ".repeat(40),
"i64 ".repeat(25)
))
}
#[test]
fn a_module_the_profile_admits_runs_on_every_engine_at_the_depth_it_admits() {
// As the review gave it, 40,000 steps recursing 1023 times: a body past the profile's.
assert_eq!(profile::check(&rotating(40_000, 1023)).unwrap_err().code, "W010");
// The most steps a body the profile admits holds, at the depth the stack admits, and
// one deeper: the same on every engine, and the first runs where the second faults.
let steps = largest(4000, |s| profile::check(&rotating(s, 1)).is_ok());
assert!(steps > 500, "{steps} steps");
let w = profile::check(&rotating(steps, 1)).unwrap().weights;
let depth = (u64::from(profile::MAX_STACK) - u64::from(w[2])) / u64::from(w[1]);
for (d, ok) in [(depth, true), (depth + 1, false)] {
let got = call_all(&rotating(steps, d), "f", 0);
assert!(got.windows(2).all(|w| w[0] == w[1]), "the engines differ at depth {d}: {:?}", got.iter().map(|g| format!("{:?}", g.0).chars().take(90).collect::<String>()).collect::<Vec<_>>());
assert_eq!(got[0].0.is_ok(), ok, "depth {d}: {:?}", got[0].0);
}
}
// ---------------------------------------------------------------------------
// 7. What is packed is what was given
// ---------------------------------------------------------------------------
#[test]
fn an_input_a_double_cannot_hold_is_refused_not_changed() {
let program = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\ninput uint[64] mask;\nextern f(uint[64]) -> bit[8];\nqubit[8] q;\nbit[8] r;\nr = f(mask);\nh q;\nbit[8] b;\nb = measure q;\n";
let module = wasm(IDENTITY);
for mask in [(1i64 << 53) + 1, i64::MAX - 100] {
let mut b = bundle(program, module.clone(), vec![]);
b.inputs.insert("mask".into(), mask);
match pack(&b) {
Err(e) => assert_eq!(e.code, "W017", "{e}"),
Ok(env) => assert_eq!(open(&env).map(|o| o.bundle.inputs).map_err(|e| e.to_string()), Ok(b.inputs.clone()), "pack wrote another input than the one given ({mask})"),
}
}
// The positive control: the largest input a double holds is packed as it is.
let mut b = bundle(program, module, vec![]);
b.inputs.insert("mask".into(), (1i64 << 53) - 1);
b.run.method = "stabilizer".into();
assert_eq!(open(&pack(&b).unwrap()).unwrap().bundle, b);
}
// ---------------------------------------------------------------------------
// 8. The profile runs what its text defines, and no more
// ---------------------------------------------------------------------------
#[test]
fn a_program_outside_the_clifford_gates_is_refused() {
// A gate outside the group, and a gate that takes an angle, whatever the angle.
for gate in ["t q[0];", "rx(0.3) q[0];", "rz(pi / 2) q[0];", "ccx q[0], q[1], q[2];"] {
let program = SCALAR.replace("h q;", &format!("h q;\n{gate}"));
assert_eq!(code_of(pack(&bundle(&program, wasm(IDENTITY), vec![]))), "W016", "{gate}");
}
assert_eq!(code_of(pack(&bundle(SCALAR, wasm(IDENTITY), vec![]))), "accepted");
}
#[test]
fn a_program_of_more_qubits_than_the_method_holds_is_refused_on_reading() {
let program = |n: u32| format!("OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[8]) -> bit[8];\nqubit[{n}] q;\nbit[8] b;\nbit[8] r;\nh q[0];\nb[0] = measure q[0];\nr = f(b);\n");
assert_eq!(code_of(pack(&bundle(&program(32_769), wasm(IDENTITY), vec![]))), "W016");
// The largest the method holds is taken (its tableau is half a gibibyte: not run here).
assert!(open(&pack(&bundle(&program(32_768), wasm(IDENTITY), vec![])).unwrap()).is_ok());
// And a program of many qubits runs: 3000 here.
let jobs = everywhere(&open(&pack(&bundle(&program(3000), wasm(IDENTITY), vec![])).unwrap()).unwrap());
assert!(jobs.windows(2).all(|w| w[0] == w[1]) && jobs[0].contains("\"status\":\"done\""), "{}", &jobs[0][..jobs[0].len().min(200)]);
}
#[test]
fn a_measurement_or_reset_takes_a_value_only_when_its_outcome_is_random() {
// §6.1, worked by hand for this program: `a` is put in superposition and reset (its
// outcome is random: the first value of the shot's stream); `q` is reset while still
// in |0> (determined: no value); then each of q's eight measurements is random, and
// takes the next value in order. `r` is `f(b)`, and `f` returns what it is given.
let program = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[8]) -> bit[8];\nqubit[8] q;\nqubit a;\nbit[8] b;\nbit[8] r;\nh a;\nreset a;\nreset q;\nh q;\nb = measure q;\nr = f(b);\n";
let mut bun = bundle(program, wasm(IDENTITY), vec![]);
bun.run = Run::new(200, 42);
let o = open(&pack(&bun).unwrap()).unwrap();
let mut want: std::collections::BTreeMap<String, u64> = Default::default();
for shot in 0..200 {
let mut stream = wai_wasmq::run::Outcomes::of_shot(42, shot);
stream.next_reads_one(); // the reset of `a`
let b: Vec<bool> = (0..8).map(|_| stream.next_reads_one()).collect();
// The outcome: r then b, each with its highest bit first.
let half: String = (0..8).rev().map(|i| if b[i] { '1' } else { '0' }).collect();
*want.entry(format!("{half}{half}")).or_default() += 1;
}
let jobs = everywhere(&o);
assert!(jobs.windows(2).all(|w| w[0] == w[1]));
let job: Value = serde_json::from_str(&jobs[0]).unwrap();
assert_eq!(job["result"]["counts"], json!(want));
// The stream is the text's: SplitMix64 from the shot's seed XOR 0x51574d31, a value
// below 2^63 reading 1.
let mut state = wai_wasmq::run::shot_seed(42, 7) ^ 0x5157_4d31;
let mut stream = wai_wasmq::run::Outcomes::of_shot(42, 7);
for _ in 0..100 {
state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = state;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
assert_eq!(stream.next_reads_one(), (z ^ (z >> 31)) >> 63 == 0);
}
}
// ---------------------------------------------------------------------------
// 11. What packs, runs; and each rule has its code
// ---------------------------------------------------------------------------
#[test]
fn what_pack_accepts_every_engine_can_run() {
// `(func)` and an exported function of `calls` calls of it.
let module = |calls: usize| {
let mut body = vec![0x00];
for _ in 0..calls {
body.extend_from_slice(&[0x10, 0x00]);
}
body.extend_from_slice(&[0x20, 0x00, 0x0b]);
let mut code = leb(2);
code.extend_from_slice(&[0x02, 0x00, 0x0b]);
code.extend(leb(body.len() as u64));
code.extend_from_slice(&body);
let types = section(1, &[0x02, 0x60, 0x00, 0x00, 0x60, 0x01, 0x7e, 0x01, 0x7e]);
raw(&[types, section(3, &[0x02, 0x00, 0x01]), section(7, &[0x01, 0x01, b'f', 0x00, 0x01]), section(10, &code)])
};
// As the review gave it, 183,000 calls: a body past the profile's.
assert_eq!(pack(&bundle(SCALAR, module(183_000), vec![])).unwrap_err().code, "W010");
// The most calls a body the profile admits holds: packed, and run the same everywhere.
let calls = largest(10_000, |n| profile::check(&module(n)).is_ok());
assert!(calls > 8000, "{calls} calls");
let env = pack(&bundle(SCALAR, module(calls), vec![])).unwrap();
let jobs = everywhere(&open(&env).unwrap());
assert!(jobs.windows(2).all(|w| w[0] == w[1]), "the engines differ");
assert!(jobs[0].contains("\"status\":\"ok\""), "{}", &jobs[0][..jobs[0].len().min(200)]);
}
#[test]
fn a_type_outside_the_profile_has_its_rule_s_code_with_or_without_an_instruction() {
for (wat, code) in [
("(module (func (param externref)))", "W007"),
("(module (func (local funcref)))", "W007"),
("(module (type (struct (field i32))))", "W007"),
("(module (func (param v128)))", "W006"),
("(module (func (local v128)))", "W006"),
("(module (func (param i64)))", "accepted"),
] {
assert_eq!(code_of(profile::check(&wasm(wat))), code, "{wat}");
}
}
#[test]
fn an_export_the_host_calls_has_exactly_its_type() {
let with = |export: &str| format!(r#"(module (memory (export "memory") 1 1) {export} (func (export "f") (param i64) (result i64) local.get 0))"#);
for (export, code) in [
(r#"(func (export "wasmq-seed") (param i64))"#, "accepted"),
(r#"(func (export "wasmq-seed") (param i32))"#, "W012"),
(r#"(func (export "wasmq-seed") (param f64))"#, "W012"),
(r#"(func (export "init") (param i32))"#, "W012"),
(r#"(func (export "wasmq-prior") (param i32 i32 i64))"#, "W012"),
(r#"(func (export "wasmq-prior") (param i32 i32 i32))"#, "accepted"),
(r#"(func (export "cabi_realloc") (param i64 i64 i64 i64) (result i64) i64.const 0)"#, "W012"),
(r#"(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) i32.const 0)"#, "accepted"),
(r#"(func (export "cabi_post_f") (param i32 i32))"#, "W012"),
(r#"(func (export "cabi_post_f") (param i32))"#, "accepted"),
] {
assert_eq!(code_of(profile::check(&wasm(&with(export)))), code, "{export}");
}
}
#[test]
fn a_contract_names_one_callout_for_each_extern_in_the_order_declared() {
let two = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nextern f(bit[8]) -> bit[8];\nextern g(bit[8]) -> bit[8];\nqubit[8] q;\nbit[8] b;\nbit[8] r;\nh q;\nb = measure q;\nr = f(b);\nr = g(r);\n";
let module = wasm(r#"(module (func (export "f") (param i64) (result i64) local.get 0) (func (export "g") (param i64) (result i64) local.get 0))"#);
let callout = |name: &str, module: usize, export: &str| Callout { name: name.into(), module, export: export.into() };
let with = |program: &str, callouts: Vec<Callout>, modules: usize| {
let mut b = bundle(program, module.clone(), vec![]);
b.modules = (0..modules).map(|k| Module { name: format!("m{k}"), bytes: module.clone(), priors: vec![] }).collect();
b.callouts = callouts;
code_of(open(&write_unchecked(&b, "stabilizer", 8).unwrap()))
};
assert_eq!(with(two, vec![callout("f", 0, "f"), callout("g", 0, "g")], 1), "accepted");
assert_eq!(with(two, vec![callout("g", 0, "g"), callout("f", 0, "f")], 1), "W015", "the other order");
assert_eq!(with(SCALAR, vec![callout("f", 0, "f"), callout("f", 0, "g")], 1), "W015", "two callouts for one extern");
assert_eq!(with(SCALAR, vec![callout("f", 0, "f"), callout("f", 7, "anything")], 1), "W015", "a second callout nothing checks");
assert_eq!(with(SCALAR, vec![callout("f", 0, "f")], 2), "W015", "a module no callout names");
}
#[test]
fn a_module_with_priors_exports_what_the_host_calls_to_pass_them() {
let module = r#"(module (memory (export "memory") 1 1) (func (export "wasmq-prior") (param i32 i32 i32)) (func (export "f") (param i64) (result i64) local.get 0))"#;
assert_eq!(code_of(pack(&bundle(SCALAR, wasm(module), vec![("p".into(), vec![1, 2, 3])]))), "W015", "priors and no cabi_realloc");
assert_eq!(code_of(pack(&bundle(SCALAR, wasm(module), vec![]))), "accepted", "no priors");
}
#[test]
fn a_result_holds_only_members_the_text_defines() {
let (_, done) = a_job(IDENTITY, 500);
let r = &done["result"];
let keys = |v: &Value| v.as_object().map(|o| o.keys().cloned().collect::<Vec<_>>()).unwrap_or_default();
let mut k = keys(&r["modules"][0]);
k.sort();
assert_eq!(k, ["blake3", "name"], "a module's members");
assert_eq!(r["accuracy"], json!({ "class": "exact" }));
let trap = r#"(module (func (export "f") (param i64) (result i64) i64.const 256))"#;
let (_, fault) = a_job(trap, 500);
let mut k = keys(&fault["result"]["fault"]);
k.sort();
assert_eq!(fault["result"]["fault"]["reason"], "bad_result");
assert!(!k.contains(&"detail".to_string()), "a fault's members: {k:?}");
}