WAI Extension: Quantum Providers (wai.quantum.provider/1)
Mirrored from the canonical text at commit 260cbd09 ().
Status: Draft. Client, reference provider and conformance checker:
wai-quantum-qpu (src/open_provider.rs, src/reference.rs,
src/conformance.rs); vectors: wai-quantum-qpu/conformance/provider-v1.json.
The engine’s side of a provider job: wai-quantum/src/engine/provider.rs.
Keywords MUST, MUST NOT, SHOULD and MAY are RFC 2119/8174.
0. What this extension adds
Bring your own quantum. WAI provides the engine, the free job service and the verifier at no cost, and never pays for or resells time on anyone’s hardware. A person connects their own quantum hardware, provider accounts, on-premises devices or simulators, and every surface treats a provider’s result as it treats a WAI result: planned, its cost shown before it is submitted, checked, and sealed in a receipt.
This text is the protocol a device speaks so that it can be brought without a client written for it: JSON messages, one request and one answer at a time, over HTTP or to a local process (§2). Six operations (§5) describe the device, estimate a job, submit it, follow it, read its counts and cancel it. A program travels in the device’s own native gates, with every angle an exact fraction of a turn (§4), so the program the device runs is the one the client compiled and checked.
A provider is the program or service that answers the messages. A device is what it runs programs on: a processor, an emulator or a simulator. A client is what sends the messages; a host is the client’s process, which holds the person’s credential and signing key (the command line, the desktop app).
1. Principles
- The credential stays with the person. A host keeps a provider’s credential in the operating system’s keychain (or, where there is none, a file only its owner can read, labelled as such) and sends it only to that provider (§2). A credential MUST NOT appear in a browser page, a log, a receipt, a command line, an answer the host prints, or the hosted job service. The job service never routes a job to a provider and never takes a credential: a request that names a provider is refused there.
- Cost before submit. A client MUST show a job’s estimate (§5.2) before it submits the job, and MUST NOT submit a job that is not free without the person’s explicit confirmation (§7). An unknown cost is not free.
- Exact programs. A client compiles a circuit exactly to the device’s native gates and connectivity and checks the compiled program against the circuit before sending it. Angles cross the wire exactly (§4).
- Returned, not recomputed. A provider’s counts are sealed as the device
it names returned them (§8):
measuredfor a processor,simulatedfor a simulator or an emulator, which no device measured. The result says where the counts come from, since counts rebuilt from probabilities are derived and not shot records, and what was set off at the provider. Nothing recomputes them.
2. Transports
Every message is a JSON object, and so is every answer. A client sends one message and reads its answer before it sends the next on the same transport.
2.1 HTTP
A message is the body of a POST to the provider’s endpoint (one URL for
every operation), with Content-Type: application/json. The answer is the
body of a 200 response, also for an answer with "ok": false (§3). Any
other status is a transport failure, not an answer.
A credential, when there is one, goes in Authorization: Bearer <credential>.
A client MUST NOT send a credential over http:// to any host but this
machine (localhost, a loopback address); over the network it uses
https://.
2.2 A local process
The provider is a program the person names, with its arguments, started by the client without a shell. Each message is one line of JSON on the program’s standard input; each answer is one line of JSON on its standard output, in order. The program MUST write nothing else to its standard output. Its standard error is its own log, and a client MAY discard it. The program runs until its standard input ends.
A credential, when there is one, is in the program’s environment as
WAI_PROVIDER_CREDENTIAL, never on its command line. A client SHOULD NOT
pass the program another provider’s credential.
3. Messages and errors
Every message carries:
| field | |
|---|---|
protocol | "wai.quantum.provider/1" |
op | describe, estimate, submit, status, result or cancel |
settings | optional: the person’s non-secret settings for this provider (a device name, a region), passed as they are |
and the operation’s own fields (§5). Every answer carries "ok": true and
the operation’s fields, or:
{ "ok": false, "error": { "code": "not_found", "message": "no job j-17" } }
The message is for a person; it MUST NOT quote a credential. The codes:
| code | when |
|---|---|
invalid | the message is malformed: a field missing or of the wrong type, a program that does not read (§4), zero shots, a message without protocol |
unsupported | an operation the provider does not offer (cancel, estimate), or an op it does not know |
unsupported_version | a protocol other than this one |
unauthorized | no credential, or one the provider does not accept |
not_found | a job the provider does not hold |
refused | a well-formed program the device cannot run: a gate outside its basis, a qubit it does not have, a pair it does not connect, too many shots; or a result asked of a job that has not finished or was cancelled |
quota | the person’s account cannot pay for or schedule the job |
unavailable | the device is offline or in maintenance |
internal | anything else |
4. The program
estimate and submit carry a program in the device’s own gates, on its
physical qubits:
{
"format": "wai.native/1",
"basis": "rzsxxcz",
"bits": 2,
"gates": [
{ "gate": "rz", "qubits": [0], "turns": [[1, 2]] },
{ "gate": "sx", "qubits": [0] },
{ "gate": "cz", "qubits": [0, 1] }
],
"measure": [{ "qubit": 0, "bit": 0 }, { "qubit": 1, "bit": 1 }],
"qasm3": "OPENQASM 3.0; …"
}
-
basisis one of three gate sets, and every gate is of it:basis gates rzsxxczrz(t),sx,x,czprxczprx(t, p),czgpizzgpi(p),gpi2(p),zz(t) -
An angle is a fraction of a full turn,
[m, k]meaningm/2^kof 2π, with0 ≤ m < 2^kandk ≤ 32, so every angle crosses the wire exactly. The gates, as operators on the named qubits, up to a global phase:rz(t)= exp(−iπt·Z);sx= √X;x= X;cz= diag(1, 1, 1, −1);prx(t, p)= Rz(2πp)·Rx(2πt)·Rz(−2πp);gpi(p)= Rz(2πp)·X·Rz(−2πp);gpi2(p)= Rz(2πp)·Rx(π/2)·Rz(−2πp);zz(t)= exp(−iπt·Z⊗Z).
-
measuregives, for each classical bit0 … bits − 1, the physical qubit it reads at the end of the program. Every bit reads exactly one qubit, and no qubit is read twice. -
qasm3is the same program as OpenQASM 3 on physical qubits ($p), theprxczandgpizzgates defined in it from the standard gates, for a provider that reads OpenQASM. It is informative: where the two differ, thegatesare the program.
A provider MUST refuse (invalid) a program of another format, a gate
outside its basis, an angle out of range, or a bit that reads no qubit; and
(refused) a program in a basis its device does not run, or on qubits or
pairs it does not have.
5. Operations
5.1 describe
No fields. The answer describes the device:
| field | |
|---|---|
protocol | "wai.quantum.provider/1" |
provider, device | the provider’s name for itself, and the device’s |
provenance | silicon (a processor), emulator, or simulator; with simulator (or emulator) a sentence naming what it is, in the member of that name ("simulator": "…") |
qubits | the device’s qubit count |
native_gates | the gates it runs; a client compiles to the first of the bases of §4 whose every gate is listed |
connectivity | "all", or the pairs [a, b] a two-qubit gate may act on, in either order |
durations | optional: {"unit", "gates", "measure"}: the unit (ns, us, ms or s), each native gate’s duration wherever it runs ({"sx": 35, "cz": 68}), and the measurement’s |
calibration | {"id"}, the calibration snapshot the device is under; optionally qubits ({"qubit", "readout_error", "error_1q", "t1_us", "t2_us"}) and edges ({"qubits": [a, b], "error"}), each error from 0 to 1 |
billing | {"free": true}, or {"unit", "price", "currency"}: the billing unit (shot, job, second, …), the price of one as a decimal string ("0.0003"), and the currency or credit it is in; optionally free_tier, the free use offered and its limits, as the provider states them |
max_shots | optional: the most shots a job may ask |
seeded | optional: true when a job’s seed makes its counts reproducible (a simulator) |
approximate | optional, with simulator or emulator: true when its results are approximate (a truncated tensor network, a sampled Pauli path); a result is then labelled so (§8.1) |
A client MUST NOT take a device to be free unless billing says
"free": true, or an estimate says so (§5.2).
A client MAY read a describe answer whose calibration states errors as a
device target (wai.quantum.target/1, wai-quantum/src/engine/targets)
and place the program on the qubits and pairs most likely to succeed. The
program it sends is checked against the circuit as every program is (§1), and
the receipt names how it was placed (§8).
5.2 estimate
Fields: program (§4), shots. The answer, before anything is submitted:
| field | |
|---|---|
free | whether this job is free |
amount, currency | when it is not: the cost, the amount as a decimal string |
unit | optional: the billing unit the cost counts |
seconds | optional: how long it is expected to take, queue included |
note | optional |
A provider that cannot estimate answers unsupported; the client then treats
the cost as unknown (§7).
5.3 submit
Fields: program, shots, and optionally seed (a seeded simulator uses
it; a device ignores it). The answer’s job is the job’s id.
5.4 status
Field: job. The answer’s status is queued, running, done, failed
or cancelled, with an optional reason.
5.5 result
Field: job. The answer, once the job is done:
| field | |
|---|---|
counts | an object from outcomes to counts (§6), summing to the shots |
shots | the shots |
calibration | optional: the calibration snapshot the job ran under, when it is not the one describe names |
charged | optional: {"amount", "currency"}, what the job cost |
energy | optional: {"kwh"}, the provider’s own figure for the job’s energy |
counts_source | optional: where the counts come from (§8.3): shots (tallied from one record a shot), vendor_counts (the device’s or service’s own counts), or probabilities (rebuilt from probabilities); an answer without it is read as vendor_counts |
execution | optional: how the device ran the program (§8.3): each of mitigation, twirling, decoupling, remapping and optimisation as off, on, none (the device has no such step) or unknown (a step the provider cannot vouch for), verbatim (true when the gates ran as written, on the qubits named), and an optional note |
A result asked twice of the same job MUST give the same counts.
A provider that relays a job to another service (a wrapper around a service’s
own interface) MUST give execution, from what it sets in its own request, and
MUST give counts_source, probabilities when it rebuilds counts from them.
Each is the provider’s word, and sealed as such (§8.3). A client refuses a
counts_source or an execution not of the form above, and seals nothing.
5.6 cancel
Field: job. The answer’s status is cancelled, or done for a job that
had already finished. A provider that cannot cancel answers unsupported.
5.7 A job outlasts its client
A device’s queue can outlast any process waiting on it, so a job is not tied to
the connection or the process that submitted it. A provider SHOULD answer
status and result for a job’s id from any later connection, and SHOULD keep
a finished job’s result for as long as it can, and answers not_found for one
it no longer holds. A provider that runs as a local process (§2.2) ends with its
client unless it keeps its jobs itself.
A client:
- SHOULD record a job before it submits it, and the job’s id as soon as it has one, with the program it sent, since a device’s calibration can change before the counts are read;
- MUST NOT cancel a job only because it stopped waiting for it, unless it keeps no record to come back to;
- MUST NOT submit a job again when its
submitdid not complete, without the person’s confirmation: the first may have been taken, and may be billed.
6. Counts and bit order
An outcome is a string of bits characters, 0 or 1, the highest bit
first: its last character is bit 0. "01" is bit 0 read as 1 and bit 1 read
as 0. This is OpenQASM’s order, and the job service’s.
The client compiles each logical qubit i of the person’s circuit to end on
the physical qubit that bit i measures, and reads the counts back onto the
program’s own classical bits. The first job on a new provider SHOULD be the
probe (an X on qubit 0, every qubit measured), whose counts must all land
on …01; the conformance vectors begin with it (§9).
7. Cost and confirmation
A job is free when the provider’s estimate says "free": true, or, for a
provider that gives no estimate, when its billing says so. Every other job,
an unknown cost included, is not free. A client:
- MUST show the estimate (or that the cost is unknown) before submitting;
- MUST NOT submit a job that is not free without the person’s explicit
confirmation for that run: a command-line flag (
wai-quantum run --provider NAME --yes), an agent tool’s argument the agent sets only after the person agreed (run_on_providerwithconfirm_cost: true), or a dialog (the desktop app); - MUST NOT hold a standing confirmation across runs.
A tool that can submit a job that is not free MUST say in its description that it can spend the person’s money.
8. The receipt
The host seals each provider job as the job service seals its own, and the
same verifier checks both: an Ed25519 signature, by the host’s key, over the
request as submitted and the result as returned (§8.2). A job’s body is an
object holding request, the job as it was asked, and result, which carries
its receipt. This section says what a provider’s result carries (§8.1), how
it is sealed and read (§8.2), what its counts’ source and its execution say
(§8.3), what a verifier checks and the reason it gives for each failure
(§8.4), and what the receipt proves (§8.5). A verifier of §8.2 and §8.4 checks
any job’s receipt, a provider’s or not.
8.1 What a provider’s result carries
| member | |
|---|---|
method | provider |
accuracy | class: measured when the provider’s provenance is silicon, simulated when it is a simulator’s or an emulator’s (no device measured those counts); approximate: true, only on a simulated result whose provider’s describe says approximate (§5.1), and absent otherwise; note, a sentence |
provider | name (the person’s name for it, the request’s provider), kind (sampler-v2, prx-circuits, native-turns, cloud-tasks, http or process), protocol (wai.quantum.provider/1, or a built-in client’s own), device, provenance (silicon; or simulator or emulator, alone or followed by : and the sentence naming it), qubits (the device’s), basis (§4), and describe, the BLAKE3 of the RFC 8785 text (§8.2) of the provider’s describe answer (for a built-in kind, of the description its client makes of the device). provenance is silicon exactly, or its part before any : is simulator or emulator |
calibration | the calibration snapshot the job ran under, as the provider names it, or null |
compiled | the program sent: basis; qasm3, its OpenQASM 3 text, $i being index i of the coupling map the provider described; qubit_names, the device’s own name for each index the program uses, where a client sends it under those names (a cloud-tasks processor’s labels, each index itself on its simulator; a prx-circuits device’s qubits), each as it was when the job was sent; program, the BLAKE3 of qasm3’s UTF-8 bytes; hash, a sentence saying so; native_gates, two_qubit_gates and swaps, its counts; layout and final_layout, for each logical qubit the index it starts and ends on; checked, the check it passed before it was sent; placement, how it was placed (with a calibration read as a target, the target’s hash, the layout’s expected success and the fewest-swaps layout’s) |
job | id, the provider’s id for the job, and polls, the status checks before it finished |
counts | keyed by the program’s classical bits (§6), each outcome that occurred with its count, at least 1 (a host drops an outcome a provider counts 0); with distinct_outcomes, the outcomes that occurred, and omitted_shots, the shots of outcomes not listed (at most 65,536 outcomes are listed). Every provider result an engine has sealed carries these, the earlier form’s included |
shots | the shots: the request’s, 1024 when it gives none |
counts_source | where the counts come from (§8.3) |
execution | how the device was asked to run the program (§8.3) |
ideal | the circuit’s exact distribution p set beside the counts’ frequencies q (up to 22 qubits): computed, and when it is true total_variation_distance (½ Σ |q − p|), hellinger_fidelity ((Σ √(p·q))²), sampling_tvd_expected (½ Σ √(2p(1 − p)/(π·shots)), the distance the shots alone leave on average, by the normal approximation) and ideal_outcomes; when false, why. The host computes them, and no verifier can without the program’s simulation |
cost | free; estimate, the estimate shown before submitting (an object, with unknown, a sentence, when none was known); charged, what the provider says it charged ({"amount", "currency"}), or null; confirmed, true exactly when the run was not free: such a run ran only with the person’s confirmation (§7), and a free run needs none and records none |
energy | unmetered, saying why it is not metered here; and, when the provider gives one, its own figure service_kwh, labelled as its own by service_kwh_is |
engine, schema | the engine that made the result ({"name": "wai-quantum", "version"}) and wai.quantum.job-result/1 |
A result sealed before results carried counts_source and execution is of
the earlier form: it carries neither, its class is measured whatever ran
it, and some of the members above are missing. It verifies by §8.4’s rules
for that form. A verifier reads its class by its provenance, and says so: a
simulator’s counts sealed as measured are a simulator’s.
A job’s id is the provider’s, sealed: some name the person’s account (a
cloud account’s number inside a task’s identifier). Sharing a receipt shares
it.
8.2 How it is sealed, and how a job is read
The seal. The result’s receipt is an object:
| member | |
|---|---|
alg | ed25519 |
canon | rfc8785; absent on a receipt of the first form (below) |
key | the signer’s Ed25519 public key A, its 32 bytes as 64 hex digits |
digest | the digest signed, 32 bytes as 64 hex digits |
signature | the signature R ‖ S, 64 bytes as 128 hex digits |
Hex digits are lower case, 0–9 and a–f, and nothing else. The receipt
is not sealed, so each member has one spelling. Any other member of the
receipt is ignored.
The digest is BLAKE3 (32 bytes, the hash mode) of
d ‖ u64(|Q|) ‖ Q ‖ u64(|T|) ‖ T
where d is the 27 ASCII bytes wai:quantum-service-receipt and then the one
byte 0x02 (0x01 for the first form); Q is the canonical text of request;
T is the canonical text of result with its receipt member removed; and
u64(n) is n as 8 bytes, big-endian. The signature is Ed25519 as RFC 8032
§5.1 defines it, with no context and not prehashed, over the 32 bytes of the
digest. Since the domain byte is inside the digest, a receipt of one form does
not verify as the other.
Only request and result are sealed. Whatever else a job’s body holds (its
status, id, method, why, estimate and times) is the host’s, and
unsealed: a verifier takes nothing from it, and its verdict does not change
with it.
Reading. A job is read from its text, strictly, before anything is checked. The text is UTF-8 with no byte order mark, and one JSON value (RFC 8259) with whitespace allowed around it. It is unreadable when:
- an object names a member twice (names compared after their escapes are read);
- a string holds a lone surrogate: a
\uescape of a high surrogate not followed at once by one of a low surrogate, or of a low surrogate alone; - a string holds a control character (U+0000 to U+001F) not escaped;
- a number’s magnitude is 2^1024 − 2^970 or more, which rounds to an infinite double;
- arrays and objects nest deeper than 127, the outermost counted as the first.
A noncharacter (U+FDD0 to U+FDEF, and the last two code points of each plane)
is a character like any other. Each number is read as the nearest IEEE 754
double, ties to even; a number too small for any double other than zero reads
as zero. A whole number written as one (a literal with no fraction and no
exponent) in the request or the result that no double holds exactly says more
than the receipt seals, since the seal holds its double (§8.4,
inexact_number), unless it is the canonical text (below) of the double it
reads as. That one spelling is RFC 8785’s for a whole double whose magnitude is
2^53 or more and under 10^21 (a negative one with its -), its shortest digits
padded with zeros (2^60 as
1152921504606847000), and what any JSON writer that follows RFC 8785 or
ECMAScript writes: it reads as the double that was sealed, and no other
spelling of an inexact whole number is taken. A canonical receipt seals every number as its double; a
first-form receipt seals a whole number written as one in its writer’s integer
range by its digits, and every other number by its double (below), and only
what a receipt seals as a double is held to this.
The canonical text of a value is RFC 8785’s:
true,falseandnullas they are;- a string between
"s, with"and\escaped as\"and\\; U+0008, U+0009, U+000A, U+000C and U+000D as\b,\t,\n,\fand\r; every other character below U+0020 as\u00and two lower-case hex digits; and every other character as its own UTF-8 bytes, unescaped (/and U+007F included) and not normalised; - a number as ECMAScript’s Number::toString writes the double it reads as
(ECMA-262, 6.1.6.1.20): zero (also negative zero) as
0; otherwise, withsthe fewest decimal digitsd1 … dkthat read back as the double (of those, the nearest to it, a tie going to the even last digit) andnsuch that the value is 0.s× 10^n: if k ≤ n ≤ 21, the digits and n − k zeros; if 0 < n ≤ 21, the digits with a.after the first n; if −6 < n ≤ 0,0., −n zeros, and the digits; otherwised1, then.andd2 … dkwhen k > 1, thene, a sign (+or-), and |n − 1| in decimal; a negative number is the same with-before it; - an array as
[, its values’ texts joined by,, and]; - an object as
{, its members joined by,, each the name’s text,:and the value’s text, in the order of their names compared as sequences of UTF-16 code units (unsigned), and}; - with no whitespace anywhere outside strings.
So a result verifies after any JSON reader and writer has carried it, even
one that writes 1.0 as 1 or orders members otherwise.
The first form. The first receipts, which name no canon, sealed the text
their signer’s JSON writer wrote, not the canonical text, under the domain byte
0x01. A verifier reads Q and T as that writer did: the value’s compact
text (no whitespace outside strings), its object members in the order the job
gives them (the result’s receipt removed, the others kept in order), its
strings as in the canonical text, and its numbers so:
- a whole number written as one (no fraction, no exponent), without a
-and from 0 to 2^64 − 1, or with a-and from −2^63 to −1, is written as its decimal digits, with its-(so-0is a double:-0.0); - every other number is written from its double:
-0.0for negative zero, and0.0for zero; otherwise, withs=d1 … dkandnas above, if n ≥ k and n ≤ 16, the digits, n − k zeros and.0; if 0 < n ≤ 16, the digits with a.after the first n; if −5 < n ≤ 0,0., −n zeros and the digits; otherwised1, then.andd2 … dkwhen k > 1, thene, a sign (+or-), and |n − 1| in decimal (1e+16,1e-7); a negative number is the same with-before it.
The first form was sealed from 2026-10-17 to 2026-10-20, every receipt by one writer, whose text these rules restate.
A first-form receipt verifies only as its signer wrote it: reordered members
break it, and so can a number written another way. No host writes the first
form now. A canon other than rfc8785 (null among them) names no form.
The signature, strictly. RFC 8032 leaves some choices open, and two verifiers that chose differently would disagree on the same receipt. A verifier here:
- decodes
AandRas RFC 8032 §5.1.3 does, and refuses an encoding whose y (its low 255 bits, little-endian) is p = 2^255 − 19 or more, one that is not on the curve, and one with x = 0 and the sign bit set; - refuses
Aof small order (a point P with [8]P the identity) andAnot in the prime-order subgroup ([ℓ]A not the identity, ℓ = 2^252 + 27742317777372353535851937790883648493); - refuses
Rof small order, andS(little-endian) of ℓ or more; - accepts exactly when [S]B = R + [k]A, with k = SHA-512(R ‖ A ‖ the 32 digest bytes) read little-endian, mod ℓ, compared as points with no cofactor (and so as their canonical encodings).
Without the rules on A, the identity as A, any S and R = [S]B hold
for every digest: a receipt for any content at all. A library’s default check
also takes the identity as R with S = 0.
8.3 Where the counts come from, and how the device was asked to run them
counts_source is one of:
| value | the counts are |
|---|---|
shots | tallied from the device’s records, one a shot |
vendor_counts | the provider’s own counts, as it returned them |
probabilities | rebuilt from the probabilities the device gave: each p·shots rounded down, then the shots left given one each to the largest remainders, a tie to the lower outcome. They are derived, and not shot records |
Who did so is read from provider.kind. On a built-in kind the host’s client
did, and it is the host’s word. On http and process the host received counts
and did neither, and it is the provider’s word for how it made them (§5.5): the
rule for rebuilding them is the one the provider says it kept, which the host
cannot vouch for.
execution says what the host knows of the five steps a provider may
take between receiving a program and running it: mitigation (correcting
counts after the run, readout correction or debiasing), twirling (of gates
or of measurements), decoupling (dynamical decoupling of idle qubits),
remapping (moving the program to other qubits) and optimisation
(rewriting its gates). It is an object of:
| member | |
|---|---|
stated_by | client, the host’s client from the request it sends; provider, the provider in its result answer (§5.5); or nobody |
| the five steps | each off or on (by a setting the client sent, or as the provider states), default (said by a client: its request sets nothing for it, so the provider’s default stands, whatever it is), none (said by a provider: its device has no such step), or unknown (when nobody states it, or a provider cannot vouch for the step) |
verbatim | true when the request asks for the gates to run as written, on the qubits named (a verbatim box); false when it does not; null when nobody states it |
sent | with client only: fields, the members of the request’s body, each once, in increasing order of their UTF-8 bytes; and settings, an object of what the request sets: JSON Pointers (RFC 6901) into the body, each with the value it holds there, and names for settings the body holds otherwise (verbatim_box); and nothing else |
note | optional: a sentence |
and nothing else. Its rules: with stated_by client, each step is off,
on or default, verbatim is true or false, and sent is given; with
provider, each step is off, on, none or unknown, verbatim is
true or false, and there is no sent; with nobody, each step is
unknown, verbatim is null and there is no sent. Each key of
sent.settings either begins with /, and its first reference token (up to
the next /, its ~1 and ~0 read as / and ~) is one of fields, the
rest of it not read; or is a name, not empty, that does not begin with /. A program run as written is
neither remapped nor optimised, so with verbatim true, remapping and
optimisation are each off or none. A client that says a step is off or
on says by which setting: settings is then not empty.
What each built-in kind records. A client builds its execution from the
very body it sends, so that what is stated is what was sent, and its counts’
source from what the service answered. These are their rows. A later client
that sends something else adds a row; a row is never removed, so every
receipt keeps verifying. A kind of the open protocol (http, process)
records the provider’s word (provider), or nobody when it says nothing. On
any kind, a job recorded before its client stated what it sent, and collected
since, is stated by nobody: nothing else is known of what was sent.
| kind | counts_source | sent.fields | sent.settings | steps | verbatim |
|---|---|---|---|---|---|
sampler-v2 | shots | backend, params, program_id | /params/options/dynamical_decoupling/enable: false, /params/options/twirling/enable_gates: false, /params/options/twirling/enable_measure: false | decoupling and twirling off; the other three default | false |
prx-circuits | vendor_counts | circuits, shots | none: the client sends nothing else | all five default | false |
native-turns | probabilities: the service answers probabilities, so its counts are always rebuilt | backend, input, settings, shots, type; and noise, on its simulator under a device’s noise model | /input/gateset: native, /settings/error_mitigation/debiasing: false; and /noise/model: the model’s name, with noise | mitigation off; the other four default | false |
cloud-tasks, on a processor (silicon) | shots, from the task’s rows; probabilities, when its result holds none | action, clientToken, deviceArn, outputS3Bucket, outputS3KeyPrefix, shots | verbatim_box: true, the program’s gates in the service’s verbatim box on physical qubits | remapping and optimisation off; the other three default | true |
cloud-tasks, on a simulator | as on a processor | as on a processor | none | all five default | false |
A row is the execution its kind states, less its note: stated_by is
client, and every member is as the row gives it. A row holds for its
provenance: cloud-tasks’ first row for silicon, its second for any other
(a simulator or an emulator); native-turns’ row with noise for any
provenance but silicon, its model’s name a string, not empty, and its row
without noise for any. prx-circuits sends only
circuits and shots, so it turns nothing off, and says so. No row says a
provider runs a program as sent where its client sets nothing that asks it to.
8.4 What a verifier checks
A verifier reads the job (§8.2) and reports every reason below that applies, as a set: none for a valid receipt. It is valid exactly when there is none.
| reason | when |
|---|---|
unreadable | the text breaks a rule of reading (§8.2). Reported alone |
not_a_job | the value is not an object whose request and result are both objects. Reported alone |
no_receipt | the result has no receipt. Reported alone |
receipt_form | the receipt is not an object; its alg is not ed25519; it has a canon other than rfc8785; or its key, digest or signature is not lower-case hex of its length. When it is reported, the next three are not checked |
digest_mismatch | the digest the receipt states is not the digest (§8.2) of this request and result |
bad_key | key is refused by the strict rules (§8.2). When it is reported, the signature is not checked |
bad_signature | the signature does not hold, strictly (§8.2), over the digest the receipt states (so a body changed after it was sealed gives digest_mismatch alone) |
inexact_number | a whole number written as one, in what the receipt seals (the request, and the result less its receipt; for a set, the request and its head, §8.6) and sealed as a double (§8.2), is not a double, and is not the canonical text of the double it reads as. A receipt is of the first form when receipt_form is not reported and it has no canon; one that receipt_form refuses names no form, and its numbers are read as a canonical receipt’s |
provider_class | a provider’s result whose accuracy.class is not the one its form gives: measured in the earlier form; in the stated form, measured for silicon and simulated for a provenance whose part before any : is simulator or emulator (none for any other); or whose accuracy.approximate is present and not true, or true in the earlier form or on a class other than simulated. Or a result of another method whose class is measured or simulated |
request_provider | a provider’s result whose provider.name is not a string equal to the request’s provider |
request_shots | a provider’s result whose shots is not the request’s (1024 when the request gives none, or null) |
counts | a provider’s result whose counts is not an object of outcomes, all of one length and each of 0 and 1 only, to whole numbers from 1 to 2^53 − 1; or whose shots (a whole number of at least 1), omitted_shots and distinct_outcomes (whole numbers of at least 0) are missing; or whose counts and omitted_shots do not sum to shots; or whose distinct_outcomes is not the number of outcomes listed when omitted_shots is 0; or, when it is not 0, lists other than 65,536 outcomes, or no more distinct outcomes than it lists |
provider_members | a stated provider’s result (below) without, or with a wrong type of: provider’s name, kind, protocol and device (each a string not empty), provenance (as provider_class reads it), describe (64 lower-case hex digits), qubits (a whole number of at least 1) and basis (equal to compiled.basis); calibration (a string or null); compiled’s basis (rzsxxcz, prxcz or gpizz), program (64 lower-case hex digits), qasm3 (a string), native_gates, two_qubit_gates and swaps (whole numbers of at least 0), layout and final_layout (arrays of whole numbers of at least 0) and checked (a string); compiled.qubit_names, where it is given or required, is below; job’s id (a string not empty) and polls (a whole number of at least 0); ideal.computed (true or false); cost’s free and confirmed (each true or false), estimate (an object) and charged (null, or an object whose amount and currency are strings); energy.unmetered (a string), and energy.service_kwh_is (a string) when there is energy.service_kwh; schema (wai.quantum.job-result/1); engine.name (wai-quantum) and engine.version (a string) |
counts_source | a stated provider’s result whose counts_source is not one of §8.3’s values, or, for a kind with rows, not one the rows that hold for its provenance give |
execution | a stated provider’s result whose execution breaks a rule of §8.3; or, for a kind with rows, is neither one of the rows that hold for its provenance nor stated by nobody; or, for http and process, is not stated by provider or nobody |
compiled_program | a stated provider’s result whose compiled.program is not the BLAKE3 of compiled.qasm3’s UTF-8 bytes, when qasm3 is a string and program is 64 lower-case hex digits |
cost_confirmation | a stated provider’s result whose cost.confirmed is true on a free run or false on one that is not free (§7), when both are true or false |
A provider’s result is one whose method is provider. It is stated when it
carries a member counts_source or a member execution (whatever their
values), and of the earlier form when it carries neither; the earlier form is
held to provider_class, request_provider, request_shots and counts
only. A whole number is any number whose value is whole, however it is
written (1, 1.0, 1e0), however large; only a count’s value has a ceiling,
and a sum is exact, not a double’s. A number’s value, in every rule here and on
every form of receipt, is its nearest double (§8.2): the digits of a first-form
receipt are for what it sealed, and for nothing checked here. -0 is 0.
“The request’s” is by value: two equal strings are the same, and that a string
is no number is another rule’s to say.
A class a verifier does not know is not a reason: a later version may add one
(VERSIONING.md).
compiled.qubit_names is required of a stated result of cloud-tasks or
prx-circuits whose execution is stated by client, and optional otherwise.
When given (null is given, and is no object) it is an object: each key an index
in decimal, with no leading zero; each value a string, not empty; the value of
every entry of layout and final_layout, written in decimal in full, is among
its keys; and so is every index qasm3 acts on, when qasm3 is a string: each
$ anywhere in it followed by one or more of the ASCII digits 0 to 9 names the
index those digits read as, written in decimal with no leading zero (a $
followed by no such digit names none). Otherwise it is a provider_members reason.
What a host seals, its own verifier accepts. A host refuses a request holding
a whole number past ±(2^53 − 1), or any number that it or RFC 8785 would write in
a spelling this section refuses, where it reads it, on every path to a seal (a
set’s own request, its sweep’s values among them, included); and it writes a
whole number no double holds in a result as the double it seals. A receipt an
earlier release sealed with such a number is refused with inexact_number,
though its signature holds: what it sealed is the double, and its text says
more. Written as that double’s exact digits, or as its canonical text, it
verifies. The local hosts’ qec took a seed of any size; their compile, pulse,
estimate and @Seal took any number in a request; and a set took any number in
its own request and its sweep’s values. The job service refused such a number
in every request but a set’s own.
A verifier also states, for a provider’s result (one with no receipt
included), its class as its provenance reads it, and, in the stated form, its
counts’ source and its execution, each as the result gives it.
These are all the rules: what §8.1 names and this table does not check
(compiled.hash and placement, the notes, the ideal figures, the
estimate’s contents) is the host’s word, unchecked. The schemas
(wai.quantum.job-result/1, wai.quantum.receipt/2) describe what a host
writes; a verifier judges by this section.
8.5 What the receipt proves, and what it does not
A valid receipt proves that the holder of the key it names signed this
request and this result, nothing else and nothing less: every member of
both, the counts, counts_source and execution among them. It does not
say whose key it is. The job service publishes its keys and their windows
(GET /.well-known/wai-quantum-receipt-keys); a host’s key is the person’s
own, and anyone relying on a receipt decides whether they trust it. Nothing
sealed carries a time: a receipt can be shown again, and no window can be
applied to it from its bytes. For the same reason a host can still seal a
result of the earlier form, stating neither its counts’ source nor its
execution; a verifier then holds it to the earlier form’s rules, reads its
class by its provenance, and says that it states neither. A host can likewise
seal a stated result with execution stated by nobody on any kind, meant for
a job recorded before its client said what it sent: that says nothing of what
was sent, and a verifier says that nobody stated it.
Everything the result says the host received, sent and checked is the host’s
own word, signed: that this was the provider’s answer, under the calibration
snapshot named, and that the host compiled the program whose text and hash it
carries exactly and checked it against the circuit before sending it. The
compiled program’s text is in the receipt, with the device’s names for its
qubits where its client uses them, so anyone can read which gates were sent to
which qubits and check its hash. It is not the bytes sent: a client writes the
same gates in what its service takes. execution says what the request
asked of the provider. What it does not prove: that the device ran that
program, or ran anything. The counts are the provider’s word, and they are
not reproducible by recomputation: the same job run again gives other counts.
A provider’s simulator’s counts say what its software gives, which is why
they are simulated, not measured. The ideal distance says how far the
counts are from what the circuit gives; a distance well above the sampling
distance is the device’s or the simulator’s error, or a program not run as
sent.
8.6 A set’s receipt
A set is one request that runs many jobs (programs, a list of programs or
jobs, or sweep, values for the inputs of the request’s own qasm); its result
has "format": "wai.quantum.set/1". Its head is its result less items and
receipt. The set’s receipt seals the set’s request and its head, by §8.2, as a
job’s seals its request and result; each item that ran carries a result with a
receipt of its own, over its own request. A verifier reads a set as it reads a
job (§8.2), and reports, beside §8.4’s reasons for the set’s request and head
(unreadable, not_a_job, no_receipt, receipt_form, digest_mismatch,
bad_key, bad_signature, inexact_number; the provider rules do not apply to
a head), each of these that applies:
| reason | when |
|---|---|
set_items | items is not a non-empty array whose i-th entry is an object with index equal to i (a whole number), request an object, exactly one of the members result (an object) and error (a string) present (a member that is present is so whatever its value, null included), and path an array of strings each of 64 lower-case hex digits. When it is reported so, the next two are not checked. Or: count is not the number of items; or the items are not the ones the request gives. With programs (an array, and no sweep), item i’s request is the request less programs, sweep and dry_run, with program i over it: a string as qasm, or an object’s members, each replacing one of the same name. With sweep (an object, and no programs), each of its values is a non-empty array, the items are as many as the product of their lengths, and each item’s request less qasm is the request less programs, sweep, dry_run and qasm, its qasm a string (the request’s program written out at its point, which §8 does not check: below). A request with neither, or with both, gives no items; a member that is null is none. Requests are compared as JSON values: members in any order, numbers by value |
set_item | an item with a result is not a valid job: {"request": its request, "result": its result}, read as §8.2 reads a job, gives any reason of §8.4; or its result is a set’s (format wai.quantum.set/1: an item is one job); or its receipt is valid and names a key other than the set’s receipt’s key member as it stands (one the set’s receipt does not have included). Or an item with an error has a request that writes a whole number its leaf could not seal: as inexact_number reads one in a canonical receipt, whatever the set’s form, since a leaf is the canonical form’s digest |
set_root | root is not 64 lower-case hex digits, or not the Merkle root of the items’ leaves; or an item’s path does not lead from its leaf to root |
An item’s leaf is the digest (§8.2, the canonical form’s) of its request and its
result less its receipt, or, for an item with an error, of its request and
{"error": the error}. The Merkle tree: a leaf’s hash is BLAKE3 of
moq-jwp:merkle-leaf and the byte 0x01, then the leaf; a node’s is BLAKE3 of
moq-jwp:merkle-node and the byte 0x01, then the left hash and the right; a
level of odd length pairs its last hash with itself; the root of one leaf is its
hash. A path lists the sibling at each level from the leaf up: with n items it
has as many entries as halvings (rounding up) take n to 1, and a path of any
other length does not lead to the root; at each level the item’s position,
halved at each step, says whether the sibling is on the right (an even position)
or the left. This is the tree the WAI worlds’ and provenance receipts use, its
domains theirs, kept so that every set already sealed verifies. Two things
follow from it. The root alone does not fix the number of items (a last leaf
paired with itself gives the root a level of one more would), and count,
sealed in the head, does; one item shown with its path and the head alone is
placed by count. And the root does not say whether an item failed: an item
with an error and one whose result is {"error": …} have one leaf, and only
the item’s own receipt tells them apart.
An item is read for its index, request, result, error and path; what
else it holds (its status, method, why, a sweep’s point) is the host’s,
unsealed, as a job body’s members beyond its request and result are, and a
member its own receipt holds beyond its five is ignored, as a job’s is.
The engine also writes each item of a sweep out again at its point, and says
whether each qasm is that program (derived, beside the reasons); that is the
engine’s check of the set, not a rule of its receipt, and a verifier that does
not write programs out does not make it.
9. Conformance
wai-quantum-qpu/conformance/provider-v1.json holds the vectors:
- messages, sent as they are: an unknown operation (
unsupported), another version (unsupported_version), a message without its protocol (invalid), a job that does not exist (not_found), malformed programs (invalid); - programs, each compiled to the device, estimated, run with seed 1, and checked by its counts: the probe, bits named out of order, a phase only interference shows, a phase kicked back by two quarter-turn controlled phases, a Bell pair, and a triangle of two-qubit gates that needs routing on a line. The thresholds leave room for a device’s noise;
- reference: the counts the reference provider gives for each program at seed 1, so its determinism is held on every platform.
The checker (wai-quantum providers test NAME) also checks the describe
answer in full, that zero shots are invalid, and that a program in another
basis is refused. It runs the programs only when every estimate says free,
or the person allows the spend (--yes).
The reference provider (wai-quantum providers reference --stdio, or
--http ADDR) is this protocol around WAI’s exact simulator. It is the
provider the tests run against over both transports, and the template to
copy: a provider for any device is the reference with its simulation replaced
by the device. It states its counts’ source and its execution (§5.5).
Receipts. quantum-conformance/receipts/ holds job receipts and what a
verifier must say of each (§8.4): receipts made by the command line on this
machine and by the job service, provider jobs of every built-in kind through
services that follow their contracts, the earlier form, the first (receipts the
first form’s own writer sealed, and others generate.py made from §8.2’s text),
and receipts sealed to break one rule each. A
receipt from a person’s account names that account, and is not published: it is
checked with the differential’s --extra, outside CI. A receipt once made is
kept, pinned by its digest. Two verifiers judge every one of them, and every
receipt made from a valid one by changing one member, numbers among them to
values past a double’s range of whole numbers: wai-quantum verify, and
quantum-conformance/tools/receipt_verify.py, written from §8 alone in another
language and sharing no code with the engine. They must report the same reasons
for every receipt, and the manifest’s for each in the corpus. Every other
surface that verifies is held to the manifest by its own tests, given each
receipt’s text: the job service over HTTP and as a tool, the command line’s tool
(each takes the job’s text), the Python package (text, bytes or a path), the
desktop app and a browser tab. A surface given a job already parsed into objects
(a tool’s request and result, a Python dict) cannot see what parsing loses:
a member named twice (unreadable), and a whole number past what its reader
keeps (inexact_number). It reports every other reason, and is held to the
manifest on every receipt but those. A tool’s text is a JSON string, which holds
UTF-8 only, so text that is not UTF-8 reaches a verifier only as a file’s bytes.
10. Versioning
wai.quantum.provider/1 is this text. A later version is a new protocol
string; a provider that speaks only this one answers unsupported_version to
another. Fields marked optional may be added to answers without a new
version, and a client MUST ignore fields it does not know.
A result’s receipt seals every member it holds, so a member a later text adds
to the result is in no receipt sealed before it, and every such receipt
verifies as it did (§8.4’s earlier form). counts_source, execution,
compiled.qasm3, the class simulated and accuracy.approximate were added
so, as were the answers’ counts_source and execution and describe’s
approximate.