metered-protocol 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +103 -0
- package/contracts/metered.ag +135 -0
- package/contracts/metered_ag.tests.json +1226 -0
- package/contracts/metered_session.sil +237 -0
- package/contracts/metered_session.tests.json +1113 -0
- package/dist/contracts/metered.ag +135 -0
- package/dist/contracts/metered_ag.tests.json +1226 -0
- package/dist/contracts/metered_session.sil +237 -0
- package/dist/contracts/metered_session.tests.json +1113 -0
- package/dist/src/bias.d.ts +48 -0
- package/dist/src/bias.d.ts.map +1 -0
- package/dist/src/bias.js +43 -0
- package/dist/src/bias.js.map +1 -0
- package/dist/src/checkpoint.d.ts +57 -0
- package/dist/src/checkpoint.d.ts.map +1 -0
- package/dist/src/checkpoint.js +84 -0
- package/dist/src/checkpoint.js.map +1 -0
- package/dist/src/deadline.d.ts +71 -0
- package/dist/src/deadline.d.ts.map +1 -0
- package/dist/src/deadline.js +77 -0
- package/dist/src/deadline.js.map +1 -0
- package/dist/src/encoding.d.ts +72 -0
- package/dist/src/encoding.d.ts.map +1 -0
- package/dist/src/encoding.js +187 -0
- package/dist/src/encoding.js.map +1 -0
- package/dist/src/history.d.ts +33 -0
- package/dist/src/history.d.ts.map +1 -0
- package/dist/src/history.js +38 -0
- package/dist/src/history.js.map +1 -0
- package/dist/src/http/buyer.d.ts +57 -0
- package/dist/src/http/buyer.d.ts.map +1 -0
- package/dist/src/http/buyer.js +151 -0
- package/dist/src/http/buyer.js.map +1 -0
- package/dist/src/http/client.d.ts +55 -0
- package/dist/src/http/client.d.ts.map +1 -0
- package/dist/src/http/client.js +83 -0
- package/dist/src/http/client.js.map +1 -0
- package/dist/src/http/harness.d.ts +18 -0
- package/dist/src/http/harness.d.ts.map +1 -0
- package/dist/src/http/harness.js +16 -0
- package/dist/src/http/harness.js.map +1 -0
- package/dist/src/http/protocol.d.ts +84 -0
- package/dist/src/http/protocol.d.ts.map +1 -0
- package/dist/src/http/protocol.js +22 -0
- package/dist/src/http/protocol.js.map +1 -0
- package/dist/src/http/provider.d.ts +123 -0
- package/dist/src/http/provider.d.ts.map +1 -0
- package/dist/src/http/provider.js +197 -0
- package/dist/src/http/provider.js.map +1 -0
- package/dist/src/http/serve.d.ts +34 -0
- package/dist/src/http/serve.d.ts.map +1 -0
- package/dist/src/http/serve.js +171 -0
- package/dist/src/http/serve.js.map +1 -0
- package/dist/src/http/service.d.ts +86 -0
- package/dist/src/http/service.d.ts.map +1 -0
- package/dist/src/http/service.js +161 -0
- package/dist/src/http/service.js.map +1 -0
- package/dist/src/index.d.ts +68 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +62 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/meter.d.ts +39 -0
- package/dist/src/meter.d.ts.map +1 -0
- package/dist/src/meter.js +100 -0
- package/dist/src/meter.js.map +1 -0
- package/dist/src/offer.d.ts +6 -0
- package/dist/src/offer.d.ts.map +1 -0
- package/dist/src/offer.js +92 -0
- package/dist/src/offer.js.map +1 -0
- package/dist/src/reconcile.d.ts +18 -0
- package/dist/src/reconcile.d.ts.map +1 -0
- package/dist/src/reconcile.js +101 -0
- package/dist/src/reconcile.js.map +1 -0
- package/dist/src/reservation.d.ts +51 -0
- package/dist/src/reservation.d.ts.map +1 -0
- package/dist/src/reservation.js +124 -0
- package/dist/src/reservation.js.map +1 -0
- package/dist/src/session.d.ts +40 -0
- package/dist/src/session.d.ts.map +1 -0
- package/dist/src/session.js +92 -0
- package/dist/src/session.js.map +1 -0
- package/dist/src/signer.d.ts +31 -0
- package/dist/src/signer.d.ts.map +1 -0
- package/dist/src/signer.js +57 -0
- package/dist/src/signer.js.map +1 -0
- package/dist/src/store.d.ts +30 -0
- package/dist/src/store.d.ts.map +1 -0
- package/dist/src/store.js +120 -0
- package/dist/src/store.js.map +1 -0
- package/dist/src/types.d.ts +80 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +9 -0
- package/dist/src/types.js.map +1 -0
- package/dist/tools/anchor-cli.d.ts +2 -0
- package/dist/tools/anchor-cli.d.ts.map +1 -0
- package/dist/tools/anchor-cli.js +101 -0
- package/dist/tools/anchor-cli.js.map +1 -0
- package/dist/tools/anchor.d.ts +68 -0
- package/dist/tools/anchor.d.ts.map +1 -0
- package/dist/tools/anchor.js +136 -0
- package/dist/tools/anchor.js.map +1 -0
- package/dist/tools/chain.d.ts +18 -0
- package/dist/tools/chain.d.ts.map +1 -0
- package/dist/tools/chain.js +18 -0
- package/dist/tools/chain.js.map +1 -0
- package/dist/tools/conformance.d.ts +2 -0
- package/dist/tools/conformance.d.ts.map +1 -0
- package/dist/tools/conformance.js +220 -0
- package/dist/tools/conformance.js.map +1 -0
- package/dist/tools/contracts.d.ts +2 -0
- package/dist/tools/contracts.d.ts.map +1 -0
- package/dist/tools/contracts.js +68 -0
- package/dist/tools/contracts.js.map +1 -0
- package/dist/tools/covenant-binding.d.ts +40 -0
- package/dist/tools/covenant-binding.d.ts.map +1 -0
- package/dist/tools/covenant-binding.js +60 -0
- package/dist/tools/covenant-binding.js.map +1 -0
- package/dist/tools/covenant-profile.d.ts +41 -0
- package/dist/tools/covenant-profile.d.ts.map +1 -0
- package/dist/tools/covenant-profile.js +74 -0
- package/dist/tools/covenant-profile.js.map +1 -0
- package/dist/tools/covenant.d.ts +30 -0
- package/dist/tools/covenant.d.ts.map +1 -0
- package/dist/tools/covenant.js +54 -0
- package/dist/tools/covenant.js.map +1 -0
- package/dist/tools/demo-report.d.ts +24 -0
- package/dist/tools/demo-report.d.ts.map +1 -0
- package/dist/tools/demo-report.js +40 -0
- package/dist/tools/demo-report.js.map +1 -0
- package/dist/tools/demo.d.ts +2 -0
- package/dist/tools/demo.d.ts.map +1 -0
- package/dist/tools/demo.js +204 -0
- package/dist/tools/demo.js.map +1 -0
- package/dist/tools/dust-map.d.ts +2 -0
- package/dist/tools/dust-map.d.ts.map +1 -0
- package/dist/tools/dust-map.js +63 -0
- package/dist/tools/dust-map.js.map +1 -0
- package/dist/tools/expire-cases.d.ts +4 -0
- package/dist/tools/expire-cases.d.ts.map +1 -0
- package/dist/tools/expire-cases.js +211 -0
- package/dist/tools/expire-cases.js.map +1 -0
- package/dist/tools/fee-check.d.ts +2 -0
- package/dist/tools/fee-check.d.ts.map +1 -0
- package/dist/tools/fee-check.js +127 -0
- package/dist/tools/fee-check.js.map +1 -0
- package/dist/tools/fixtures.d.ts +39 -0
- package/dist/tools/fixtures.d.ts.map +1 -0
- package/dist/tools/fixtures.js +31 -0
- package/dist/tools/fixtures.js.map +1 -0
- package/dist/tools/gen-contract-tests.d.ts +2 -0
- package/dist/tools/gen-contract-tests.d.ts.map +1 -0
- package/dist/tools/gen-contract-tests.js +170 -0
- package/dist/tools/gen-contract-tests.js.map +1 -0
- package/dist/tools/kaspa.d.ts +13 -0
- package/dist/tools/kaspa.d.ts.map +1 -0
- package/dist/tools/kaspa.js +76 -0
- package/dist/tools/kaspa.js.map +1 -0
- package/dist/tools/live-deadline.d.ts +2 -0
- package/dist/tools/live-deadline.d.ts.map +1 -0
- package/dist/tools/live-deadline.js +122 -0
- package/dist/tools/live-deadline.js.map +1 -0
- package/dist/tools/live-expire.d.ts +2 -0
- package/dist/tools/live-expire.d.ts.map +1 -0
- package/dist/tools/live-expire.js +127 -0
- package/dist/tools/live-expire.js.map +1 -0
- package/dist/tools/live-settle.d.ts +2 -0
- package/dist/tools/live-settle.d.ts.map +1 -0
- package/dist/tools/live-settle.js +226 -0
- package/dist/tools/live-settle.js.map +1 -0
- package/dist/tools/live-steps.d.ts +63 -0
- package/dist/tools/live-steps.d.ts.map +1 -0
- package/dist/tools/live-steps.js +155 -0
- package/dist/tools/live-steps.js.map +1 -0
- package/dist/tools/model.d.ts +32 -0
- package/dist/tools/model.d.ts.map +1 -0
- package/dist/tools/model.js +99 -0
- package/dist/tools/model.js.map +1 -0
- package/dist/tools/send.d.ts +2 -0
- package/dist/tools/send.d.ts.map +1 -0
- package/dist/tools/send.js +41 -0
- package/dist/tools/send.js.map +1 -0
- package/dist/tools/session-chain.d.ts +69 -0
- package/dist/tools/session-chain.d.ts.map +1 -0
- package/dist/tools/session-chain.js +113 -0
- package/dist/tools/session-chain.js.map +1 -0
- package/dist/tools/sighash.d.ts +9 -0
- package/dist/tools/sighash.d.ts.map +1 -0
- package/dist/tools/sighash.js +68 -0
- package/dist/tools/sighash.js.map +1 -0
- package/dist/tools/sigscript-check.d.ts +2 -0
- package/dist/tools/sigscript-check.d.ts.map +1 -0
- package/dist/tools/sigscript-check.js +113 -0
- package/dist/tools/sigscript-check.js.map +1 -0
- package/dist/tools/sigscript.d.ts +61 -0
- package/dist/tools/sigscript.d.ts.map +1 -0
- package/dist/tools/sigscript.js +70 -0
- package/dist/tools/sigscript.js.map +1 -0
- package/dist/tools/state-layout.d.ts +2 -0
- package/dist/tools/state-layout.d.ts.map +1 -0
- package/dist/tools/state-layout.js +57 -0
- package/dist/tools/state-layout.js.map +1 -0
- package/package.json +59 -0
- package/spec/CONFORMANCE.md +80 -0
- package/spec/SPEC.md +821 -0
- package/spec/conformance-vectors.json +677 -0
package/spec/SPEC.md
ADDED
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
# Metered — a metered session scheme for x402
|
|
2
|
+
|
|
3
|
+
**Status:** Phase 0 draft. Normative. Nothing implemented.
|
|
4
|
+
**Version:** 0.1.0-draft.2 · 2026-09-09
|
|
5
|
+
**Scheme identifier:** `metered`
|
|
6
|
+
|
|
7
|
+
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 0. Why this exists
|
|
12
|
+
|
|
13
|
+
x402 ships two schemes. `exact` transfers a fixed amount. `upto` authorises a maximum and charges
|
|
14
|
+
actual consumption. The `upto` specification states three non-goals:
|
|
15
|
+
|
|
16
|
+
- *"Multi-settlement / streaming"* — out of scope
|
|
17
|
+
- *"Recurring payments"* — out of scope
|
|
18
|
+
- *"Settling the same authorization multiple times (e.g. pay-per-chunk streaming)"* — not supported
|
|
19
|
+
|
|
20
|
+
and settlement is unilateral: the resource server reports consumption, the facilitator checks only
|
|
21
|
+
that it does not exceed the cap. There is no client verification and no dispute mechanism.
|
|
22
|
+
|
|
23
|
+
`metered` adds the two missing pieces: **babel reservation**, so a session can span many
|
|
24
|
+
settlements, and **two-sided measurement**, so the buyer's own count is part of what settles.
|
|
25
|
+
|
|
26
|
+
### 0.0 The babel
|
|
27
|
+
|
|
28
|
+
**A `babel` is one reserved slice of delivered work: `babelUnits` units of the Offer's `unit`.**
|
|
29
|
+
It is the quantum of this protocol. A Reservation authorises exactly one babel, a Measurement
|
|
30
|
+
reports one babel, and a State settles the running total of every babel agreed so far.
|
|
31
|
+
|
|
32
|
+
**The babel is the exposure bound.** It is the most either party can lose to the other turning
|
|
33
|
+
dishonest mid-session, it is fixed by the Offer before anything is spent, and every parameter in
|
|
34
|
+
this document is ultimately a statement about how small it can be made.
|
|
35
|
+
|
|
36
|
+
Named as the *bel* is -- a unit for a quantity that is otherwise awkward to measure -- and after
|
|
37
|
+
the tower, which is where two parties last stopped being able to agree on words.
|
|
38
|
+
|
|
39
|
+
### 0.1 What decided the parameters
|
|
40
|
+
|
|
41
|
+
Two studies ran before this document was written. They are in `../evidence/`, seeded and reproducible.
|
|
42
|
+
|
|
43
|
+
| Finding | Consequence |
|
|
44
|
+
|---|---|
|
|
45
|
+
| Per-babel token summation diverges by exactly 1 token in 0.08% of sessions (3 counter-examples in 3,634 adversarial trials) | `toleranceAbs` MUST be ≥ 1 **for a tokeniser** — §6 |
|
|
46
|
+
| Re-encoding an *interior* token slice diverges 7.6% of the time; babels tiling from zero drop that to 0.08% | Babels MUST tile from sequence position zero |
|
|
47
|
+
| Per-frame tokenisation is wrong by up to 21.5% (code) and 21.2% (CJK), systematically upward | Implementations MUST reassemble before tokenising |
|
|
48
|
+
| The same text under `o200k_base` vs `cl100k_base` differs by 40.9% on CJK | The Offer MUST name the meter; an unnamed meter is not usable |
|
|
49
|
+
| Checkpoint fee 0.002 KAS, latency p50 1,053 ms / p90 1,879 ms over 25 live anchors | Checkpoints MUST be non-blocking |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 1. Roles and trust
|
|
54
|
+
|
|
55
|
+
| Party | Trusted for |
|
|
56
|
+
|---|---|
|
|
57
|
+
| Buyer | **Nothing.** Its measurement is a claim the provider need not believe. |
|
|
58
|
+
| Provider | **Nothing.** Same. |
|
|
59
|
+
| Kaspa consensus | Settlement and ordering. The only trust assumption. |
|
|
60
|
+
| Facilitator | **Nothing.** Optional convenience. Either party MUST be able to settle without one. |
|
|
61
|
+
|
|
62
|
+
There is no arbiter, oracle or reputation service. The only remedy for disagreement is to stop.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 2. Canonical encoding
|
|
67
|
+
|
|
68
|
+
Every signed object is encoded identically by both parties or nothing works. A field-order
|
|
69
|
+
divergence between two encoders is silent, and produces signatures that verify for neither.
|
|
70
|
+
|
|
71
|
+
1. Objects are serialised as JSON with **keys sorted by UTF-16 code unit**, ascending — this is
|
|
72
|
+
what RFC 8785 (JCS) mandates and what JavaScript's default `Array.prototype.sort` does.
|
|
73
|
+
It is **not** the same as Unicode code-point order: they diverge above the BMP, where
|
|
74
|
+
surrogate pairs (U+D800–DFFF) sort below U+E000–FFFF. `�` and `U+10000` order
|
|
75
|
+
oppositely under the two rules. Field names in this version are ASCII, so nothing is
|
|
76
|
+
affected today — but an implementer following the wrong rule would diverge silently the
|
|
77
|
+
first time a key contained an astral character.
|
|
78
|
+
2. **No insignificant whitespace.** No spaces after `:` or `,`, no trailing newline.
|
|
79
|
+
3. Integers are JSON numbers and MUST be exactly representable — no value may exceed
|
|
80
|
+
2^53−1. Sompi amounts and unit counts are integers. **Floats are forbidden. There are none in
|
|
81
|
+
this protocol** — the last of them, `toleranceRel`, was removed in this version (§5 rule 4), so
|
|
82
|
+
nothing settled here depends on binary floating point being reproduced identically by two
|
|
83
|
+
implementations.
|
|
84
|
+
4. `null` is permitted only where this document names it (`prevState` at sequence 0).
|
|
85
|
+
5. Byte strings are **lower-case hexadecimal**, unprefixed, of the exact length stated.
|
|
86
|
+
6. **The signature is computed over the object with its own signature field absent**, not over a
|
|
87
|
+
field set to null or empty. **BIP340 receives the canonical bytes themselves, NOT a digest of
|
|
88
|
+
them.**
|
|
89
|
+
|
|
90
|
+
That last sentence exists because its absence was a real gap, found by writing a second
|
|
91
|
+
implementation. BIP340 accepts a message of any length, so signing `blake3(canonical)` is just
|
|
92
|
+
as implementable as signing `canonical` and looks just as correct -- and §3.4.1 explicitly
|
|
93
|
+
digests first for a State, which invites a reader to assume signing always digests first. It
|
|
94
|
+
does not. **A State is the exception**, and it is the exception because a Kaspa script must
|
|
95
|
+
reconstruct what it verifies and cannot build canonical JSON.
|
|
96
|
+
7. Digests are **BLAKE3-256**, 32 bytes, hex — **everywhere, without exception.** The message
|
|
97
|
+
digest that chains States, the content digest, the session id, the parties commitment and
|
|
98
|
+
the checkpoint digest all use the same function over the same canonical bytes.
|
|
99
|
+
|
|
100
|
+
This is stated so emphatically because it was wrong. The borrowed encoder used BLAKE2b for
|
|
101
|
+
message digests and BLAKE3 for content addressing, while this document called both blake3.
|
|
102
|
+
An implementer following the sentence would have produced entirely different `prevState`
|
|
103
|
+
values and a different checkpoint digest — total incompatibility, from one word. The
|
|
104
|
+
encoder is now vendored in `src/encoding.ts` and uses BLAKE3 for all of it.
|
|
105
|
+
8. **Signature bytes are not canonical and MUST NOT be compared for equality.** BIP340 signs with
|
|
106
|
+
random auxiliary data, so two correct signatures over the same message differ. A signature is
|
|
107
|
+
*verified*, never diffed. Conformance vectors therefore pin the signing payload and its digest,
|
|
108
|
+
and assert only that a signature verifies.
|
|
109
|
+
|
|
110
|
+
An implementation MUST reject an object whose re-encoding does not reproduce the bytes it verified.
|
|
111
|
+
|
|
112
|
+
**One object is signed over something other than its JSON form: the State.** Its two signatures
|
|
113
|
+
cover the fixed byte concatenation defined in §3.4.1, not its canonical JSON. This is not a
|
|
114
|
+
stylistic exception -- the covenant must reconstruct exactly what it verifies, and a Kaspa script
|
|
115
|
+
cannot build canonical JSON. Every rule above still governs how a State is *transmitted*; §3.4.1
|
|
116
|
+
governs what its signatures *cover*. An implementation that signs a State's JSON form produces a
|
|
117
|
+
signature the chain cannot check, and a session that cannot settle.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 3. Messages
|
|
122
|
+
|
|
123
|
+
### 3.1 Offer
|
|
124
|
+
|
|
125
|
+
Sent by the provider inside the HTTP 402 response as an x402 `PaymentRequirements` with
|
|
126
|
+
`scheme: "metered"`. Signed by the provider. It is a commitment, not a suggestion.
|
|
127
|
+
|
|
128
|
+
| Field | Type | Rule |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| `v` | int | MUST be `1`. |
|
|
131
|
+
| `scheme` | string | MUST be `"metered"`. |
|
|
132
|
+
| `network` | string | CAIP-2-style, e.g. `"kaspa:testnet-10"`. |
|
|
133
|
+
| `asset` | string | `"KAS"` in this version. |
|
|
134
|
+
| `sessionId` | hex[16] | Provider-chosen, unique. Binds every later message. See §3.1a. |
|
|
135
|
+
| `unit` | string | Versioned unit identifier. §6 defines two. |
|
|
136
|
+
| `meter` | string | **MUST be present and resolvable, and MUST measure `unit`.** See §6. |
|
|
137
|
+
| `unitPriceSompi` | int | Price of one unit. MUST be ≥ 1. |
|
|
138
|
+
| `babelUnits` | int | Units per reservation. **This is the exposure bound.** MUST be ≥ 1. |
|
|
139
|
+
| `maxBabels` | int | Session ceiling. MUST be ≥ 1. |
|
|
140
|
+
| `toleranceAbs` | int | **MUST be ≥ the meter's floor** (§6): 0 for an exact meter, 1 otherwise. |
|
|
141
|
+
| `checkpointEvery` | int | Chunks between checkpoints. `0` disables checkpointing. |
|
|
142
|
+
| `responseWindowDaa` | int | **Relative** sequence delay. MUST be in `1..=4294967295`. See §7.3. |
|
|
143
|
+
| `buyerPubkey` | hex[32] | BIP340 x-only. |
|
|
144
|
+
| `providerPubkey` | hex[32] | BIP340 x-only. |
|
|
145
|
+
| `partiesCommitment` | hex[32] | `blake3(buyerPubkey ‖ providerPubkey)`. |
|
|
146
|
+
| `sig` | hex[64] | Provider signature. |
|
|
147
|
+
|
|
148
|
+
A buyer MUST reject an Offer with a `toleranceAbs` below its meter's floor (§6), an absent or
|
|
149
|
+
unresolvable `meter`, a `meter` that does not measure the named `unit`, or a `responseWindowDaa`
|
|
150
|
+
outside the stated range.
|
|
151
|
+
|
|
152
|
+
### 3.1a `sessionId` novelty is the buyer's obligation
|
|
153
|
+
|
|
154
|
+
**A buyer MUST reject an Offer carrying a `sessionId` it has already been offered by the same
|
|
155
|
+
provider, and MUST retain enough history to do so for as long as it will accept Offers from that
|
|
156
|
+
provider.**
|
|
157
|
+
|
|
158
|
+
`sessionId` is **provider-chosen**, and every threat that its width appears to address is one the
|
|
159
|
+
provider is not subject to. Sixteen bytes give 128 bits against accidental collision and against a
|
|
160
|
+
third party trying to guess or grind one -- both real, both handled. Neither is the interesting
|
|
161
|
+
case. A provider does not guess its own identifiers: it picks them, and picking a previous one
|
|
162
|
+
costs nothing.
|
|
163
|
+
|
|
164
|
+
The consequence is that the settlement preimage of §3.4.1 is no longer unique to a session. Two
|
|
165
|
+
sessions sharing a `sessionId` and a `(seq, cumulativeUnits, cumulativeSompi, prevState)` produce
|
|
166
|
+
the *same 72 bytes*, so the buyer's signature from the first session is a valid signature for the
|
|
167
|
+
second. That is exactly threat X1, cross-session replay, executed by the one party for
|
|
168
|
+
whom no choice of width is a defence.
|
|
169
|
+
|
|
170
|
+
No larger `sessionId` fixes this, and no on-chain rule can: the covenant is instantiated per
|
|
171
|
+
session and cannot see the other one. **The only party positioned to catch a reused `sessionId` is
|
|
172
|
+
the buyer, and only by remembering.** Hence the rule above, and hence the width was reduced to 16
|
|
173
|
+
bytes with nothing lost -- the bytes were never doing this job.
|
|
174
|
+
|
|
175
|
+
An implementation that keeps no such history MUST document that it does not, because a buyer
|
|
176
|
+
without it is unprotected against a provider that reuses identifiers deliberately.
|
|
177
|
+
|
|
178
|
+
**THIS RULE IS ALSO WHAT SEPARATES THE NETWORKS.** The §3.4.1 preimage does not commit to a
|
|
179
|
+
network, and a redeem script hashes to the same value on every one of them -- so a State signed
|
|
180
|
+
for a testnet session would, in principle, settle a mainnet covenant carrying the same parties and
|
|
181
|
+
`sessionId`. It cannot in practice, and the reason is that the history above is keyed by provider
|
|
182
|
+
and identifier, **deliberately not by network**: the second Offer is refused wherever it claims to
|
|
183
|
+
be. The buyer also funds the covenant, so the collision cannot be created without its help.
|
|
184
|
+
|
|
185
|
+
**Binding the network into the preimage instead was measured and rejected.** It costs 4 bytes in
|
|
186
|
+
the hand-written covenant, which has 16 spare, and **28 bytes in the Argent port, which has none**
|
|
187
|
+
-- the tag becomes state, and a continuation must re-emit it. Spending the only implementation
|
|
188
|
+
that a compiler checks, to duplicate a guarantee §3.1a already gives, is a bad trade. An
|
|
189
|
+
implementation that drops the novelty rule loses network separation along with everything else,
|
|
190
|
+
which is the other reason it is a MUST.
|
|
191
|
+
|
|
192
|
+
### 3.2 Reservation
|
|
193
|
+
|
|
194
|
+
Signed by the buyer. Authorises **one babel**, never the session.
|
|
195
|
+
|
|
196
|
+
**A provider MUST verify that signature against the Offer's `buyerPubkey` before doing anything
|
|
197
|
+
else with the Reservation, and MUST refuse it otherwise.** An unverified Reservation authorises
|
|
198
|
+
nothing: it is not evidence the buyer asked for the work, so a provider acting on one is
|
|
199
|
+
delivering for free to whoever sent it, and holds no record of an authorisation to bill against.
|
|
200
|
+
Because `sessionId` travels in clear, "whoever sent it" includes anyone who has seen one message.
|
|
201
|
+
|
|
202
|
+
| Field | Type | Rule |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| `v` | int | MUST be `1`. |
|
|
205
|
+
| `sessionId` | hex[16] | MUST equal the Offer's. |
|
|
206
|
+
| `seq` | int | Chunk index from 0, strictly incrementing by exactly 1. |
|
|
207
|
+
| `units` | int | Units authorised. MUST be ≤ `babelUnits`. |
|
|
208
|
+
| `cumulativeUnits` | int | Running total including this babel. See below. |
|
|
209
|
+
| `cumulativeSompi` | int | Running amount. Recomputed and compared, never trusted. |
|
|
210
|
+
| `prevState` | hex[32] or null | Digest of the previous **doubly-signed** State. `null` at `seq` 0. |
|
|
211
|
+
| `sig` | hex[64] | Buyer signature. |
|
|
212
|
+
|
|
213
|
+
**Running from what.** Both running totals build on the previous **State**'s figures -- what was
|
|
214
|
+
actually settled -- plus this babel's *authorised* amount:
|
|
215
|
+
|
|
216
|
+
cumulativeUnits = prevState.cumulativeUnits + units
|
|
217
|
+
cumulativeSompi = prevState.cumulativeSompi + units x unitPriceSompi
|
|
218
|
+
|
|
219
|
+
This needs saying because §5 rule 6 bills the **lower** of the two counts, so the authorised and
|
|
220
|
+
settled figures diverge the first time the parties disagree by a token, and they never re-converge.
|
|
221
|
+
An implementation that instead accumulated its own authorised totals would drift a little further
|
|
222
|
+
from the settled chain on every divergent babel, and the two parties would disagree about
|
|
223
|
+
`cumulativeSompi` while both followed this document.
|
|
224
|
+
|
|
225
|
+
So: **a Reservation is a ceiling measured from the last settled point, and a State is a fact.** The
|
|
226
|
+
Reservation says *at most this much will be owed after this babel*; the State says what is owed.
|
|
227
|
+
`cumulativeSompi` in a Reservation is therefore an upper bound on the State that follows it, and
|
|
228
|
+
equal to it only when the two counts agreed exactly.
|
|
229
|
+
|
|
230
|
+
### 3.3 Measurement
|
|
231
|
+
|
|
232
|
+
Both parties emit one per babel boundary. Signed by its author.
|
|
233
|
+
|
|
234
|
+
| Field | Type | Rule |
|
|
235
|
+
|---|---|---|
|
|
236
|
+
| `v` | int | MUST be `1`. |
|
|
237
|
+
| `sessionId` | hex[16] | |
|
|
238
|
+
| `seq` | int | The babel being reported. |
|
|
239
|
+
| `by` | string | `"buyer"` or `"provider"`. MUST match the signing key. |
|
|
240
|
+
| `units` | int | Units this party counted **for this babel alone**. |
|
|
241
|
+
| `cumulativeUnits` | int | This party's running total. Catches drift a per-babel check misses. |
|
|
242
|
+
| `contentDigest` | hex[32] | blake3 over the exact bytes of this babel. See §6.3. |
|
|
243
|
+
| `measurementId` | hex[16] | Idempotency key. A retransmission MUST be a no-op -- see below. |
|
|
244
|
+
| `sig` | hex[64] | |
|
|
245
|
+
|
|
246
|
+
### 3.3a A retransmission is a no-op, not a disagreement
|
|
247
|
+
|
|
248
|
+
A Measurement carries `measurementId` so the same message can arrive twice without being counted
|
|
249
|
+
twice. **A party receiving a `measurementId` it has already settled MUST return the result it
|
|
250
|
+
returned the first time, unchanged.** It MUST NOT halt, and MUST NOT settle it again.
|
|
251
|
+
|
|
252
|
+
This is not a nicety. Retransmission is ordinary behaviour for anything carried over a network: a
|
|
253
|
+
client whose request times out will resend it. An implementation that treats the second copy as a
|
|
254
|
+
protocol violation converts a lost response -- nobody's fault, nobody's disagreement -- into a
|
|
255
|
+
permanently halted session, and does so most often to the honest party with the worst connection.
|
|
256
|
+
|
|
257
|
+
### 3.4 State
|
|
258
|
+
|
|
259
|
+
The only object that can settle. Carries **two** signatures.
|
|
260
|
+
|
|
261
|
+
| Field | Type | Rule |
|
|
262
|
+
|---|---|---|
|
|
263
|
+
| `v` | int | MUST be `1`. |
|
|
264
|
+
| `sessionId` | hex[16] | |
|
|
265
|
+
| `seq` | int | |
|
|
266
|
+
| `cumulativeUnits` | int | Agreed total. |
|
|
267
|
+
| `cumulativeSompi` | int | Agreed amount owed the provider. |
|
|
268
|
+
| `prevState` | hex[32] or null | Digest of the previous State. The session is a hash chain. |
|
|
269
|
+
| `buyerSig` | hex[64] | Both REQUIRED. |
|
|
270
|
+
| `providerSig` | hex[64] | A State with one signature is not a State. |
|
|
271
|
+
|
|
272
|
+
Both signatures are computed over the **settlement preimage** of §3.4.1 -- **not** over the
|
|
273
|
+
State's canonical JSON with its signature fields absent, which is how every other signed object in
|
|
274
|
+
this document works. See §2.
|
|
275
|
+
|
|
276
|
+
### 3.4.1 The settlement preimage
|
|
277
|
+
|
|
278
|
+
The only bytes a State's signatures ever cover. Fixed width, fixed order, no delimiters, no
|
|
279
|
+
length prefixes -- the covenant reconstructs this exact layout from values it already holds, so
|
|
280
|
+
anything variable-length would be unreconstructable.
|
|
281
|
+
|
|
282
|
+
| Offset | Width | Field |
|
|
283
|
+
|---|---|---|
|
|
284
|
+
| 0 | 16 | `sessionId` |
|
|
285
|
+
| 16 | 8 | `seq` |
|
|
286
|
+
| 24 | 8 | `cumulativeUnits` |
|
|
287
|
+
| 32 | 8 | `cumulativeSompi` |
|
|
288
|
+
| 40 | 32 | `prevState` |
|
|
289
|
+
| | **72** | total |
|
|
290
|
+
|
|
291
|
+
1. Integers are **8-byte signed-magnitude**, the encoding `OpNum2Bin(x, 8)` produces. §2.3 already
|
|
292
|
+
caps every integer in this protocol at 2^53−1, which is comfortably inside 8 signed-magnitude
|
|
293
|
+
bytes, so the conversion is always defined. An implementation MUST still range-check before
|
|
294
|
+
converting: SilverScript's undefined behaviour is **fail-open**, and an out-of-range
|
|
295
|
+
`OpNum2Bin` is undefined, not a rejection.
|
|
296
|
+
2. `prevState` is 32 bytes always. At `seq` 0, where the JSON carries `null`, the preimage carries
|
|
297
|
+
**32 zero bytes**. A `null` has no byte form and the covenant cannot branch on one cheaply.
|
|
298
|
+
3. The signed digest is `blake3(preimage)`, 32 bytes, per §2.7. Both parties sign **that digest**
|
|
299
|
+
with BIP340. On-chain it is verified with `checkMsgSig`, which checks a signature over supplied
|
|
300
|
+
data rather than over the spending transaction.
|
|
301
|
+
4. **`v` is deliberately absent.** A version field would cost 8 more bytes against a 520-byte
|
|
302
|
+
script limit, and it is not load-bearing here: a future version changes the covenant's
|
|
303
|
+
bytecode, which changes its P2SH address, which means the funds a v1 signature could be
|
|
304
|
+
replayed against do not exist at a v2 address. The contract's own bytecode is the version tag.
|
|
305
|
+
This is a deliberate omission with an argument, not an oversight.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## 4. Signer obligations
|
|
310
|
+
|
|
311
|
+
Adapted from Kurrent's stated signer policy. These are obligations on implementations, not on the
|
|
312
|
+
wire format, and violating them loses money without any message being malformed.
|
|
313
|
+
|
|
314
|
+
1. A party MUST sign **at most one** State for a given `(sessionId, seq)`.
|
|
315
|
+
2. A party MUST **durably record** the highest State it has signed **before transmitting it**.
|
|
316
|
+
Record-then-send. Never send-then-record.
|
|
317
|
+
3. A party MUST NOT sign a State whose `prevState` does not match the digest of the State it last
|
|
318
|
+
agreed to.
|
|
319
|
+
4. On restart, a party MUST load its highest recorded State before participating further, and MUST
|
|
320
|
+
refuse to sign at a `seq` at or below it.
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## 5. The reconciliation rule
|
|
325
|
+
|
|
326
|
+
At each babel boundary, in order. Any failure halts the session.
|
|
327
|
+
|
|
328
|
+
1. Both Measurement signatures verify, and each `by` matches the key that signed it.
|
|
329
|
+
2. Both `seq` equal the babel being reconciled.
|
|
330
|
+
3. **`contentDigest` values are equal.** If counts match but digests differ, the parties are
|
|
331
|
+
describing different bytes and counting cannot resolve it.
|
|
332
|
+
4. `|buyerUnits − providerUnits| ≤ toleranceAbs`. **The bound is absolute and does not scale with
|
|
333
|
+
the babel.** An earlier draft added a relative term. It was removed because the evidence never
|
|
334
|
+
supported it: honest divergence is a boundary effect, Study A saw it three times in 3,634
|
|
335
|
+
adversarial trials and every occurrence was exactly one token, and Study C found none at all at
|
|
336
|
+
seven babel sizes from 3 to 550 units with 4,000 boundaries each. The magnitude does not grow
|
|
337
|
+
with the babel, so a bound that grows with the babel widens the room a counterparty can shave
|
|
338
|
+
in — §5.1's leak — in exchange for nothing measured.
|
|
339
|
+
5. The same bound holds for `cumulativeUnits`.
|
|
340
|
+
6. **The billed amount uses the lower of the two counts.**
|
|
341
|
+
|
|
342
|
+
### 5.0 What reconciliation produces
|
|
343
|
+
|
|
344
|
+
The rules above say what must hold. This says what comes out, because a second implementation
|
|
345
|
+
cannot guess it and two implementations that disagree about the OUTPUT disagree about the bill.
|
|
346
|
+
|
|
347
|
+
On success:
|
|
348
|
+
|
|
349
|
+
| Field | Value |
|
|
350
|
+
|---|---|
|
|
351
|
+
| `billedUnits` | `min(buyerUnits, providerUnits)` — rule 6 |
|
|
352
|
+
| `billedCumulativeUnits` | `min(buyerCumulativeUnits, providerCumulativeUnits)` |
|
|
353
|
+
| `residual` | `providerUnits - buyerUnits`, **signed, in that order** |
|
|
354
|
+
|
|
355
|
+
`billedCumulativeUnits` is stated because rule 6 only names the per-babel amount. An implementation
|
|
356
|
+
that carries the provider's cumulative figure forward instead agrees about every babel and disagrees
|
|
357
|
+
about the session.
|
|
358
|
+
|
|
359
|
+
The **sign** of `residual` is stated because §5.1.1 feeds it to a one-sided CUSUM. Reversing it does
|
|
360
|
+
not produce a detector that fires late; it produces one that ignores a counterparty shaving and
|
|
361
|
+
alarms on an honest one.
|
|
362
|
+
|
|
363
|
+
On failure the outcome carries a `reason`, which is one of:
|
|
364
|
+
|
|
365
|
+
`signature`, `sequence`, `content-digest`, `tolerance-babel`, `tolerance-cumulative`, `bias`.
|
|
366
|
+
|
|
367
|
+
Rule 6 removes the incentive to sit at the top of the tolerance band. It also absorbs the one-token
|
|
368
|
+
divergence measured in Study A, since every counter-example found had the buyer counting fewer.
|
|
369
|
+
That second property was luck, and is recorded as luck.
|
|
370
|
+
|
|
371
|
+
### 5.1 Bias detection — symmetric and normative
|
|
372
|
+
|
|
373
|
+
Rule 6 bills the lower count, which removes the provider's incentive to sit at the top of the
|
|
374
|
+
tolerance band. Phase 1 found it hands the identical trick to the **buyer**: under-reporting by
|
|
375
|
+
exactly `toleranceAbs` every babel pays less, systematically, and no single babel breaches
|
|
376
|
+
anything. At 1 token on a 550-token babel that is 0.18% — small, but a leak with no mechanism
|
|
377
|
+
against it.
|
|
378
|
+
|
|
379
|
+
Therefore **both parties MUST** track the signed residual `providerUnits − buyerUnits` across the
|
|
380
|
+
session. **The honest residual is zero to measurement** (Study C: 0 non-zero residuals in 10,013
|
|
381
|
+
honest babels at 50/200/550 units; 95% upper bound 0.075% at every babel size tested), so any
|
|
382
|
+
sustained positive drift is signal rather than noise. A party MUST halt when the test below fires.
|
|
383
|
+
|
|
384
|
+
This converts tolerance-riding from an unbounded slow leak into a bounded one: an adversary must
|
|
385
|
+
stay inside the tolerance *and* keep the residual centred, which means giving back what it takes.
|
|
386
|
+
|
|
387
|
+
#### 5.1.1 The test, measured rather than chosen
|
|
388
|
+
|
|
389
|
+
"A persistent one-sided residual" was normative and undefined -- no threshold, no window, no
|
|
390
|
+
statistic, so two implementers could not produce the same behaviour. Study C
|
|
391
|
+
(`evidence/results-c.txt`) measured what honest sessions actually do and sized a test against it.
|
|
392
|
+
|
|
393
|
+
**Both parties MUST run a one-sided CUSUM over the per-babel residual `providerUnits - buyerUnits`:**
|
|
394
|
+
|
|
395
|
+
S[0] = 0
|
|
396
|
+
S[i] = max(0, S[i-1] + residual[i] - k) with k = 0.5
|
|
397
|
+
halt when S[i] >= h with h = 5
|
|
398
|
+
|
|
399
|
+
One-sided, because the threat is directional and so is the only honest divergence ever observed:
|
|
400
|
+
both make the residual **positive**. Sign therefore carries no information and rate is the whole
|
|
401
|
+
signal -- which is what a CUSUM measures and what "persistent" was groping for.
|
|
402
|
+
|
|
403
|
+
| Buyer under-reports 1 token on | Detected | Median babels to alarm |
|
|
404
|
+
|---|---|---|
|
|
405
|
+
| every babel (the 0.18% leak §5.1 names) | 100% | 9 |
|
|
406
|
+
| 50% of babels | 100% | 80 |
|
|
407
|
+
| 25% of babels | 1% | — |
|
|
408
|
+
| 10% of babels | never | — |
|
|
409
|
+
|
|
410
|
+
False alarms: **zero in 800,000 honest babels** (400 sessions of 2,000).
|
|
411
|
+
|
|
412
|
+
**What this does and does not bound, stated because a reader will otherwise infer zero.** The test
|
|
413
|
+
catches the threat §5.1 was written for, quickly. It does **not** catch a patient buyer: below
|
|
414
|
+
about a quarter of babels the leak is invisible to it. So §5.1's promise of converting an
|
|
415
|
+
unbounded slow leak into a bounded one holds, and **the bound is roughly 0.045% per babel** -- an
|
|
416
|
+
order of magnitude under the named threat, and not zero. An implementation that needs a tighter
|
|
417
|
+
bound must reduce `toleranceAbs`, not tune `k` and `h`; below `k = 0.5` the honest rate is no
|
|
418
|
+
longer known to be small enough, because Study C bounds it at 0.075% rather than measuring it as
|
|
419
|
+
exactly zero.
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
### 5.2 Only an authenticated counterparty may halt a session
|
|
424
|
+
|
|
425
|
+
§1 says "the only remedy for disagreement is to stop". That applies to two parties who have
|
|
426
|
+
**authenticated themselves** and cannot agree on a measurement. It does not apply to a message
|
|
427
|
+
from an unknown sender.
|
|
428
|
+
|
|
429
|
+
**An implementation MUST NOT halt a session because a message failed to authenticate.** It MUST
|
|
430
|
+
refuse the message and leave the session untouched. Halting is reserved for failures that only an
|
|
431
|
+
authenticated counterparty can reach.
|
|
432
|
+
|
|
433
|
+
The distinction is load-bearing because `sessionId` is not a secret -- it is carried in clear in
|
|
434
|
+
every message, and it must be, since it is what binds them together (§3.1a). An implementation
|
|
435
|
+
that halts on any malformed input therefore lets anyone who has observed a single message destroy
|
|
436
|
+
the session permanently, at no cost and with no key. That is a denial of service wearing this
|
|
437
|
+
specification as its authorisation.
|
|
438
|
+
|
|
439
|
+
Concretely: a bad signature, an unparseable body and an oversized body are refusals. A measurement
|
|
440
|
+
outside tolerance, a broken State chain and a tripped bias detector are halts.
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## 6. Units and meters
|
|
445
|
+
|
|
446
|
+
The protocol counts integers and does not know what they represent. A **unit** names what is being
|
|
447
|
+
sold; a **meter** names the procedure that turns delivered content into a number. The Offer carries
|
|
448
|
+
both, and a meter that does not measure the named unit MUST be refused -- counting tokens against a
|
|
449
|
+
byte price is not a rounding error, it is a different bill.
|
|
450
|
+
|
|
451
|
+
| Unit | Meter | Exact |
|
|
452
|
+
|---|---|---|
|
|
453
|
+
| `llm.output_tokens.v1` | `o200k_base` | no |
|
|
454
|
+
| `net.bytes_delivered.v1` | `octets` | yes |
|
|
455
|
+
|
|
456
|
+
### 6.0 Exact and inexact meters, and where the tolerance comes from
|
|
457
|
+
|
|
458
|
+
**A meter is EXACT when two correct implementations always reach the same number for the same
|
|
459
|
+
bytes.** That property decides `toleranceAbs`, and it belongs to the meter rather than to the
|
|
460
|
+
protocol:
|
|
461
|
+
|
|
462
|
+
| | Minimum `toleranceAbs` | Because |
|
|
463
|
+
|---|---|---|
|
|
464
|
+
| exact | **0**, and it SHOULD be 0 | §5 rule 3 has already agreed `contentDigest`, so the bytes are identical and the counts cannot differ. A tolerance would absorb no honest divergence and would be pure room to shave in. |
|
|
465
|
+
| inexact | **1** | Tokenisation is a lossy map from bytes to a count. Two implementations can agree on every byte and still differ by one token, because the boundaries belong to the tokeniser. §0.1 measured it. |
|
|
466
|
+
|
|
467
|
+
An earlier version of this document required `toleranceAbs ≥ 1` of every Offer. That rule was
|
|
468
|
+
derived from tokenisation and stated as though it were a property of the protocol; adding a second
|
|
469
|
+
unit is what made the difference visible.
|
|
470
|
+
|
|
471
|
+
### 6.1 `llm.output_tokens.v1`
|
|
472
|
+
|
|
473
|
+
Tokens in the assistant content **actually delivered to the buyer**, tokenised with the meter named
|
|
474
|
+
in the Offer.
|
|
475
|
+
|
|
476
|
+
**Not counted:** input tokens, system prompt, and anything the buyer does not receive.
|
|
477
|
+
|
|
478
|
+
#### Hidden reasoning tokens are out of scope
|
|
479
|
+
|
|
480
|
+
A model billing for internal reasoning the buyer never sees cannot be metered two-sided -- the
|
|
481
|
+
buyer cannot count what it was not given. Providers billing for hidden reasoning MUST either price
|
|
482
|
+
it into the delivered-token rate or use `exact` for that portion. This is a limitation of the
|
|
483
|
+
approach, not an omission, and it is the boundary of two-sided measurement rather than a gap in
|
|
484
|
+
this specification: no protocol can make a buyer able to count data it never receives.
|
|
485
|
+
|
|
486
|
+
#### Counting rules for `llm.output_tokens.v1`
|
|
487
|
+
|
|
488
|
+
1. Implementations MUST **reassemble the full babel** before tokenising. Tokenising stream frames
|
|
489
|
+
separately is wrong by up to 21.5% (§0.1) and MUST NOT be done.
|
|
490
|
+
2. Chunks MUST **tile from sequence position zero**. Cutting a session into interior slices
|
|
491
|
+
diverges 7.6% of the time; tiling from zero drops it to 0.08%. A future change to how babels are
|
|
492
|
+
cut would silently reintroduce this, which is why it is normative rather than advisory.
|
|
493
|
+
3. `contentDigest` is blake3 over the **raw delivered bytes**, with **no normalisation** — no
|
|
494
|
+
whitespace collapsing, no unicode normalisation, no trimming.
|
|
495
|
+
4. The `tokenizer` field MUST identify a specific, publicly obtainable tokeniser and version.
|
|
496
|
+
5. An implementation that cannot obtain the named tokeniser MUST **refuse the Offer**. It MUST NOT
|
|
497
|
+
count with a different one. A substituted tokeniser produces a number that looks like a
|
|
498
|
+
measurement and settles like one; §0.1 measured 40.9% divergence on CJK between two real
|
|
499
|
+
tokenisers, so the substitute is not an approximation of the agreed unit but a different unit
|
|
500
|
+
wearing its name.
|
|
501
|
+
6. Implementations of this specification in different languages MUST agree **token for token**,
|
|
502
|
+
not merely on totals. Two tokenisers can produce the same count for one input and disagree
|
|
503
|
+
about where the boundaries fall, which diverges on the next input rather than this one. This
|
|
504
|
+
reference implementation pins its JavaScript tokeniser against Python `tiktoken` over §0.1's
|
|
505
|
+
adversarial corpus -- CJK, emoji, combining marks, surrogate pairs, pathological whitespace --
|
|
506
|
+
and compares the token IDs.
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
### 6.2 `net.bytes_delivered.v1`
|
|
512
|
+
|
|
513
|
+
**Octets of the content actually delivered to the buyer, exactly as `contentDigest` covers them.**
|
|
514
|
+
The meter is `octets`, and it is exact.
|
|
515
|
+
|
|
516
|
+
1. Count the bytes as delivered. **No re-encoding, no normalisation, no decompression.** If a
|
|
517
|
+
transport compressed the body, the unit is the bytes the buyer received and digested, not the
|
|
518
|
+
bytes before or after any transform the transport applied. `contentDigest` is computed over the
|
|
519
|
+
same bytes, which is what makes the two sides agree by construction.
|
|
520
|
+
2. The rules of §6.1's counting section that concern *what* is counted apply unchanged:
|
|
521
|
+
reassemble the whole babel before counting, and tile babels from sequence position zero.
|
|
522
|
+
3. `toleranceAbs` SHOULD be 0. A non-zero tolerance is permitted and is a decision to accept
|
|
523
|
+
shaving, since there is no honest divergence for it to absorb.
|
|
524
|
+
|
|
525
|
+
This unit is why the protocol names a meter rather than a tokeniser. Anything a buyer physically
|
|
526
|
+
receives and can count -- bytes, frames, records -- fits the same machinery, and only §6 changes.
|
|
527
|
+
|
|
528
|
+
**What does NOT fit:** a resource the buyer never receives. Storage at rest has nothing delivered
|
|
529
|
+
to count and needs proof of continued possession, which is a different mechanism with different
|
|
530
|
+
assumptions. **This protocol meters delivery, not possession.**
|
|
531
|
+
|
|
532
|
+
### 6.4 Delivered content is bytes, and a transport encoding is not the content
|
|
533
|
+
|
|
534
|
+
Everything this protocol does to delivered content -- digest it under §6.3, count it under §6.1 or
|
|
535
|
+
§6.2, agree on it under §5 -- is defined over **bytes**. An implementation MUST NOT restrict
|
|
536
|
+
delivery to content expressible as text.
|
|
537
|
+
|
|
538
|
+
This is stated because it is easy to violate without noticing, and the reference implementation did
|
|
539
|
+
for as long as it had only ever sold tokens: every signature on its delivery path took a string, so
|
|
540
|
+
`net.bytes_delivered.v1` could be declared in an Offer and could not actually be served. The
|
|
541
|
+
specification was not wrong; the implementation was narrower than the specification, which is the
|
|
542
|
+
harder failure to see.
|
|
543
|
+
|
|
544
|
+
1. A transport that cannot carry arbitrary bytes MAY encode them -- JSON bodies, for instance,
|
|
545
|
+
require it. This reference transport base64-encodes the babel body and names the field
|
|
546
|
+
`contentB64` so that the encoding is visible at every use.
|
|
547
|
+
2. **The encoding is never what is metered or digested.** Both parties MUST decode before counting
|
|
548
|
+
and before computing `contentDigest`. Metering the encoded form bills the buyer for roughly a
|
|
549
|
+
third more than it received, and digesting it commits the parties to the encoding rather than to
|
|
550
|
+
the content.
|
|
551
|
+
3. A lenient decoder is acceptable but MUST NOT be relied upon. Base64 decoders commonly discard
|
|
552
|
+
characters outside the alphabet rather than refusing, so a corrupted field decodes to different
|
|
553
|
+
bytes instead of raising. That cannot pass silently here: different bytes produce a different
|
|
554
|
+
digest, and §5 rule 3 halts the session on a digest mismatch before any count is consulted.
|
|
555
|
+
|
|
556
|
+
## 7. Settlement
|
|
557
|
+
|
|
558
|
+
### 7.1 Cooperative close — NOT AVAILABLE in this version
|
|
559
|
+
|
|
560
|
+
~~Both parties sign an **ordinary spend** paying `cumulativeSompi` to the provider and the
|
|
561
|
+
remainder to the buyer. **The covenant is not executed.** This is what happens almost every time,
|
|
562
|
+
and keeping it off the covenant path removes nearly all the script-size pressure from the common
|
|
563
|
+
case.~~
|
|
564
|
+
|
|
565
|
+
**That is not implementable, and the reason is structural rather than a bug.** The session's funds
|
|
566
|
+
sit at a covenant P2SH address. A P2SH output cannot be spent without supplying its redeem script
|
|
567
|
+
and satisfying it, so **every** close executes the covenant. There is no "ordinary spend" available
|
|
568
|
+
to a UTXO locked by a script, and the sentence about removing script-size pressure has it exactly
|
|
569
|
+
backwards: a cooperative close must be a THIRD ENTRY POINT, and entry points are what script size
|
|
570
|
+
is made of.
|
|
571
|
+
|
|
572
|
+
**Measured, 2026-09-10.** A minimal cooperative entry -- bind the parties, check two signatures,
|
|
573
|
+
constrain no outputs because two consenting parties have already agreed the split -- takes the
|
|
574
|
+
contract from 516 bytes to **619**, against a limit of 520. It costs **103 bytes** and there are
|
|
575
|
+
**4** spare.
|
|
576
|
+
|
|
577
|
+
**Two custody models, and this version picked one.**
|
|
578
|
+
|
|
579
|
+
| | Funds live at | Cooperative close | Unilateral close |
|
|
580
|
+
|---|---|---|---|
|
|
581
|
+
| **Covenant** (this version) | a covenant P2SH | a third entry, +103 bytes | `settle` then `expire`, consensus-enforced |
|
|
582
|
+
| **Channel** (Lightning-shaped) | a plain 2-of-2 | an ordinary spend, free | pre-signed asymmetric commitments plus revocation |
|
|
583
|
+
|
|
584
|
+
They are alternatives, not complements: one UTXO cannot be both a bare 2-of-2 and a covenant. The
|
|
585
|
+
covenant model was chosen because consensus enforces the sequence ordering directly, which removes
|
|
586
|
+
revocation secrets and the entire class of bugs that comes with them. The price is that the common
|
|
587
|
+
case pays for a script, and §7.1 was written as though it did not.
|
|
588
|
+
|
|
589
|
+
**What a close costs today, plainly:** two transactions -- `settle`, then `expire` after
|
|
590
|
+
`responseWindowDaa` -- rather than one. A cooperative path would save one transaction fee and the
|
|
591
|
+
window. Whether a cooperative close is worth the 103 bytes it measures at is an open decision
|
|
592
|
+
rather than an oversight, and it is recorded here so a later version can take it up deliberately.
|
|
593
|
+
|
|
594
|
+
### 7.2 Unilateral close
|
|
595
|
+
|
|
596
|
+
Used when a counterparty is unresponsive or a party refuses to co-sign. Three paths:
|
|
597
|
+
|
|
598
|
+
**Two entry points, not three.** Phase 1's script budget found that three paths plus a
|
|
599
|
+
continuation state does not plausibly fit in 520 bytes, and that `supersede` need not be its own
|
|
600
|
+
entry — it is a guard on `settle`.
|
|
601
|
+
|
|
602
|
+
| Path | Who | What consensus enforces |
|
|
603
|
+
|---|---|---|
|
|
604
|
+
| `settle` | Either party with a doubly-signed State | Both signatures verify against `partiesCommitment` over the §3.4.1 preimage. **If a State is already pending, this one MUST carry a strictly higher `seq`** — this is supersede, as a guard rather than a branch. **`settle` pays no one.** It posts the claim: the single output returns the funds to this same covenant carrying `(seq, cumulativeSompi)` as state, and the response window restarts. |
|
|
605
|
+
| `expire` | Either party after the response window | Pays out the pending claim — `cumulativeSompi` to the provider, the remainder to the buyer. **If no claim was ever posted, refunds the buyer entirely.** |
|
|
606
|
+
|
|
607
|
+
**Settlement is two-phase, and it has to be.** An earlier draft of this table said `settle` "pays
|
|
608
|
+
exactly `cumulativeSompi` and the remainder" *and* that a later `settle` supersedes it. Those
|
|
609
|
+
cannot both hold: once the money is paid the covenant is spent and there is nothing left to
|
|
610
|
+
supersede. §7.5's "a stale close that has already been **accepted**" was always describing a
|
|
611
|
+
posted claim, not a completed payout. Found by writing
|
|
612
|
+
the covenant.
|
|
613
|
+
|
|
614
|
+
### 7.2a One covenant per transaction
|
|
615
|
+
|
|
616
|
+
**A closing transaction MUST spend exactly one covenant input.** `expire` enforces
|
|
617
|
+
`tx.inputs.length == 1`.
|
|
618
|
+
|
|
619
|
+
The reason is that `expire` pays to `P2PK(buyer)` and `P2PK(provider)`, and those scripts are not
|
|
620
|
+
session-specific. Two sessions between the same two parties therefore produce **identical output
|
|
621
|
+
scripts**, so one pair of outputs can satisfy both inputs' checks at once -- each input reads its
|
|
622
|
+
own `total` and each is separately satisfied, while only one payout exists.
|
|
623
|
+
|
|
624
|
+
Measured before the rule was added: two covenant inputs of 10,000,000 sompi each, closed with
|
|
625
|
+
outputs totalling 9,600,000, was **accepted**. 10,400,000 sompi -- more than half the money --
|
|
626
|
+
went to fee. Either party can sign such a transaction, so it is a griefing attack against whoever
|
|
627
|
+
has more at stake.
|
|
628
|
+
|
|
629
|
+
`settle` is not exposed the same way, because its single output is the continuation P2SH and that
|
|
630
|
+
script embeds the `sessionId`; two sessions cannot share one. The rule is nonetheless stated for
|
|
631
|
+
the whole scheme rather than for one entry, because the property being relied on is that **a
|
|
632
|
+
covenant accounts for every input carrying it**, and a future entry that pays to a
|
|
633
|
+
non-session-specific script would reintroduce this without warning.
|
|
634
|
+
|
|
635
|
+
Found by reading Argent's leader/delegate input-group invariants, whose Rule 3 requires exactly
|
|
636
|
+
this accounting.
|
|
637
|
+
|
|
638
|
+
### 7.3 The response window is relative, not absolute
|
|
639
|
+
|
|
640
|
+
`this.age` in SilverScript lowers to `OpCheckSequenceVerify`, which reads the spending input's
|
|
641
|
+
`sequence` field — **not** a current-DAA context. Therefore:
|
|
642
|
+
|
|
643
|
+
- `responseWindowDaa` is a **relative** delay, satisfied by setting the spending input's `sequence`.
|
|
644
|
+
- It MUST fit the low-32-bit encoding: `1..=4294967295`.
|
|
645
|
+
- The disabled bit (`1 << 63`) MUST be unset. Mask `0x00000000ffffffff`.
|
|
646
|
+
|
|
647
|
+
### 7.3a The window is the provider's DEADLINE
|
|
648
|
+
|
|
649
|
+
Because the delay is relative to the **covenant UTXO being spent**, the clock starts when the
|
|
650
|
+
covenant is funded and every `settle` restarts it by creating a fresh output. That has a
|
|
651
|
+
consequence §7.3's mechanics do not state, and it is the sharpest rule in this document:
|
|
652
|
+
|
|
653
|
+
**Once the covenant UTXO is older than `responseWindowDaa` and no claim is pending, the buyer can
|
|
654
|
+
`expire` and take back everything — including payment for work already delivered and already
|
|
655
|
+
agreed in doubly-signed States.**
|
|
656
|
+
|
|
657
|
+
**A provider MUST therefore post a claim before that deadline, and MUST choose
|
|
658
|
+
`responseWindowDaa` long enough to do so.** A provider that delivers for longer than the window
|
|
659
|
+
without settling is not protected by holding signed States; it is holding evidence of a debt the
|
|
660
|
+
chain will shortly release.
|
|
661
|
+
|
|
662
|
+
The provider is not otherwise exposed. It holds a doubly-signed State it can post at any moment,
|
|
663
|
+
and `seq > pendingSeq` is strict, so a stale claim can never overwrite a fresher one — the buyer's
|
|
664
|
+
only move is to get there **first**, and only while nothing is pending. The requirement is a
|
|
665
|
+
deadline, not a vigil.
|
|
666
|
+
|
|
667
|
+
**This is not the problem a Lightning watchtower solves, and MUST NOT be answered the same way.**
|
|
668
|
+
There, punishment is retrospective: an old state must be detected and answered with a justice
|
|
669
|
+
transaction, using per-update revocation secrets, by a wallet that is offline by nature — hence
|
|
670
|
+
delegation to a third party who must be trusted, can be bribed, and has to be paid. None of that
|
|
671
|
+
shape appears here. The party at risk is a server, online because serving is its business; it
|
|
672
|
+
needs only the latest State, which it already holds; there is no secret to store, no third party,
|
|
673
|
+
and nothing to delegate. **An implementation MUST NOT introduce one.**
|
|
674
|
+
|
|
675
|
+
Two settings bound the loss, and both are the provider's to choose:
|
|
676
|
+
|
|
677
|
+
| Setting | Bounds |
|
|
678
|
+
|---|---|
|
|
679
|
+
| post before the UTXO reaches a chosen age | how long an unposted claim may sit |
|
|
680
|
+
| post once unsettled value reaches a chosen amount | how much may accrue unposted |
|
|
681
|
+
|
|
682
|
+
The second is the provider's counterpart to the babel: with it, the worst a buyer can take by
|
|
683
|
+
waiting out the window is that amount. Without it, the bound is whatever the session can bill in
|
|
684
|
+
the time allowed, which is a choice too — just an implicit one.
|
|
685
|
+
|
|
686
|
+
**Each settle costs a transaction**, so a short window is safer and dearer. That trade is the
|
|
687
|
+
provider's, and it should be made with the numbers rather than by default.
|
|
688
|
+
|
|
689
|
+
### 7.4 Mutable covenant state is integers only
|
|
690
|
+
|
|
691
|
+
There is a NUM2BIN size cap on `byte[32]` state writes in the current compiler. Mutable covenant
|
|
692
|
+
state is therefore restricted to `seq` and `cumulativeSompi`, both integers. `partiesCommitment` is
|
|
693
|
+
a **constructor constant**, which is a different mechanism and is unaffected. `prevState` lives in
|
|
694
|
+
the off-chain message and is never written on-chain.
|
|
695
|
+
|
|
696
|
+
### 7.4a Dust, and the three shapes a close may take
|
|
697
|
+
|
|
698
|
+
Kaspa's KIP-9 prices an output by its reciprocal, and a transaction is refused when
|
|
699
|
+
|
|
700
|
+
10^12 / out_1 + 10^12 / out_2 - 10^12 / in > 500,000
|
|
701
|
+
|
|
702
|
+
The formula is the node's own, confirmed to within one sompi against a real rejection.
|
|
703
|
+
|
|
704
|
+
**THE FLOOR IS NOT A CONSTANT, and treating it as one strands funds.** An output is priced by its
|
|
705
|
+
reciprocal and the input's is subtracted, so what is payable depends on the OTHER output and on
|
|
706
|
+
how much the covenant holds. 2,000,000 is only the limit a very large transaction approaches.
|
|
707
|
+
`tools/dust-map.ts` walks every possible claim against the formula above and reports which of the
|
|
708
|
+
three shapes below, if any, consensus would accept. Against a threshold of 2,000,000 there is an
|
|
709
|
+
**unclosable window at every balance** -- claims for which the covenant demands an output the
|
|
710
|
+
network will not create, and the whole balance is stranded:
|
|
711
|
+
|
|
712
|
+
| Covenant holds | Claims with no legal close |
|
|
713
|
+
|---|---|
|
|
714
|
+
| 1 KAS | 2,000,000 .. 2,000,980 |
|
|
715
|
+
| 0.5 KAS | 2,000,000 .. 2,004,044 |
|
|
716
|
+
| 0.2 KAS | 2,000,000 .. 2,028,014 |
|
|
717
|
+
| 0.1 KAS | 2,000,000 .. 2,146,692 |
|
|
718
|
+
| 0.05 KAS | 2,000,000 .. 2,599,999 |
|
|
719
|
+
|
|
720
|
+
**So the threshold is 2,600,000, and the fold threshold 3,000,000** -- measured as the smallest
|
|
721
|
+
values that close the window at every balance §7.4b admits.
|
|
722
|
+
|
|
723
|
+
That is not a pricing inconvenience; it decides whether a session can be closed. A close
|
|
724
|
+
therefore has **exactly three legal shapes**, and an implementation MUST choose between them
|
|
725
|
+
rather than always emitting two outputs:
|
|
726
|
+
|
|
727
|
+
| Condition | Shape |
|
|
728
|
+
|---|---|
|
|
729
|
+
| `cumulativeSompi` < 2,600,000 | ONE output, everything to the **buyer**. Includes the no-claim case. |
|
|
730
|
+
| refund < 2,600,000 | ONE output, everything to the **provider**. Requires `cumulativeSompi + 3,000,000 >= total`. |
|
|
731
|
+
| both payable | TWO outputs: `cumulativeSompi` to the provider, the remainder to the buyer. |
|
|
732
|
+
|
|
733
|
+
**Whichever side's share is dust, the other side takes the lot.** Dust cannot be paid to anyone,
|
|
734
|
+
so folding it costs that party at most 0.02 KAS, and the alternative is locking the entire
|
|
735
|
+
balance. A provider SHOULD still price a session so the ordinary two-output close is reachable,
|
|
736
|
+
and MAY close cooperatively (§7.1) at any size, where an ordinary spend can pay whatever both
|
|
737
|
+
parties agree.
|
|
738
|
+
|
|
739
|
+
**The fold threshold is 2,400,000 rather than 2,000,000, and the difference is the fee.** The
|
|
740
|
+
refund is `total - cumulativeSompi - fee`, so a bound of 2,000,000 would admit refunds only up to
|
|
741
|
+
1,600,000 and leave everything between there and the dust floor with no legal shape at all --
|
|
742
|
+
another way to lock the balance. Adding the fee allowance closes that gap exactly.
|
|
743
|
+
|
|
744
|
+
This section is what a real session taught: three babels of ten words at 3,630 sompi earned
|
|
745
|
+
108,900, the State settled on chain, and the close was then impossible. The same session now
|
|
746
|
+
closes.
|
|
747
|
+
|
|
748
|
+
### 7.5 What settlement does not promise
|
|
749
|
+
|
|
750
|
+
A stale close that has already been **accepted** cannot be reversed by a later State. The response
|
|
751
|
+
window is the entire protection. A party offline for its duration loses. This is the honest state
|
|
752
|
+
of the art and matches Kurrent's stated non-claims; see §9.
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
### 7.4b The funding floor
|
|
757
|
+
|
|
758
|
+
**A buyer MUST fund the covenant with at least**
|
|
759
|
+
|
|
760
|
+
max( maxBabels x babelUnits x unitPriceSompi + closeFee , 10,000,000 sompi )
|
|
761
|
+
|
|
762
|
+
**The second term is a SHAPE rule, not a dust rule.** A two-output close needs both halves to
|
|
763
|
+
clear KIP-9 *together*, and for balances from 5,700,000 to 6,800,000 sompi they cannot -- at any
|
|
764
|
+
split, and whatever the dust threshold is set to. `tools/dust-map.ts` walks balances in
|
|
765
|
+
100,000-sompi steps and finds claims with no legal close at every balance in that band, and none
|
|
766
|
+
at 6,850,000 or above. 10,000,000 is the next round number, and is what this rule requires.
|
|
767
|
+
|
|
768
|
+
**and MUST NOT countersign a State whose `cumulativeSompi` exceeds what the covenant holds less
|
|
769
|
+
that fee.**
|
|
770
|
+
|
|
771
|
+
§7.2's `expire` pays the provider an output of **exactly** `pendingSompi`. If the parties have
|
|
772
|
+
signed a total the covenant cannot cover -- including the fee for the very transaction that pays
|
|
773
|
+
it -- then no valid close transaction exists, and the entire balance is stranded. It is Finding G
|
|
774
|
+
again, reached by agreement rather than by dust.
|
|
775
|
+
|
|
776
|
+
**The covenant cannot enforce this and no covenant could.** It would have to know the fee of a
|
|
777
|
+
transaction that has not been built yet, which is not available to a script at validation time.
|
|
778
|
+
The buyer can, because the buyer chooses the funding amount, and it can do so before spending
|
|
779
|
+
anything at all. This is why the rule is normative on the buyer rather than a line of script.
|
|
780
|
+
|
|
781
|
+
---
|
|
782
|
+
|
|
783
|
+
## 8. Checkpoints
|
|
784
|
+
|
|
785
|
+
Every `checkpointEvery` babels, `digest(State)` is anchored in a Kaspa lane at a measured cost of
|
|
786
|
+
0.002 KAS. Checkpointing MUST be **non-blocking** — the session continues while it confirms,
|
|
787
|
+
because measured p90 latency is 1,879 ms.
|
|
788
|
+
|
|
789
|
+
**Checkpoints are evidence, not safety.** They prove a State existed before a given block, which
|
|
790
|
+
makes a stale close provable and attributable. They cannot prevent one: the covenant cannot recover
|
|
791
|
+
a State from a digest, so it cannot enforce a minimum `seq`. Prevention is §7.2's job.
|
|
792
|
+
|
|
793
|
+
---
|
|
794
|
+
|
|
795
|
+
## 9. Non-goals
|
|
796
|
+
|
|
797
|
+
- **Hidden reasoning tokens.** Not two-sided measurable. Permanent.
|
|
798
|
+
- **Watchtowers.** A party offline for the response window can be closed against unfairly.
|
|
799
|
+
- **Quality.** The meter counts units delivered. Whether they were any good is verification.
|
|
800
|
+
- **Routing or multi-hop.** Bilateral only.
|
|
801
|
+
- **Discovery, reputation, marketplaces.**
|
|
802
|
+
- **Fiat, stablecoins, cross-chain.** Native KAS in this version.
|
|
803
|
+
- **Privacy.** Checkpoints are public; session size and cadence leak.
|
|
804
|
+
|
|
805
|
+
---
|
|
806
|
+
|
|
807
|
+
## 10. Worked example
|
|
808
|
+
|
|
809
|
+
`worked-example.txt` is generated by `vectors.ts` using the reference encoder
|
|
810
|
+
(`src/encoding.ts`), with real BLAKE3 digests and real BIP340 signatures. It is a **golden vector**: an implementation that produces different bytes for the same
|
|
811
|
+
inputs is wrong.
|
|
812
|
+
|
|
813
|
+
It runs a six-babel session in which babel 2 exhibits the one-token divergence Study A measured
|
|
814
|
+
(absorbed by `toleranceAbs`, billed at 549), a checkpoint fires after babel 2, and babel 4 diverges
|
|
815
|
+
by 48 units and halts. Settlement is at State 3: **2,199 units, 7,982,370 sompi, 3,996,630 sompi
|
|
816
|
+
refunded.**
|
|
817
|
+
|
|
818
|
+
Regenerate with `npm run vectors`. CI regenerates it on every push and requires a
|
|
819
|
+
byte-for-byte match.
|
|
820
|
+
|
|
821
|
+
---
|