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.
Files changed (206) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/contracts/metered.ag +135 -0
  4. package/contracts/metered_ag.tests.json +1226 -0
  5. package/contracts/metered_session.sil +237 -0
  6. package/contracts/metered_session.tests.json +1113 -0
  7. package/dist/contracts/metered.ag +135 -0
  8. package/dist/contracts/metered_ag.tests.json +1226 -0
  9. package/dist/contracts/metered_session.sil +237 -0
  10. package/dist/contracts/metered_session.tests.json +1113 -0
  11. package/dist/src/bias.d.ts +48 -0
  12. package/dist/src/bias.d.ts.map +1 -0
  13. package/dist/src/bias.js +43 -0
  14. package/dist/src/bias.js.map +1 -0
  15. package/dist/src/checkpoint.d.ts +57 -0
  16. package/dist/src/checkpoint.d.ts.map +1 -0
  17. package/dist/src/checkpoint.js +84 -0
  18. package/dist/src/checkpoint.js.map +1 -0
  19. package/dist/src/deadline.d.ts +71 -0
  20. package/dist/src/deadline.d.ts.map +1 -0
  21. package/dist/src/deadline.js +77 -0
  22. package/dist/src/deadline.js.map +1 -0
  23. package/dist/src/encoding.d.ts +72 -0
  24. package/dist/src/encoding.d.ts.map +1 -0
  25. package/dist/src/encoding.js +187 -0
  26. package/dist/src/encoding.js.map +1 -0
  27. package/dist/src/history.d.ts +33 -0
  28. package/dist/src/history.d.ts.map +1 -0
  29. package/dist/src/history.js +38 -0
  30. package/dist/src/history.js.map +1 -0
  31. package/dist/src/http/buyer.d.ts +57 -0
  32. package/dist/src/http/buyer.d.ts.map +1 -0
  33. package/dist/src/http/buyer.js +151 -0
  34. package/dist/src/http/buyer.js.map +1 -0
  35. package/dist/src/http/client.d.ts +55 -0
  36. package/dist/src/http/client.d.ts.map +1 -0
  37. package/dist/src/http/client.js +83 -0
  38. package/dist/src/http/client.js.map +1 -0
  39. package/dist/src/http/harness.d.ts +18 -0
  40. package/dist/src/http/harness.d.ts.map +1 -0
  41. package/dist/src/http/harness.js +16 -0
  42. package/dist/src/http/harness.js.map +1 -0
  43. package/dist/src/http/protocol.d.ts +84 -0
  44. package/dist/src/http/protocol.d.ts.map +1 -0
  45. package/dist/src/http/protocol.js +22 -0
  46. package/dist/src/http/protocol.js.map +1 -0
  47. package/dist/src/http/provider.d.ts +123 -0
  48. package/dist/src/http/provider.d.ts.map +1 -0
  49. package/dist/src/http/provider.js +197 -0
  50. package/dist/src/http/provider.js.map +1 -0
  51. package/dist/src/http/serve.d.ts +34 -0
  52. package/dist/src/http/serve.d.ts.map +1 -0
  53. package/dist/src/http/serve.js +171 -0
  54. package/dist/src/http/serve.js.map +1 -0
  55. package/dist/src/http/service.d.ts +86 -0
  56. package/dist/src/http/service.d.ts.map +1 -0
  57. package/dist/src/http/service.js +161 -0
  58. package/dist/src/http/service.js.map +1 -0
  59. package/dist/src/index.d.ts +68 -0
  60. package/dist/src/index.d.ts.map +1 -0
  61. package/dist/src/index.js +62 -0
  62. package/dist/src/index.js.map +1 -0
  63. package/dist/src/meter.d.ts +39 -0
  64. package/dist/src/meter.d.ts.map +1 -0
  65. package/dist/src/meter.js +100 -0
  66. package/dist/src/meter.js.map +1 -0
  67. package/dist/src/offer.d.ts +6 -0
  68. package/dist/src/offer.d.ts.map +1 -0
  69. package/dist/src/offer.js +92 -0
  70. package/dist/src/offer.js.map +1 -0
  71. package/dist/src/reconcile.d.ts +18 -0
  72. package/dist/src/reconcile.d.ts.map +1 -0
  73. package/dist/src/reconcile.js +101 -0
  74. package/dist/src/reconcile.js.map +1 -0
  75. package/dist/src/reservation.d.ts +51 -0
  76. package/dist/src/reservation.d.ts.map +1 -0
  77. package/dist/src/reservation.js +124 -0
  78. package/dist/src/reservation.js.map +1 -0
  79. package/dist/src/session.d.ts +40 -0
  80. package/dist/src/session.d.ts.map +1 -0
  81. package/dist/src/session.js +92 -0
  82. package/dist/src/session.js.map +1 -0
  83. package/dist/src/signer.d.ts +31 -0
  84. package/dist/src/signer.d.ts.map +1 -0
  85. package/dist/src/signer.js +57 -0
  86. package/dist/src/signer.js.map +1 -0
  87. package/dist/src/store.d.ts +30 -0
  88. package/dist/src/store.d.ts.map +1 -0
  89. package/dist/src/store.js +120 -0
  90. package/dist/src/store.js.map +1 -0
  91. package/dist/src/types.d.ts +80 -0
  92. package/dist/src/types.d.ts.map +1 -0
  93. package/dist/src/types.js +9 -0
  94. package/dist/src/types.js.map +1 -0
  95. package/dist/tools/anchor-cli.d.ts +2 -0
  96. package/dist/tools/anchor-cli.d.ts.map +1 -0
  97. package/dist/tools/anchor-cli.js +101 -0
  98. package/dist/tools/anchor-cli.js.map +1 -0
  99. package/dist/tools/anchor.d.ts +68 -0
  100. package/dist/tools/anchor.d.ts.map +1 -0
  101. package/dist/tools/anchor.js +136 -0
  102. package/dist/tools/anchor.js.map +1 -0
  103. package/dist/tools/chain.d.ts +18 -0
  104. package/dist/tools/chain.d.ts.map +1 -0
  105. package/dist/tools/chain.js +18 -0
  106. package/dist/tools/chain.js.map +1 -0
  107. package/dist/tools/conformance.d.ts +2 -0
  108. package/dist/tools/conformance.d.ts.map +1 -0
  109. package/dist/tools/conformance.js +220 -0
  110. package/dist/tools/conformance.js.map +1 -0
  111. package/dist/tools/contracts.d.ts +2 -0
  112. package/dist/tools/contracts.d.ts.map +1 -0
  113. package/dist/tools/contracts.js +68 -0
  114. package/dist/tools/contracts.js.map +1 -0
  115. package/dist/tools/covenant-binding.d.ts +40 -0
  116. package/dist/tools/covenant-binding.d.ts.map +1 -0
  117. package/dist/tools/covenant-binding.js +60 -0
  118. package/dist/tools/covenant-binding.js.map +1 -0
  119. package/dist/tools/covenant-profile.d.ts +41 -0
  120. package/dist/tools/covenant-profile.d.ts.map +1 -0
  121. package/dist/tools/covenant-profile.js +74 -0
  122. package/dist/tools/covenant-profile.js.map +1 -0
  123. package/dist/tools/covenant.d.ts +30 -0
  124. package/dist/tools/covenant.d.ts.map +1 -0
  125. package/dist/tools/covenant.js +54 -0
  126. package/dist/tools/covenant.js.map +1 -0
  127. package/dist/tools/demo-report.d.ts +24 -0
  128. package/dist/tools/demo-report.d.ts.map +1 -0
  129. package/dist/tools/demo-report.js +40 -0
  130. package/dist/tools/demo-report.js.map +1 -0
  131. package/dist/tools/demo.d.ts +2 -0
  132. package/dist/tools/demo.d.ts.map +1 -0
  133. package/dist/tools/demo.js +204 -0
  134. package/dist/tools/demo.js.map +1 -0
  135. package/dist/tools/dust-map.d.ts +2 -0
  136. package/dist/tools/dust-map.d.ts.map +1 -0
  137. package/dist/tools/dust-map.js +63 -0
  138. package/dist/tools/dust-map.js.map +1 -0
  139. package/dist/tools/expire-cases.d.ts +4 -0
  140. package/dist/tools/expire-cases.d.ts.map +1 -0
  141. package/dist/tools/expire-cases.js +211 -0
  142. package/dist/tools/expire-cases.js.map +1 -0
  143. package/dist/tools/fee-check.d.ts +2 -0
  144. package/dist/tools/fee-check.d.ts.map +1 -0
  145. package/dist/tools/fee-check.js +127 -0
  146. package/dist/tools/fee-check.js.map +1 -0
  147. package/dist/tools/fixtures.d.ts +39 -0
  148. package/dist/tools/fixtures.d.ts.map +1 -0
  149. package/dist/tools/fixtures.js +31 -0
  150. package/dist/tools/fixtures.js.map +1 -0
  151. package/dist/tools/gen-contract-tests.d.ts +2 -0
  152. package/dist/tools/gen-contract-tests.d.ts.map +1 -0
  153. package/dist/tools/gen-contract-tests.js +170 -0
  154. package/dist/tools/gen-contract-tests.js.map +1 -0
  155. package/dist/tools/kaspa.d.ts +13 -0
  156. package/dist/tools/kaspa.d.ts.map +1 -0
  157. package/dist/tools/kaspa.js +76 -0
  158. package/dist/tools/kaspa.js.map +1 -0
  159. package/dist/tools/live-deadline.d.ts +2 -0
  160. package/dist/tools/live-deadline.d.ts.map +1 -0
  161. package/dist/tools/live-deadline.js +122 -0
  162. package/dist/tools/live-deadline.js.map +1 -0
  163. package/dist/tools/live-expire.d.ts +2 -0
  164. package/dist/tools/live-expire.d.ts.map +1 -0
  165. package/dist/tools/live-expire.js +127 -0
  166. package/dist/tools/live-expire.js.map +1 -0
  167. package/dist/tools/live-settle.d.ts +2 -0
  168. package/dist/tools/live-settle.d.ts.map +1 -0
  169. package/dist/tools/live-settle.js +226 -0
  170. package/dist/tools/live-settle.js.map +1 -0
  171. package/dist/tools/live-steps.d.ts +63 -0
  172. package/dist/tools/live-steps.d.ts.map +1 -0
  173. package/dist/tools/live-steps.js +155 -0
  174. package/dist/tools/live-steps.js.map +1 -0
  175. package/dist/tools/model.d.ts +32 -0
  176. package/dist/tools/model.d.ts.map +1 -0
  177. package/dist/tools/model.js +99 -0
  178. package/dist/tools/model.js.map +1 -0
  179. package/dist/tools/send.d.ts +2 -0
  180. package/dist/tools/send.d.ts.map +1 -0
  181. package/dist/tools/send.js +41 -0
  182. package/dist/tools/send.js.map +1 -0
  183. package/dist/tools/session-chain.d.ts +69 -0
  184. package/dist/tools/session-chain.d.ts.map +1 -0
  185. package/dist/tools/session-chain.js +113 -0
  186. package/dist/tools/session-chain.js.map +1 -0
  187. package/dist/tools/sighash.d.ts +9 -0
  188. package/dist/tools/sighash.d.ts.map +1 -0
  189. package/dist/tools/sighash.js +68 -0
  190. package/dist/tools/sighash.js.map +1 -0
  191. package/dist/tools/sigscript-check.d.ts +2 -0
  192. package/dist/tools/sigscript-check.d.ts.map +1 -0
  193. package/dist/tools/sigscript-check.js +113 -0
  194. package/dist/tools/sigscript-check.js.map +1 -0
  195. package/dist/tools/sigscript.d.ts +61 -0
  196. package/dist/tools/sigscript.d.ts.map +1 -0
  197. package/dist/tools/sigscript.js +70 -0
  198. package/dist/tools/sigscript.js.map +1 -0
  199. package/dist/tools/state-layout.d.ts +2 -0
  200. package/dist/tools/state-layout.d.ts.map +1 -0
  201. package/dist/tools/state-layout.js +57 -0
  202. package/dist/tools/state-layout.js.map +1 -0
  203. package/package.json +59 -0
  204. package/spec/CONFORMANCE.md +80 -0
  205. package/spec/SPEC.md +821 -0
  206. package/spec/conformance-vectors.json +677 -0
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Canonical encoding, digests and signatures for Metered.
3
+ *
4
+ * Vendored deliberately rather than imported. The golden vector was previously generated by
5
+ * kaspa-depin's protocol package, which meant CI could not regenerate it and the specification's
6
+ * most foundational claims were unverifiable from this repository. Two bugs had already survived
7
+ * that gap; a third was found while writing this file.
8
+ *
9
+ * ONE HASH, EVERYWHERE. The borrowed code used BLAKE2b for message digests and BLAKE3 for content
10
+ * addressing, and the specification said "digests are blake3" about both. An implementer following
11
+ * the specification would have produced different `prevState` values and a different checkpoint
12
+ * digest -- total incompatibility, from one sentence. Rather than document the accident, this
13
+ * settles it: BLAKE3-256 for everything. The specification is now true.
14
+ */
15
+ import { blake3 } from '@noble/hashes/blake3';
16
+ import { schnorr } from '@noble/curves/secp256k1';
17
+ import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/hashes/utils';
18
+ /**
19
+ * RFC 8785 (JCS) canonical JSON.
20
+ *
21
+ * Keys sort by UTF-16 code unit, which is what JCS mandates and what JavaScript's default sort
22
+ * does. This is NOT Unicode code-point order -- they diverge above the BMP, where surrogate pairs
23
+ * sort below U+E000-FFFF. The specification said code point until a check caught it.
24
+ */
25
+ export function canonicalize(value) {
26
+ if (value === null || typeof value === 'boolean')
27
+ return JSON.stringify(value);
28
+ if (typeof value === 'number') {
29
+ if (!Number.isFinite(value))
30
+ throw new Error(`canonicalize: non-finite number ${value}`);
31
+ return JSON.stringify(value);
32
+ }
33
+ if (typeof value === 'string')
34
+ return JSON.stringify(value);
35
+ if (Array.isArray(value))
36
+ return `[${value.map(canonicalize).join(',')}]`;
37
+ if (typeof value === 'object')
38
+ return canonicalizeObject(value);
39
+ throw new Error(`canonicalize: unsupported value of type ${typeof value}`);
40
+ }
41
+ function canonicalizeObject(value) {
42
+ const keys = Object.keys(value)
43
+ .filter((k) => value[k] !== undefined)
44
+ .sort(); // default sort is UTF-16 code unit order, which is what JCS requires
45
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalize(value[k])}`).join(',')}}`;
46
+ }
47
+ /** BLAKE3-256 of raw bytes or a UTF-8 string, hex. */
48
+ /**
49
+ * Text as the bytes that represent it.
50
+ *
51
+ * Delivered content is bytes (SPEC.md 6), so anything that starts life as a string has to become
52
+ * bytes at some boundary. This is that boundary, named once, so no caller invents its own.
53
+ */
54
+ export const utf8 = (text) => utf8ToBytes(text);
55
+ export function blake3Hex(input) {
56
+ const bytes = typeof input === 'string' ? utf8ToBytes(input) : input;
57
+ return bytesToHex(blake3(bytes, { dkLen: 32 }));
58
+ }
59
+ /**
60
+ * SPEC.md 3.1's `partiesCommitment`: blake3 over the two public keys, as BYTES.
61
+ *
62
+ * THE ONE DEFINITION, because there were two and they disagreed. "blake3(buyerPubkey || providerPubkey)"
63
+ * never said whether the concatenation joins the 64 raw key bytes or the 128 characters of hex
64
+ * that represent them, and those produce different digests. The live provider hashed the bytes;
65
+ * the conformance generator hashed the hex text. Both were correct for as long as nothing
66
+ * compared them -- and consensus settles the argument, because the covenant recomputes
67
+ * blake3(byte[](buyer) + byte[](provider)) and will not pay out against anything else.
68
+ *
69
+ * So the bytes reading is normative, and this is the only place it is computed.
70
+ */
71
+ export function partiesCommitment(buyerPubkey, providerPubkey) {
72
+ return blake3Hex(new Uint8Array([...hexToBytes(buyerPubkey), ...hexToBytes(providerPubkey)]));
73
+ }
74
+ /** BLAKE3-256 of the canonical form of a value, hex. The only digest this protocol uses. */
75
+ export function digestHex(value) {
76
+ return blake3Hex(canonicalize(value));
77
+ }
78
+ export function publicKeyHex(privateKeyHex) {
79
+ return bytesToHex(schnorr.getPublicKey(hexToBytes(privateKeyHex)));
80
+ }
81
+ /**
82
+ * Sign a message. The signature covers the canonical form with the signature field ABSENT --
83
+ * not present-and-empty, which is a different byte string and a classic source of divergence.
84
+ */
85
+ export function sign(message, privateKeyHex, field = 'sig') {
86
+ const payload = withoutField(message, field);
87
+ return bytesToHex(schnorr.sign(utf8ToBytes(canonicalize(payload)), hexToBytes(privateKeyHex)));
88
+ }
89
+ export function verify(message, publicKeyHexStr, field = 'sig') {
90
+ const sig = message[field];
91
+ if (typeof sig !== 'string')
92
+ return false;
93
+ try {
94
+ const payload = withoutField(message, field);
95
+ return schnorr.verify(hexToBytes(sig), utf8ToBytes(canonicalize(payload)), hexToBytes(publicKeyHexStr));
96
+ }
97
+ catch {
98
+ return false;
99
+ }
100
+ }
101
+ /** Attach a signature under `field`, leaving the original untouched. */
102
+ export function signEnvelope(message, privateKeyHex, field = 'sig') {
103
+ return { ...message, [field]: sign(message, privateKeyHex, field) };
104
+ }
105
+ /**
106
+ * Kaspa script number, 8 bytes: little-endian magnitude with the sign in the top bit of the last
107
+ * byte. This is what `OpNum2Bin(x, 8)` produces, and the covenant rebuilds the preimage with it,
108
+ * so any disagreement here is a signature the chain cannot verify.
109
+ */
110
+ function num2bin8(value) {
111
+ if (!Number.isSafeInteger(value))
112
+ throw new Error(`num2bin8: ${value} is not a safe integer`);
113
+ const out = new Uint8Array(8);
114
+ let magnitude = Math.abs(value);
115
+ for (let i = 0; i < 8; i += 1) {
116
+ out[i] = magnitude % 256;
117
+ magnitude = Math.floor(magnitude / 256);
118
+ }
119
+ // The index is in range by construction (out is a fixed 8-byte array), but the typechecker
120
+ // cannot know that and noUncheckedIndexedAccess is worth keeping -- out-of-bounds is precisely
121
+ // the family of bug this project keeps finding. Written so the compiler can see it too.
122
+ if (value < 0)
123
+ out[7] = (out[7] ?? 0) | 0x80;
124
+ return out;
125
+ }
126
+ /**
127
+ * SPEC.md 3.4.1 -- the settlement preimage. 72 bytes, fixed width, fixed order, no delimiters:
128
+ * sessionId(16) + seq(8) + cumulativeUnits(8) + cumulativeSompi(8) + prevState(32).
129
+ *
130
+ * These 72 bytes are unique to a session only if the sessionId is. It is provider-chosen, so that
131
+ * is not a property of this function -- SPEC.md 3.1a puts the obligation on the buyer to reject a
132
+ * sessionId it has seen before, which src/offer.ts enforces.
133
+ *
134
+ * A State is the ONE signed object here whose signatures do not cover its canonical JSON. They
135
+ * cannot: the covenant must reconstruct what it verifies and a Kaspa script cannot build canonical
136
+ * JSON. Signing the JSON form yields a signature two peers accept and the chain cannot check --
137
+ * a session that can never settle.
138
+ *
139
+ * `prevState` is 32 zero bytes at seq 0, where the JSON carries null. A null has no byte form.
140
+ */
141
+ export function settlementPreimage(state) {
142
+ const out = new Uint8Array(72);
143
+ out.set(hexToBytes(state.sessionId), 0);
144
+ out.set(num2bin8(state.seq), 16);
145
+ out.set(num2bin8(state.cumulativeUnits), 24);
146
+ out.set(num2bin8(state.cumulativeSompi), 32);
147
+ const prev = state.prevState;
148
+ if (prev)
149
+ out.set(hexToBytes(prev), 40);
150
+ return out;
151
+ }
152
+ /**
153
+ * The 32 bytes both parties actually sign: blake3 of the preimage. The covenant computes exactly
154
+ * this and hands it to `checkMsgSig`, which verifies a BIP340 signature over supplied data rather
155
+ * than over the spending transaction.
156
+ *
157
+ * Neither signature field appears in the preimage at all, so the two parties sign identical bytes
158
+ * and neither depends on the other having signed first. The old "canonical JSON with both fields
159
+ * absent" rule is gone; there is nothing left to omit.
160
+ */
161
+ export function settlementDigest(state) {
162
+ return blake3(settlementPreimage(state), { dkLen: 32 });
163
+ }
164
+ /** Hex of the preimage. A string, so it can be compared, logged and pinned in a vector. */
165
+ export function stateSigningPayload(state) {
166
+ return bytesToHex(settlementPreimage(state));
167
+ }
168
+ export function signState(state, privateKeyHex) {
169
+ return bytesToHex(schnorr.sign(settlementDigest(state), hexToBytes(privateKeyHex)));
170
+ }
171
+ export function verifyState(state, sigHex, publicKeyHexStr) {
172
+ try {
173
+ const digest = settlementDigest(state);
174
+ return schnorr.verify(hexToBytes(sigHex), digest, hexToBytes(publicKeyHexStr));
175
+ }
176
+ catch {
177
+ return false;
178
+ }
179
+ }
180
+ function withoutField(obj, field) {
181
+ const copy = {};
182
+ for (const [k, v] of Object.entries(obj))
183
+ if (k !== field)
184
+ copy[k] = v;
185
+ return copy;
186
+ }
187
+ //# sourceMappingURL=encoding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding.js","sourceRoot":"","sources":["../../src/encoding.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * SPEC.md §3.1a -- the buyer's record of which session identifiers it has already been offered.
3
+ *
4
+ * WHY THIS IS NOT A FIELD WIDTH. `sessionId` is provider-CHOSEN. Every threat its width appears
5
+ * to address -- accidental collision, an outsider guessing or grinding one -- is a threat the
6
+ * provider is not subject to, because it does not guess its own identifiers. It picks them, and
7
+ * picking a previous one costs nothing.
8
+ *
9
+ * A reused `sessionId` makes the §3.4.1 preimage of two different sessions identical, so the
10
+ * buyer's signature from the first is a valid signature for the second. That is threat X1 executed
11
+ * by the one party no width defends against, and no on-chain rule can see it either: the covenant
12
+ * is instantiated per session and cannot observe the other one. The buyer is the only party
13
+ * positioned to catch it, and only by remembering.
14
+ *
15
+ * Synchronous by design, for the same reason SignerStore is (§4.2): an async check invites a
16
+ * caller to forget the await, which silently turns a refusal into an acceptance.
17
+ */
18
+ /** Durable storage for §3.1a. Keyed by provider: two providers may pick the same id honestly. */
19
+ export interface SessionHistory {
20
+ seen(providerPubkey: string, sessionId: string): boolean;
21
+ record(providerPubkey: string, sessionId: string): void;
22
+ }
23
+ /**
24
+ * The default history: correct for the life of a process, and FORGETFUL ACROSS RESTART.
25
+ *
26
+ * SPEC.md §3.1a requires an implementation that keeps no history to say so, and this is that
27
+ * disclosure: a buyer using this is protected against a provider reusing an identifier during one
28
+ * run, and unprotected against one that waits for the buyer to restart. It is the default because
29
+ * a default of "no check at all" would make the rule an intention rather than a mechanism; a buyer
30
+ * that needs the real guarantee supplies a store that survives restart.
31
+ */
32
+ export declare function memoryHistory(): SessionHistory;
33
+ //# sourceMappingURL=history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,iGAAiG;AACjG,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IACzD,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,IAAI,cAAc,CAU9C"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * SPEC.md §3.1a -- the buyer's record of which session identifiers it has already been offered.
3
+ *
4
+ * WHY THIS IS NOT A FIELD WIDTH. `sessionId` is provider-CHOSEN. Every threat its width appears
5
+ * to address -- accidental collision, an outsider guessing or grinding one -- is a threat the
6
+ * provider is not subject to, because it does not guess its own identifiers. It picks them, and
7
+ * picking a previous one costs nothing.
8
+ *
9
+ * A reused `sessionId` makes the §3.4.1 preimage of two different sessions identical, so the
10
+ * buyer's signature from the first is a valid signature for the second. That is threat X1 executed
11
+ * by the one party no width defends against, and no on-chain rule can see it either: the covenant
12
+ * is instantiated per session and cannot observe the other one. The buyer is the only party
13
+ * positioned to catch it, and only by remembering.
14
+ *
15
+ * Synchronous by design, for the same reason SignerStore is (§4.2): an async check invites a
16
+ * caller to forget the await, which silently turns a refusal into an acceptance.
17
+ */
18
+ /**
19
+ * The default history: correct for the life of a process, and FORGETFUL ACROSS RESTART.
20
+ *
21
+ * SPEC.md §3.1a requires an implementation that keeps no history to say so, and this is that
22
+ * disclosure: a buyer using this is protected against a provider reusing an identifier during one
23
+ * run, and unprotected against one that waits for the buyer to restart. It is the default because
24
+ * a default of "no check at all" would make the rule an intention rather than a mechanism; a buyer
25
+ * that needs the real guarantee supplies a store that survives restart.
26
+ */
27
+ export function memoryHistory() {
28
+ const byProvider = new Map();
29
+ return {
30
+ seen: (providerPubkey, sessionId) => byProvider.get(providerPubkey)?.has(sessionId) ?? false,
31
+ record: (providerPubkey, sessionId) => {
32
+ const ids = byProvider.get(providerPubkey) ?? new Set();
33
+ ids.add(sessionId);
34
+ byProvider.set(providerPubkey, ids);
35
+ },
36
+ };
37
+ }
38
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAQH;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,UAAU,GAAG,IAAI,GAAG,EAAuB,CAAC;IAClD,OAAO;QACL,IAAI,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,GAAG,CAAC,SAAS,CAAC,IAAI,KAAK;QAC5F,MAAM,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,EAAE;YACpC,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,IAAI,GAAG,EAAU,CAAC;YAChE,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACnB,UAAU,CAAC,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QACtC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { type SignerStore } from '../signer.js';
2
+ import { type SessionHistory } from '../history.js';
3
+ import type { Measurement, Offer, Reservation, State } from '../types.js';
4
+ import type { Meter } from './provider.js';
5
+ export declare class BuyerRefused extends Error {
6
+ }
7
+ export declare class BuyerSession {
8
+ readonly offer: Offer;
9
+ private readonly buyerSk;
10
+ private readonly meter;
11
+ /** SPEC.md 4, and the same warning as the provider's: the default forgets on restart. */
12
+ private readonly store;
13
+ /**
14
+ * SPEC.md 7.4b. What the covenant actually holds, if this buyer funded one. Supplying it lets
15
+ * the buyer refuse to agree a total the covenant could never pay out -- see `countersign`.
16
+ */
17
+ private readonly fundedSompi?;
18
+ private seq;
19
+ private cumulativeUnits;
20
+ private cumulativeSompi;
21
+ private lastStateDigest;
22
+ private bias;
23
+ constructor(offer: Offer, buyerSk: string, meter: Meter, expectedNetwork?: string,
24
+ /**
25
+ * SPEC.md 3.1a. Defaults to a history that remembers nothing before this session, which
26
+ * catches nothing -- a single session cannot repeat an identifier. A buyer that runs more
27
+ * than one session against a provider MUST pass a history that outlives them all.
28
+ */
29
+ history?: SessionHistory,
30
+ /** SPEC.md 4, and the same warning as the provider's: the default forgets on restart. */
31
+ store?: SignerStore,
32
+ /**
33
+ * SPEC.md 7.4b. What the covenant actually holds, if this buyer funded one. Supplying it lets
34
+ * the buyer refuse to agree a total the covenant could never pay out -- see `countersign`.
35
+ */
36
+ fundedSompi?: number | undefined);
37
+ get spentSompi(): number;
38
+ /** Authorise exactly one chunk. Never the session (SPEC.md 3.2). */
39
+ reserve(units?: number): Reservation;
40
+ /**
41
+ * Count the delivered content and produce the buyer's Measurement.
42
+ *
43
+ * The provider's Measurement is checked here for signature and content digest, but its COUNT is
44
+ * not adopted -- that is the point. A mismatched digest means the two sides are describing
45
+ * different bytes, and no amount of counting can reconcile that (SPEC.md 5 rule 3).
46
+ */
47
+ measure(content: Uint8Array, providerMeasurement: Measurement, seq: number): Measurement;
48
+ /**
49
+ * Check the State the provider proposes, and countersign it if it is right.
50
+ *
51
+ * RECOMPUTED, NEVER TRUSTED. The amount is derived from the Offer's price and the agreed units,
52
+ * not read off the wire -- a buyer that signs whatever `cumulativeSompi` arrives has handed the
53
+ * provider a blank cheque with a signature already on it.
54
+ */
55
+ countersign(state: State, providerSig: string, myUnits: number): string;
56
+ }
57
+ //# sourceMappingURL=buyer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buyer.d.ts","sourceRoot":"","sources":["../../../src/http/buyer.ts"],"names":[],"mappings":"AAWA,OAAO,EAAyC,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAEvF,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAInE,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAE3C,qBAAa,YAAa,SAAQ,KAAK;CAAG;AAE1C,qBAAa,YAAY;IAQrB,QAAQ,CAAC,KAAK,EAAE,KAAK;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAQtB,yFAAyF;IACzF,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAvB/B,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,IAAI,CAA6B;gBAG9B,KAAK,EAAE,KAAK,EACJ,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,KAAK,EAC7B,eAAe,CAAC,EAAE,MAAM;IACxB;;;;OAIG;IACH,OAAO,GAAE,cAAgC;IACzC,yFAAyF;IACxE,KAAK,GAAE,WAA2B;IACnD;;;OAGG;IACc,WAAW,CAAC,EAAE,MAAM,YAAA;IAKvC,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,oEAAoE;IACpE,OAAO,CAAC,KAAK,SAAwB,GAAG,WAAW;IAenD;;;;;;OAMG;IACH,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW;IA0BxF;;;;;;OAMG;IACH,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;CA0CxE"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * The buyer's half of a metered session.
3
+ *
4
+ * WHAT THE BUYER IS TRUSTED FOR: nothing either (SPEC.md 1). It counts what it actually received
5
+ * and signs that. If its count and the provider's disagree past tolerance the session stops --
6
+ * there is no arbiter, and "the only remedy for disagreement is to stop."
7
+ *
8
+ * THE BUYER COUNTS THE BYTES IT WAS GIVEN, not the ones it was told about. That sentence is the
9
+ * whole product. Everything else here is bookkeeping around it.
10
+ */
11
+ import { blake3Hex, digestHex, signEnvelope, verify, verifyState } from '../encoding.js';
12
+ import { signStateWithObligations, memoryStore } from '../signer.js';
13
+ import { acceptOffer } from '../offer.js';
14
+ import { memoryHistory } from '../history.js';
15
+ import { priceOf, CLOSE_FEE_SOMPI } from '../reservation.js';
16
+ import { newBiasState, observeResidual, biasAlarm } from '../bias.js';
17
+ import { toleranceBound } from '../reconcile.js';
18
+ export class BuyerRefused extends Error {
19
+ }
20
+ export class BuyerSession {
21
+ offer;
22
+ buyerSk;
23
+ meter;
24
+ store;
25
+ fundedSompi;
26
+ seq = -1;
27
+ cumulativeUnits = 0;
28
+ cumulativeSompi = 0;
29
+ lastStateDigest = null;
30
+ bias = newBiasState();
31
+ constructor(offer, buyerSk, meter, expectedNetwork,
32
+ /**
33
+ * SPEC.md 3.1a. Defaults to a history that remembers nothing before this session, which
34
+ * catches nothing -- a single session cannot repeat an identifier. A buyer that runs more
35
+ * than one session against a provider MUST pass a history that outlives them all.
36
+ */
37
+ history = memoryHistory(),
38
+ /** SPEC.md 4, and the same warning as the provider's: the default forgets on restart. */
39
+ store = memoryStore(),
40
+ /**
41
+ * SPEC.md 7.4b. What the covenant actually holds, if this buyer funded one. Supplying it lets
42
+ * the buyer refuse to agree a total the covenant could never pay out -- see `countersign`.
43
+ */
44
+ fundedSompi) {
45
+ this.offer = offer;
46
+ this.buyerSk = buyerSk;
47
+ this.meter = meter;
48
+ this.store = store;
49
+ this.fundedSompi = fundedSompi;
50
+ acceptOffer(offer, expectedNetwork, history);
51
+ }
52
+ get spentSompi() {
53
+ return this.cumulativeSompi;
54
+ }
55
+ /** Authorise exactly one chunk. Never the session (SPEC.md 3.2). */
56
+ reserve(units = this.offer.babelUnits) {
57
+ if (units > this.offer.babelUnits)
58
+ throw new BuyerRefused('refusing to reserve more than one chunk');
59
+ const next = this.seq + 1;
60
+ const reservation = {
61
+ v: 1,
62
+ sessionId: this.offer.sessionId,
63
+ seq: next,
64
+ units,
65
+ cumulativeUnits: this.cumulativeUnits + units,
66
+ cumulativeSompi: this.cumulativeSompi + priceOf(this.offer, units),
67
+ prevState: this.lastStateDigest,
68
+ };
69
+ return signEnvelope(reservation, this.buyerSk);
70
+ }
71
+ /**
72
+ * Count the delivered content and produce the buyer's Measurement.
73
+ *
74
+ * The provider's Measurement is checked here for signature and content digest, but its COUNT is
75
+ * not adopted -- that is the point. A mismatched digest means the two sides are describing
76
+ * different bytes, and no amount of counting can reconcile that (SPEC.md 5 rule 3).
77
+ */
78
+ measure(content, providerMeasurement, seq) {
79
+ if (!verify(providerMeasurement, this.offer.providerPubkey)) {
80
+ throw new BuyerRefused('the provider Measurement does not verify');
81
+ }
82
+ const contentDigest = blake3Hex(content);
83
+ if (providerMeasurement.contentDigest !== contentDigest) {
84
+ throw new BuyerRefused('the provider is describing different bytes than it delivered');
85
+ }
86
+ const units = this.meter(content);
87
+ const bound = toleranceBound(this.offer, providerMeasurement.units);
88
+ if (Math.abs(units - providerMeasurement.units) > bound) {
89
+ throw new BuyerRefused(`counts differ by more than ${bound}: ${units} vs ${providerMeasurement.units}`);
90
+ }
91
+ const measurement = {
92
+ v: 1,
93
+ sessionId: this.offer.sessionId,
94
+ seq,
95
+ by: 'buyer',
96
+ units,
97
+ cumulativeUnits: this.cumulativeUnits + units,
98
+ contentDigest,
99
+ measurementId: blake3Hex(`${this.offer.sessionId}/${seq}/buyer`).slice(0, 32),
100
+ };
101
+ return signEnvelope(measurement, this.buyerSk);
102
+ }
103
+ /**
104
+ * Check the State the provider proposes, and countersign it if it is right.
105
+ *
106
+ * RECOMPUTED, NEVER TRUSTED. The amount is derived from the Offer's price and the agreed units,
107
+ * not read off the wire -- a buyer that signs whatever `cumulativeSompi` arrives has handed the
108
+ * provider a blank cheque with a signature already on it.
109
+ */
110
+ countersign(state, providerSig, myUnits) {
111
+ if (state.sessionId !== this.offer.sessionId)
112
+ throw new BuyerRefused('State is for another session');
113
+ if (state.seq !== this.seq + 1)
114
+ throw new BuyerRefused(`State seq ${state.seq} does not follow ${this.seq}`);
115
+ if (state.prevState !== this.lastStateDigest)
116
+ throw new BuyerRefused('State does not chain to the last one');
117
+ const expected = state.cumulativeUnits * this.offer.unitPriceSompi;
118
+ if (state.cumulativeSompi !== expected) {
119
+ throw new BuyerRefused(`State bills ${state.cumulativeSompi}, recomputed ${expected}`);
120
+ }
121
+ if (state.cumulativeUnits > this.cumulativeUnits + myUnits) {
122
+ throw new BuyerRefused('State bills more units than the buyer counted');
123
+ }
124
+ // SPEC.md 7.4b. `expire` pays the provider EXACTLY `pendingSompi`, so a total the covenant
125
+ // cannot cover -- including the fee for the transaction that pays it -- has NO valid close,
126
+ // and the entire balance is stranded. The covenant cannot check this itself: it would need to
127
+ // know the fee of a transaction that does not exist yet. The buyer can, and must.
128
+ if (this.fundedSompi !== undefined) {
129
+ const payable = this.fundedSompi - CLOSE_FEE_SOMPI;
130
+ if (state.cumulativeSompi > payable) {
131
+ throw new BuyerRefused(`State bills ${state.cumulativeSompi}, above the ${payable} this covenant can pay out ` +
132
+ `(funded ${this.fundedSompi} less the ${CLOSE_FEE_SOMPI} close fee) -- SPEC.md 7.4b`);
133
+ }
134
+ }
135
+ if (!verifyState(state, providerSig, this.offer.providerPubkey)) {
136
+ throw new BuyerRefused('the provider signature over the State does not verify');
137
+ }
138
+ // SPEC.md 5.1.1, buyer side. The provider can ride the tolerance too.
139
+ this.bias = observeResidual(this.bias, state.cumulativeUnits - (this.cumulativeUnits + myUnits));
140
+ if (biasAlarm(this.bias))
141
+ throw new BuyerRefused('bias detector tripped -- halting the session');
142
+ this.seq = state.seq;
143
+ this.cumulativeUnits = state.cumulativeUnits;
144
+ this.cumulativeSompi = state.cumulativeSompi;
145
+ this.lastStateDigest = digestHex(state);
146
+ // SPEC.md 4, buyer side. The obligations are symmetric: a buyer that signs two States at one
147
+ // seq has handed the provider a choice of which to settle, and it will not choose the cheaper.
148
+ return signStateWithObligations(this.store, state, this.buyerSk);
149
+ }
150
+ }
151
+ //# sourceMappingURL=buyer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buyer.js","sourceRoot":"","sources":["../../../src/http/buyer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * A buyer that drives a metered session over HTTP, start to finish.
3
+ *
4
+ * This is the reference client: read the 402, decide whether the terms are acceptable, then run
5
+ * chunks until the work is done or something disagrees. It is short because every decision it
6
+ * makes lives in buyer.ts, which is where the rules are.
7
+ */
8
+ import { digestHex } from '../encoding.js';
9
+ import { BuyerSession } from './buyer.js';
10
+ import type { SessionHistory } from '../history.js';
11
+ import type { Meter } from './provider.js';
12
+ import type { Offer, State } from '../types.js';
13
+ export declare class ProtocolError extends Error {
14
+ }
15
+ /**
16
+ * Fetch the 402 and take the Offer out of it. The 402 is the answer, not a failure.
17
+ *
18
+ * The buyer's key goes UP with the request because the Offer commits to
19
+ * blake3(buyer || provider); a provider cannot sign terms for a buyer it has not been told about.
20
+ */
21
+ export declare function readOffer(base: string, buyerPubkey: string): Promise<Offer>;
22
+ export interface ChunkOutcome {
23
+ content: Uint8Array;
24
+ state: State;
25
+ billedUnits: number;
26
+ /**
27
+ * The two signatures over this State's 72-byte preimage.
28
+ *
29
+ * Returned because SETTLEMENT NEEDS THEM and nothing else could reach them. The covenant's
30
+ * `settle` entry takes both, and a caller holding only the State holds a number it cannot
31
+ * enforce -- which was true of every caller of this function until a product tried to settle.
32
+ */
33
+ providerSig: string;
34
+ buyerSig: string;
35
+ }
36
+ /**
37
+ * Run one chunk: reserve, receive, count, reconcile, countersign.
38
+ *
39
+ * Two round trips, because the buyer cannot honestly sign a count for content it has not seen.
40
+ */
41
+ export declare function runBabel(base: string, session: BuyerSession, prompt: string, units?: number): Promise<ChunkOutcome>;
42
+ /**
43
+ * Open a session against a server and hand back the buyer half, ready to run chunks.
44
+ *
45
+ * `history` is SPEC.md 3.1a's obligation and belongs to the CALLER, because it has to outlive any
46
+ * one session to mean anything: a buyer opening many sessions against one provider creates the
47
+ * history once and passes it to every call. Omitting it leaves that buyer unprotected against a
48
+ * provider that reuses a session identifier deliberately.
49
+ */
50
+ export declare function openSession(base: string, buyerSk: string, meter: Meter, expectedNetwork?: string, history?: SessionHistory): Promise<{
51
+ offer: Offer;
52
+ session: BuyerSession;
53
+ }>;
54
+ export { digestHex };
55
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/http/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,SAAS,EAAgB,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEhD,qBAAa,aAAc,SAAQ,KAAK;CAAG;AAa3C;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAWjF;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,UAAU,CAAC;IACpB,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,YAAY,CAAC,CAoBvB;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,KAAK,EACZ,eAAe,CAAC,EAAE,MAAM,EACxB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC,CAMlD;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * A buyer that drives a metered session over HTTP, start to finish.
3
+ *
4
+ * This is the reference client: read the 402, decide whether the terms are acceptable, then run
5
+ * chunks until the work is done or something disagrees. It is short because every decision it
6
+ * makes lives in buyer.ts, which is where the rules are.
7
+ */
8
+ import { digestHex, publicKeyHex } from '../encoding.js';
9
+ import { BuyerSession } from './buyer.js';
10
+ import { fromBase64 } from './protocol.js';
11
+ export class ProtocolError extends Error {
12
+ }
13
+ async function post(base, path, body) {
14
+ const res = await fetch(`${base}${path}`, {
15
+ method: 'POST',
16
+ headers: { 'content-type': 'application/json' },
17
+ body: JSON.stringify(body),
18
+ });
19
+ const text = await res.text();
20
+ if (!res.ok)
21
+ throw new ProtocolError(`${path} -> ${res.status} ${text}`);
22
+ return JSON.parse(text);
23
+ }
24
+ /**
25
+ * Fetch the 402 and take the Offer out of it. The 402 is the answer, not a failure.
26
+ *
27
+ * The buyer's key goes UP with the request because the Offer commits to
28
+ * blake3(buyer || provider); a provider cannot sign terms for a buyer it has not been told about.
29
+ */
30
+ export async function readOffer(base, buyerPubkey) {
31
+ const res = await fetch(`${base}/metered/open`, {
32
+ method: 'POST',
33
+ headers: { 'content-type': 'application/json' },
34
+ body: JSON.stringify({ buyerPubkey }),
35
+ });
36
+ if (res.status !== 402)
37
+ throw new ProtocolError(`expected 402, got ${res.status}`);
38
+ const body = (await res.json());
39
+ const offer = body.accepts.find((a) => a.scheme === 'metered');
40
+ if (!offer)
41
+ throw new ProtocolError('no metered offer in accepts');
42
+ return offer;
43
+ }
44
+ /**
45
+ * Run one chunk: reserve, receive, count, reconcile, countersign.
46
+ *
47
+ * Two round trips, because the buyer cannot honestly sign a count for content it has not seen.
48
+ */
49
+ export async function runBabel(base, session, prompt, units) {
50
+ const reservation = session.reserve(units);
51
+ const delivered = await post(base, '/metered/babel', { reservation, prompt });
52
+ // Decoded before anything is measured, so every number below is taken over the bytes the buyer
53
+ // actually keeps -- not over the transit encoding they arrived in.
54
+ const content = fromBase64(delivered.contentB64);
55
+ const mine = session.measure(content, delivered.measurement, reservation.seq);
56
+ const settled = await post(base, '/metered/state', { measurement: mine });
57
+ const buyerSig = session.countersign(settled.state, settled.providerSig, mine.units);
58
+ await post(base, '/metered/countersign', { state: settled.state, buyerSig });
59
+ return {
60
+ content,
61
+ state: settled.state,
62
+ billedUnits: settled.billedUnits,
63
+ providerSig: settled.providerSig,
64
+ buyerSig,
65
+ };
66
+ }
67
+ /**
68
+ * Open a session against a server and hand back the buyer half, ready to run chunks.
69
+ *
70
+ * `history` is SPEC.md 3.1a's obligation and belongs to the CALLER, because it has to outlive any
71
+ * one session to mean anything: a buyer opening many sessions against one provider creates the
72
+ * history once and passes it to every call. Omitting it leaves that buyer unprotected against a
73
+ * provider that reuses a session identifier deliberately.
74
+ */
75
+ export async function openSession(base, buyerSk, meter, expectedNetwork, history) {
76
+ const offer = await readOffer(base, publicKeyHex(buyerSk));
77
+ if (offer.buyerPubkey !== publicKeyHex(buyerSk)) {
78
+ throw new ProtocolError('the Offer names a different buyer than the one that asked');
79
+ }
80
+ return { offer, session: new BuyerSession(offer, buyerSk, meter, expectedNetwork, history) };
81
+ }
82
+ export { digestHex };
83
+ //# sourceMappingURL=client.js.map