Skip to main content

WAI Extension: Price Document

Mirrored from the canonical text at commit 0d3f0d7c ().

Status: Draft. Registers the record wai.price.document: a publisher’s signed statement of the prices of the endpoints it routes to, part of which it may withhold. A price document (wai-price-document, §5) states, for each operation of each endpoint it prices, the terms a router’s hard limits read and the figures its ranking reads, the model weights the endpoint runs by their digests with the licence each is under, and its prices, each a numerator per divisor of the entry’s named unit with the window [from, until) it holds for (§3). A split rule the document states, in a closed vocabulary the verifier applies, divides the entries into a public part and the rest (§4); the signed document holds the public part and, for the rest, only a salted and domain-separated commitment, withheld (§5.2). A public view is the document alone; a full view is the document and its opening, the rest and the salt (§6); both verify to the document’s one digest. A verifier refuses a public part that holds an entry its own rule withholds, an opening that does not open withheld or that withholds an entry the rule publishes, and holds a route record’s price book, or any price stated as the publisher’s, to a view at an instant: each entry an entry of the view, its price the one in force then (§7). The reference implementation is the price_document module of wai-rs (feature price_document); the corpus is live-conformance/price-documents/, and its second implementation is live-conformance/price_document_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

Texts read for this revision: the WAI texts this one cites. RFC 8259, RFC 8785, RFC 8032, FIPS 180-4 and ISO 4217 are read as route-record §2 reads them, and an SPDX licence expression as prior-carriage §18.1 reads one.

1. Scope

In scope: an entry, its prices and its weights (§3); the split rule (§4); the document, its commitment, its digest and its signature (§5); its two views and what each proves (§6); how a route record’s price book and a stated price are checked against a view (§7); verification (§8); and the reference implementation and corpus (§10).

Out of scope: how a publisher sets its prices, and which entries it withholds, which its rule states and this extension applies as stated; whether a price is what a provider charges; which endpoints an operator offers a job, and why (decision D59); accounts and payment. A price document is a quote and never a charge: a line bills only at a tariff its buyer signed (receipt-ledger §4.2).

Relationship to the other texts that state prices:

Relationship to the core spec: wai.price.document is a record (SPEC §5): a signed record a WAI tool emits and verifies. It names no payload format and is not dispatchable.

2. Terms and encodings

3. Entries

3.1 An entry

An entry states one operation of one endpoint. It is an object of exactly:

memberform
endpointa token: the endpoint’s id
operationan operation: the one this entry prices
providera token: who runs it
capabilitya capability: what it produces
region, residencya token, or null when not known
trains_on_inputsa boolean, or null when not known
retention_daysan integer, or null when not known
gaa boolean, or null when not known
needs_consenta boolean, or null when not known
unita unit: what it is priced by
divisoran integer at least 1: every price of the entry is per divisor units of unit
ttfb_msan integer, or null when not known
energy_ujan integer, or null when not stated: the microjoules it states per divisor units
energy_classa class, or null exactly when energy_uj is null
energy_ppman integer from 1 to 2³² − 1 when energy_class is CalibratedInstrument or OnChipCounter, and null otherwise
qualityan integer 0 to 1000, or null when not known
weightsits weights (§3.3)
pricesits prices (§3.2)
withholda boolean: the entry’s own flag, which a rule that reads flags reads (§4)

Every member but the last three means what route-record §3.2 says of the member of the same name in an entry of a price book of version 3, in the same form. So an entry of a price book is an entry of a document with its price chosen at an instant and the router’s measured failures added (§7.2), and a price book entry has nothing a document entry cannot state but failures, which a router measures and no publisher signs.

One unit and one divisor serve every price of an entry, so that its energy_uj and its prices are stated per the same quantity, and a price has one encoding: a price of the same ratio stated under another divisor is another price (§7.3).

3.2 Prices and their windows

prices is 1 to 64 objects of exactly:

memberform
pricean integer: an amount per divisor units of the entry’s unit
froma time: the first instant of its window
untila time greater than from: the first instant after its window; or null, for a window with no end

They are in ascending order of from, and each window begins at or after the end of the one before it: its from is at least the previous until. So only the last may have until null, no two windows share an instant, and at any instant an entry has at most one price, its price in force then (§7.1). Between two windows, where one until is below the next from, it has none: the endpoint’s operation is not offered at a price then.

A document states the windows it states; nothing here revokes a price. A publisher that changes a price issues a document that states the change.

3.3 Weights and their licences

weights is 0 to 16 objects of exactly:

memberform
sha256a set digest
licencea licence expression, or null when the publisher states none

They are in strictly ascending order of sha256. Each names a parameter set the endpoint’s operation runs, by the digest its pin names it by, and the licence the set’s rights holder states its bytes are under: where the set carries a usage statement, that statement’s licence (prior-carriage §18.1). An entry with no weights states none: an endpoint that runs no parameter set, or one whose sets the publisher does not state.

A licence here is what the rights holder permits, as the publisher read it. It is not the publisher’s clearance of the licence for its own use: whether an operator has cleared a set’s licence is the operator’s rule, and no rule of this text or of route-record reads it (D59). A reader that holds the set checks its digest, and its bound usage statement, itself.

4. The split rule

A document’s rule is an object of exactly:

memberform
providersnull, or 1 to 64 tokens in strictly ascending order
flaggeda boolean

An entry is public under the rule when both hold: providers is null or names the entry’s provider; and flagged is false or the entry’s withhold is false. Every other entry is withheld. The rule withholds nothing when providers is null and flagged is false.

So a publisher publishes its own endpoints and withholds every other provider’s by naming its own provider in providers; withholds any entry it flags by setting flagged; or does both, and an entry is then public only when its provider is named and it is not flagged. When flagged is false, no entry’s withhold is true (§5.4 rule 6, §6.2): a flag no rule reads would be a second encoding of one document.

The rule names what it publishes, never what it withholds. A rule that listed the providers it withholds would disclose them. providers names only providers whose entries are public, and an entry’s flag is inside the entry, so a withheld entry’s flag is inside the commitment. Nothing the rule states depends on a withheld entry.

A closed vocabulary. Which entries a publisher withholds is its own business; this text gives it two ways to state that, by provider and by flag, so that a verifier can apply the statement. A public part that holds an entry the rule withholds is refused (§5.4 rule 8), and so is an opening that holds an entry the rule publishes (§6.2).

5. The document

5.1 Members

A price document is a document of exactly these members:

memberform
kind"wai-price-document"
version1
publishera key
currencythree capitals A–Z (ISO 4217): the currency of every price
issueda time: when the document was signed; it states no price for an instant before it (§7.1)
rulethe split rule (§4)
entries0 to 1024 entries (§3.1): the public part, in strictly ascending order of pair
withheldnull when the rule withholds nothing; otherwise the commitment to the withheld part (§5.2), 64 lowercase hexadecimal digits
sig128 lowercase hexadecimal digits (§5.3)

The document’s entries are its public part and its withheld part together: at most 1024, no two of one pair.

5.2 The commitment

The withheld part is the entries the rule withholds, in strictly ascending order of pair; it may be empty. Its commitment is SHA-256 over the 19 bytes wai:price-withheld (ASCII) and 0x01, then the salt’s 32 bytes, then the canonical form of the withheld part as one JSON array. Every part of that input but the last has a fixed length, so the input is read one way.

withheld is the commitment whenever the rule withholds anything, even when the withheld part is empty, so that a public view tells neither whether anything is withheld nor how many entries are.

The salt MUST be 32 bytes from a cryptographically secure random source, drawn anew for each document. A withheld entry is drawn from a small space, an endpoint, a provider and a price a competitor can guess, and with no salt anyone could test a guess against withheld. Under a salt of 256 random bits no guess can be tested, and two documents that withhold the same entries under different salts cannot be told to be the same; a salt used twice gives that away. A verifier cannot check a salt’s randomness; the reference draws a fresh one from the operating system (§10).

The commitment’s domain is not the signature’s (§5.3), so no input of one is an input of the other.

5.3 Digest and signature

The digest of a document is SHA-256 over the canonical form of the document without sig, written as 64 lowercase hexadecimal digits. The signature is Ed25519 by publisher over the 19 bytes wai:price-document (ASCII) and 0x01, followed by that same canonical form: route-record §5.3’s construction under its own domain. It is verified strictly, as SPEC §7 Signatures requires: a key that fails its step 1 or 2 is bad_key, and a signature that fails a later step is bad_signature. The digest leaves sig out for rung-price §4.4’s reason: it names what the publisher said, and Ed25519 admits many signatures of one body.

So a public view and a full view verify to one digest: a public view is the document, a full view is the document and its opening (§6), and the opening changes no byte the digest covers. A route record, a line or any other record that names a price document names it by this digest; a record that names it by a kind takes kind 38 (receipt-ledger §5.2).

5.4 Reading and rules

A reader is given a document’s text. It refuses it malformed when the text holds more than 1,048,576 bytes, is not a document (route-record §2), or its kind is not wai-price-document, and unknown_version, at version, when its version is an integer other than 1. It then holds it to these rules, in order:

  1. Its members are exactly §5.1’s, each of its form; each entry’s are exactly §3.1’s, each of its form, each price’s §3.2’s and each weight’s §3.3’s.
  2. Its entries are in strictly ascending order of pair.
  3. Each entry’s prices are in ascending order of from, each from at least the previous until, and only the last until is null.
  4. Each entry’s weights are in strictly ascending order of sha256.
  5. Each entry’s energy_class is null exactly when its energy_uj is, and its energy_ppm is stated exactly when its class is CalibratedInstrument or OnChipCounter.
  6. Its rule’s providers is in strictly ascending order; its withheld is null exactly when its rule withholds nothing; and when its rule’s flagged is false, no entry’s withhold is true.
  7. Its signature (§5.3): bad_key, bad_signature, at sig.
  8. Each entry is public under its rule. An entry the rule withholds is refused withheld_entry_revealed, at entries and its index: a public part that holds it has revealed what its own rule withholds.

A document that breaks a rule of 1 to 6 is price_document_invalid. So a document reads the same whatever the order of its members, since its canonical form is what is signed, and one set of entries under one rule and one salt has one document.

6. Views

6.1 The public view

The public view is a document that reads (§5.4), alone. Its entries are the public part.

6.2 The opening and the full view

An opening is a document of exactly:

memberform
kind"wai-price-opening"
salta salt
entries0 to 1024 entries (§3.1): the withheld part (§5.2)

A reader given a document that reads and an opening’s text refuses the opening, in this order:

  1. malformed, when the text holds more than 1,048,576 bytes, is not a document, or its kind is not wai-price-opening;
  2. opening_invalid, when it is not of these members, each of its form, with its entries held to §5.4 rules 1 to 5 and, when the rule’s flagged is false, no entry’s withhold true; when the document’s withheld is null, since there is then nothing to open; when an entry of it has a public entry’s pair; or when the public part and its entries number more than 1024;
  3. withheld_mismatch, when the commitment of its entries under its salt (§5.2) is not the document’s withheld;
  4. public_entry_withheld, at entries and its index, for an entry of it the rule publishes: a withheld part that holds it has hidden what the rule publishes.

A document that reads and an opening that opens it are the full view. Its entries are the public part and the opened entries.

6.3 What each view proves

A public view proves:

A public view does not prove:

A full view proves, besides:

Neither view proves:

7. Prices checked against a view

7.1 A price in force, and finding an entry

An entry’s price in force at an instant t is the price whose window holds t (§3.2), when t is at least the document’s issued. At an earlier instant, and at one no window holds, it has none. A document states nothing for an instant before it was signed: a router that read a price at t read a document issued by t.

A view’s entry of a pair is the public entry of that pair, or in a full view the opened one. Its answer is public or withheld, by the part that holds it. When the view holds no entry of the pair:

7.2 A route record’s price book

A route record names its price book by digest, and route-record §7 recomputes the record from it. A reader that holds a view checks that price book against the view at the price book’s issued, the instant its router stated its prices (route-record §3.2):

  1. The price book’s text is read as route-record §3.2 reads one: malformed, pricebook_invalid. A price book of version 1 names no operation, so its entries name no entry of a document: check_invalid.
  2. Its currency is the document’s: otherwise currency_mismatch, at currency.
  3. Each entry in turn, at entries and its index: the view’s entry of its pair (§7.1: price_not_in_view, or the answer unproved); then each of its members provider, capability, region, residency, trains_on_inputs, retention_days, ga, needs_consent, unit, divisor, ttfb_ms, energy_uj, energy_class, in a price book of version 3 energy_ppm, and quality, in that order, is the view entry’s: otherwise entry_mismatch, at the first member that differs; then the view entry has a price in force at the price book’s issued: otherwise price_not_in_force; and its price is that price: otherwise price_mismatch, at price.

An entry’s failures are its router’s measurement (route-record §3.2.1) and are held to nothing here. So a route record’s candidates are held, by two verifiers, to the entries a publisher signed: route-record §7.2 step 6 holds every candidate to its price book, and this section holds every entry of the price book to the view, but for its failures, and but for an entry a public view may withhold, which a full view answers.

7.3 A stated price

A price claim is an object of exactly endpoint (a token), operation (an operation), provider (a token), unit (a unit), divisor (an integer at least 1), price (an integer) and at (a time): a statement that the publisher’s price of that operation of that endpoint, at the instant at, was price per divisor units of unit. A claim not of these members and forms is check_invalid. It is answered as a price book’s entry is (§7.2 step 3), at the claim: the view’s entry of its pair; its provider, unit and divisor, in that order, the entry’s (entry_mismatch, at the member); a price in force at at (price_not_in_force); and its price that price (price_mismatch, at price). A claim in another divisor is another claim, whatever its ratio.

A line of the receipt ledger bills at the tariff its buyer signed (receipt-ledger §4.2), never at a document. A reader who asks whether a job’s line was billed at the publisher’s price states a claim: the endpoint of the route record’s billed attempt and its provider, the record’s operation, the tariff’s job price for them (receipt-ledger §4.6, §4.9) in its unit, divisor and price, and at the record’s pricebook.issued. A claim refused price_mismatch says the line was not billed at the publisher’s price, as a tariff that discounts it is not: the claim is about the document, and never about what a buyer agreed to.

8. Verification

8.1 Inputs

A verifier is given a verification document: a document (route-record §2) of exactly:

memberform
documenta string holding the price document’s text
openingnull, for a public view; or a string holding the opening’s text, for a full view
pricebooks0 to 64 strings, each holding a route record’s price book’s text (§7.2)
prices0 to 1024 values, each a price claim (§7.3)

The texts are carried as strings so that each is read as written. A verification document that is not of these members and forms is malformed, at the verification document itself (an empty at).

8.2 Steps

A verifier runs these steps, stops at the first refusal, and reports its reason and where (at, a list: the member, then indexes or members within it):

  1. The verification document (§8.1): malformed.
  2. The document (§5.4), at document: malformed, unknown_version, price_document_invalid, bad_key, bad_signature, withheld_entry_revealed.
  3. The opening (§6.2), when it is given, at opening: malformed, opening_invalid, withheld_mismatch, public_entry_withheld.
  4. The price books (§7.2), each in turn, at pricebooks and its index: malformed, pricebook_invalid, check_invalid, currency_mismatch, price_not_in_view, entry_mismatch, price_not_in_force, price_mismatch.
  5. The price claims (§7.3), each in turn, at prices and its index: check_invalid, price_not_in_view, entry_mismatch, price_not_in_force, price_mismatch.

8.3 Outcome

9. Reasons

The codes this extension adds, each with where it is raised:

reasonraised at
price_document_invalid§5.4, §8.2 step 2
withheld_entry_revealed§4, §5.4, §8.2 step 2
opening_invalid§6.2, §8.2 step 3
withheld_mismatch§5.2, §6.2, §8.2 step 3
public_entry_withheld§4, §6.2, §8.2 step 3
check_invalid§7.2, §7.3, §8.2 steps 4 and 5
currency_mismatch§7.2, §8.2 step 4
price_not_in_view§7.1, §8.2 steps 4 and 5
entry_mismatch§7.2, §7.3, §8.2 steps 4 and 5
price_not_in_force§7.1, §8.2 steps 4 and 5
price_mismatch§7.2, §7.3, §8.2 steps 4 and 5

A verifier of this extension also raises malformed for a text that is not a document or a verification document that is not §8.1’s, unknown_version for a document of another version, bad_key and bad_signature for its signature (SPEC §7), and route-record’s pricebook_invalid for a price book that route-record §3.2 refuses.

10. Conformance

live-conformance/price-documents/ holds the corpus, in one file, vectors.json, written by wai_price_document_vectors gen from fixed seeds: a publisher’s key and a second publisher’s, every salt a constant, every instant a constant. Each vector is a verification document with the outcome §8.3 gives it, declared in the generator:

wai_price_document_vectors verify and the test tests/price_document.rs check that the file regenerates byte for byte, that wai-rs gives every vector its outcome, that every reason of §9 and every code §9 reuses refuses a vector, and that §9 lists exactly the reference’s codes. live-conformance/price_document_verify.py is the second implementation, written from this text: it shares no code with wai-rs, reads documents and price books with route-record’s second implementation (route_record_verify.py), verifies the publisher’s signature with WAI’s strict rule written from RFC 8032 (claim-conformance/_ed25519.py), reads licence expressions, applies the rule, computes the commitment and finds prices in force itself, gives every vector the same outcome, requires every mutated copy of the corpus to fail, and requires the corpus to catch the verifier with each of its checks dropped.

The reference seals a document with price_document::seal, which takes the salt, and price_document::seal_fresh, which draws it from the operating system; reads one with price_document::Document::read; opens it with price_document::View::open; and checks a price book and a claim with View::check_pricebook and View::check_price. price_document::verify runs §8.2 over a verification document.

11. Security and privacy considerations