1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519 | /**
* Wire-compat golden-vector suite. Runs against the committed dist/, never src/.
*
* The fixtures under test/vectors/ were derived WITHOUT this kit — canonical
* JSON re-implemented from its spec sentence, each preimage additionally
* written out as a literal template, Ed25519 from @noble/curves, SHA-256 from
* node:crypto (see test/vectors/independent.mjs). This file is where the two
* derivations are made to meet: everything the kit computes must equal what the
* fixture says, every tamper row must verify to null, and the signatures are
* ALSO re-verified here by the second Ed25519 implementation, so a broken
* WebCrypto path cannot pass by agreeing with itself.
*
* The fixtures are the CONTRACT. Add rows; never regenerate them to make a
* failing assertion go away.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import {
AMT_MAX,
AMT_MIN,
CTX_MAX,
DECLINE_REASONS,
MAX_TTL_MS,
MEMO_MAX,
NEUTRAL_UNIT,
ORDER_ID_RE,
PAY_BASE_URL,
SIG_RE,
SKEW_MS,
bankerOf,
expired,
isNeutralUnit,
isPayableCurrency,
issuanceKey,
issuancePreimage,
issuanceShape,
orderId,
orderPreimage,
orderShape,
parseCurrency,
parsePayFragment,
payRequestPreimage,
payRequestShape,
payRequestUrl,
receiptKey,
settlementPreimage,
settlementShape,
unsignedIssuance,
unsignedOrder,
unsignedPayRequest,
unsignedSettlement,
verifyIssuanceReceipt,
verifyOrder,
verifyPayRequest,
verifySettlement,
} from "../dist/index.js";
import {
SEED_BANK,
SEED_PAYEE,
SEED_PAYER,
canonIndependent,
preimage,
sha256b64url,
signArtifact,
verifyOver,
} from "./vectors/independent.mjs";
const fixture = async (name) =>
JSON.parse(await readFile(new URL(`./vectors/${name}`, import.meta.url), "utf8"));
const artifacts = await fixture("artifacts.json");
const keys = await fixture("keys.json");
const currency = await fixture("currency.json");
const limits = await fixture("limits.json");
const paylink = await fixture("paylink.json");
const clocks = await fixture("clocks.json");
const A = artifacts.artifacts;
const { bankPub, bank2Pub, cur: CUR } = artifacts.actors;
const UNSIGNED = { wpr: unsignedPayRequest, wpo: unsignedOrder, wrc: unsignedSettlement, wrj: unsignedSettlement, wri: unsignedIssuance };
const PREIMAGE = { wpr: payRequestPreimage, wpo: orderPreimage, wrc: settlementPreimage, wrj: settlementPreimage, wri: issuancePreimage };
const verifyAny = (kind, a) =>
kind === "wpr" ? verifyPayRequest(a)
: kind === "wpo" ? verifyOrder(a)
: kind === "wri" ? verifyIssuanceReceipt(a, undefined)
: verifySettlement(a, undefined);
// ---- the artifacts ----------------------------------------------------------
for (const kind of ["wpr", "wpo", "wrc", "wrj", "wri"]) {
test(`${kind}: the kit rebuilds the fixture's exact signature preimage`, () => {
assert.equal(PREIMAGE[kind](A[kind].wire), A[kind].preimage);
// and the fixture's own two derivations still agree with each other
assert.equal(preimage(kind, UNSIGNED[kind](A[kind].wire)), A[kind].preimage);
});
test(`${kind}: verifies, and comes back byte-identical on the wire`, async () => {
const v = await verifyAny(kind, A[kind].wire);
assert.ok(v, `${kind} must verify`);
assert.equal(JSON.stringify(v), A[kind].wire_json, "rebuild must be byte-identical, key order included");
assert.deepEqual(Object.keys(v), artifacts.key_order[kind]);
});
test(`${kind}: a second Ed25519 implementation accepts the same signature`, () => {
const a = A[kind].wire;
const sigField = kind === "wrc" || kind === "wrj" || kind === "wri" ? "bsig" : "sig";
const signer = kind === "wpr" ? a.to : kind === "wpo" ? a.from : a.bank;
assert.ok(verifyOver(signer, a[sigField], A[kind].preimage));
});
}
test("the hand-computed lengths hold", () => {
const L = artifacts.lengths_by_hand;
assert.equal(A.wpo.wire.from.length, L.identity_pub_b64url);
assert.equal(A.wpo.wire.sig.length, L.ed25519_sig_b64url);
assert.equal(A.wrc.wire.bsig.length, L.ed25519_sig_b64url);
assert.equal(A.wpo.wire.id.length, L.order_id_b64url);
assert.equal(keys.order_id.sha256_b64url.length, L.sha256_b64url);
assert.match(A.wpo.wire.id, ORDER_ID_RE);
assert.match(A.wpo.wire.sig, SIG_RE);
});
test("wpo carries thirteen keys in the order the wire fixes", () => {
// the reason this kit has an `obj` macro: nine pairs is where #js{} and
// js-obj silently switch to hash order
assert.equal(artifacts.key_order.wpo.length, 13);
assert.deepEqual(artifacts.key_order.wpo, [
"v", "t", "id", "cur", "amt", "seq", "from", "to", "ctx", "memo", "ts", "exp", "sig",
]);
});
test("wri carries ten keys in the order the wire fixes", () => {
// also past the nine-pair cliff — a hash-ordered literal would be a silent
// wire change no signature test could see, since canon() sorts
assert.equal(artifacts.key_order.wri.length, 10);
assert.deepEqual(artifacts.key_order.wri, [
"v", "t", "cur", "seq", "to", "amt", "h", "ts", "bank", "bsig",
]);
});
// ---- order independence -----------------------------------------------------
test("key order is the wire but not the signature", async () => {
const oi = artifacts.order_independence;
for (const [kind, json] of [["wpo", oi.wpo_reversed_json], ["wrc", oi.wrc_reversed_json], ["wri", oi.wri_reversed_json]]) {
const reversed = JSON.parse(json);
assert.notEqual(json, A[kind].wire_json, "the reversed form really is different bytes");
assert.equal(PREIMAGE[kind](reversed), oi.expect_same_preimage[kind], "canon sorts, so the preimage is the same");
const v = await verifyAny(kind, reversed);
assert.ok(v, "a reversed artifact still verifies against the same signature");
assert.equal(JSON.stringify(v), A[kind].wire_json, "and is normalised back to wire order");
}
});
// ---- tamper -----------------------------------------------------------------
for (const row of artifacts.tamper) {
test(`tamper rejected — ${row.name}`, async () => {
assert.equal(await verifyAny(row.kind, row.artifact), null);
});
}
test("replay under a different bank is refused", async () => {
const r = artifacts.replay;
assert.ok(await verifySettlement(r.artifact, r.expect_accept_for));
assert.equal(await verifySettlement(r.artifact, r.expect_reject_for), null);
});
test("an issuance replayed under a different bank is refused", async () => {
const r = artifacts.replay_issuance;
assert.ok(await verifyIssuanceReceipt(r.artifact, r.expect_accept_for));
assert.equal(await verifyIssuanceReceipt(r.artifact, r.expect_reject_for), null);
});
test("verify* never throws, whatever it is handed", async () => {
const junk = [null, undefined, 0, "", "wpo", [], {}, { v: 1 }, { v: 1, t: "wpo" }, new Date(), NaN];
for (const j of junk) {
assert.equal(await verifyPayRequest(j), null);
assert.equal(await verifyOrder(j), null);
assert.equal(await verifySettlement(j, bankPub), null);
assert.equal(await verifyIssuanceReceipt(j, bankPub), null);
assert.equal(await parsePayFragment(j), null);
}
});
// ---- dedup keys -------------------------------------------------------------
test("orderId and receiptKey match the independently hashed fixtures", async () => {
assert.equal(await orderId(A.wpo.wire), keys.order_id.sha256_b64url);
assert.equal(await receiptKey(A.wrc.wire), keys.receipt_key_wrc.sha256_b64url);
assert.equal(await receiptKey(A.wrj.wire), keys.receipt_key_wrj.sha256_b64url);
assert.equal(await issuanceKey(A.wri.wire), keys.issuance_key.sha256_b64url);
});
test("the committed hash inputs really hash to the committed digests", () => {
// nothing from the kit in this test: it checks the FIXTURE against SHA-256
for (const row of [keys.order_id, keys.receipt_key_wrc, keys.receipt_key_wrj, keys.issuance_key]) {
assert.equal(sha256b64url(row.input), row.sha256_b64url, row.input_desc);
}
assert.equal(keys.order_id.input, canonIndependent(unsignedOrder(A.wpo.wire)));
});
test("orderId ignores the signature and nothing else", async () => {
const base = await orderId(A.wpo.wire);
const unsigned = { ...A.wpo.wire };
delete unsigned.sig;
assert.equal(await orderId(unsigned), base, "sig is not part of the key");
assert.notEqual(await orderId({ ...A.wpo.wire, amt: 101 }), base);
assert.notEqual(await orderId({ ...A.wpo.wire, seq: 8 }), base);
assert.notEqual(await orderId({ ...A.wpo.wire, ctx: "other" }), base);
});
test("issuanceKey is (bank, h) and nothing else", async () => {
// (bank, h) IS the fact being made final: this bank pinned this mint entry.
// Everything else on the artifact is a property of the entry `h` names, so a
// bank that re-states the receipt (fresh ts) folds once, and a bank that
// signs two receipts with one `h` and two amounts has signed a contradiction
// the ledger refuses to double-credit.
const base = await issuanceKey(A.wri.wire);
const w = A.wri.wire;
assert.equal(await issuanceKey({ ...w, ts: w.ts + 5000 }), base, "a re-issued receipt is one receipt");
assert.equal(await issuanceKey({ ...w, seq: 99 }), base, "seq is a property of the entry");
assert.equal(await issuanceKey({ ...w, to: w.bank }), base, "so is the recipient");
assert.equal(await issuanceKey({ ...w, amt: 999 }), base, "and the amount");
const noSig = { ...w };
delete noSig.bsig;
assert.equal(await issuanceKey(noSig), base, "the signature is not part of the key");
assert.notEqual(await issuanceKey({ ...w, h: w.h + "x" }), base, "a different entry is a different fact");
assert.notEqual(await issuanceKey({ ...w, bank: bank2Pub }), base, "two banks' logs are two spaces");
});
test("receiptKey ignores ts and the bank's log ref, and separates receipt from decline", async () => {
const k = await receiptKey(A.wrc.wire);
assert.equal(await receiptKey({ ...A.wrc.wire, ts: A.wrc.wire.ts + 5000 }), k, "a re-issued answer is one answer");
assert.equal(await receiptKey({ ...A.wrc.wire, seq: "L-999" }), k, "a different log ref is the same settlement");
assert.notEqual(await receiptKey(A.wrj.wire), k, "a decline must never collide with a receipt");
assert.notEqual(await receiptKey({ ...A.wrc.wire, bank: bank2Pub }), k, "two banks answering stay distinct");
});
// ---- currency ids -----------------------------------------------------------
for (const row of currency.rows) {
test(`currency ${JSON.stringify(row.cur).slice(0, 50)} — ${row.why}`, () => {
assert.deepEqual(parseCurrency(row.cur), row.parse === null ? null : { ...row.parse });
assert.equal(isPayableCurrency(row.cur), row.payable);
assert.equal(bankerOf(row.cur), row.parse === null ? null : row.parse.banker);
});
}
test("the neutral unit is reserved, parseable and unpayable", () => {
assert.equal(NEUTRAL_UNIT, "~.GAZ");
assert.ok(isNeutralUnit(NEUTRAL_UNIT));
assert.ok(!isPayableCurrency(NEUTRAL_UNIT));
assert.deepEqual(parseCurrency(NEUTRAL_UNIT), { banker: "~", code: "GAZ" });
});
// ---- boundaries -------------------------------------------------------------
//
// Each row is the fixture order with ONE field replaced and re-signed by the
// independent path, so an acceptance is a real acceptance and a rejection is
// about the rule rather than about a signature we broke on the way in.
const reSignedOrder = (field, value) => {
const unsigned = { ...unsignedOrder(A.wpo.wire), [field]: value };
try {
return signArtifact("wpo", unsigned, SEED_PAYER);
} catch {
// canon refuses the value outright (undefined, a function): still a reject
return { ...unsigned, sig: A.wpo.wire.sig };
}
};
for (const [field, rows] of Object.entries(limits)) {
if (field.startsWith("_")) continue;
for (const row of rows) {
test(`limit ${field}=${JSON.stringify(row.value).slice(0, 30)} — ${row.why}`, async () => {
const v = await verifyOrder(reSignedOrder(field, row.value));
assert.equal(v !== null, row.accept);
});
}
}
test("the constants the limits are written against", () => {
assert.equal(AMT_MIN, 1);
assert.equal(AMT_MAX, 2 ** 50);
assert.equal(MEMO_MAX, 140);
assert.equal(CTX_MAX, 128);
assert.deepEqual([...DECLINE_REASONS], ["insufficient", "unknown-payer", "expired", "cur", "seq"]);
});
test("every decline reason is accepted and nothing else is", async () => {
for (const why of DECLINE_REASONS) {
const unsigned = { v: 1, t: "wrj", po: A.wpo.wire, why, ts: A.wrj.wire.ts, bank: bankPub };
assert.ok(await verifySettlement(signArtifact("wrj", unsigned, SEED_BANK), bankPub), why);
}
const bad = { v: 1, t: "wrj", po: A.wpo.wire, why: "nope", ts: A.wrj.wire.ts, bank: bankPub };
assert.equal(await verifySettlement(signArtifact("wrj", bad, SEED_BANK), bankPub), null);
});
// ---- expiry is a state, not a verdict ---------------------------------------
test("an expired artifact still verifies — a bank has to be able to decline it", async () => {
assert.ok(await verifyOrder(A.wpo.wire), "the fixture order expired in January 2025");
assert.equal(expired(A.wpo.wire), true);
assert.equal(expired(A.wpo.wire, A.wpo.wire.exp - 1), false);
assert.equal(expired(A.wpo.wire, A.wpo.wire.exp), false, "exp is the last live millisecond");
assert.equal(expired(A.wrc.wire), true, "a settlement inherits its order's expiry");
});
// ---- the clock contract -----------------------------------------------------
//
// Every admission check takes an optional trailing `nowMs`: absent = the
// reader's wall clock, a number = that instant, `null` = NO CLOCK. The third is
// what a REPLICATED FOLD calls, and it is the reason the parameter exists —
// two replicas of a bank's log with clocks three minutes apart must not
// disagree about whether one entry is a valid order.
//
// The rows are re-signed here against the reader's own Date.now(), because a
// verdict that moves with the reader cannot be pinned as a literal.
const orderAt = (ts, expMinusTs = 600000) =>
signArtifact(
"wpo",
{ ...unsignedOrder(A.wpo.wire), ts, exp: ts + expMinusTs },
SEED_PAYER,
);
for (const row of clocks.order_ts_rows) {
test(`clock — wpo with ${row.name}`, async () => {
const o = orderAt(Date.now() + row.ts_offset_ms);
assert.equal(orderShape(o) !== null, row.clocked, "the wall-clock form");
assert.equal((await verifyOrder(o)) !== null, row.clocked, "and verifyOrder agrees with it");
assert.equal(orderShape(o, null) !== null, row.clock_free, "the clock-free form");
assert.equal(
(await verifyOrder(o, null)) !== null,
row.clock_free,
"and verifyOrder(e, null) agrees with it",
);
});
}
test("clock — a clock-free verdict is the same verdict for every nowMs", () => {
// the whole property banca needs: no wall clock reaches the check, so no two
// replicas can disagree. The clocked verdict over the same sweep is the
// control — it changes, which is what makes this assertion mean something.
const o = orderAt(Date.now() + 86400000); // a day into every reader's future
const free = clocks.now_sweep.map((n) => orderShape(o, null) !== null);
assert.deepEqual(free, clocks.now_sweep.map(() => true), "clock-free is constant across the sweep");
const clocked = clocks.now_sweep.map((n) => orderShape(o, n) !== null);
assert.ok(new Set(clocked).size > 1, "and the clocked form really does vary, so this is not vacuous");
for (const n of clocks.now_sweep) {
assert.equal(orderShape(o, n) !== null, o.ts <= n + SKEW_MS, `nowMs=${n} admits exactly ts <= now + SKEW_MS`);
}
// and it rebuilds the identical artifact, not merely "an" acceptance
assert.equal(JSON.stringify(orderShape(o, null)), JSON.stringify(orderShape(o, o.ts)));
assert.equal(JSON.stringify(orderShape(A.wpo.wire, null)), A.wpo.wire_json,
"the fixture order comes back byte-identical without a clock");
});
for (const row of clocks.order_ts_relative_rows) {
test(`clock-free is not weaker — wpo with ${row.name} is rejected by ${row.bound}`, async () => {
const base = Date.now() - 60000;
const ts = row.patch.ts !== undefined ? row.patch.ts : base;
const exp = row.patch.exp_is_ts ? ts : ts + (row.patch.exp_minus_ts ?? 600000);
const o = signArtifact("wpo", { ...unsignedOrder(A.wpo.wire), ts, exp }, SEED_PAYER);
assert.equal(orderShape(o, null), null, "the clock-free form still enforces it");
assert.equal(await verifyOrder(o, null), null);
assert.equal(orderShape(o), null, "and so does the wall-clock form");
});
}
test("clock — MAX_TTL_MS is the bound the ts-relative rows are written against", () => {
assert.equal(MAX_TTL_MS, 90 * 86400000);
assert.equal(SKEW_MS, 120000);
const row = clocks.order_ts_relative_rows.find((r) => r.patch.exp_minus_ts > 0);
assert.equal(row.patch.exp_minus_ts, MAX_TTL_MS + 1, "the fixture's number is that constant plus one");
});
test("clock — a garbled nowMs reads as the wall clock, never as clock-free", () => {
// there must be no most-permissive setting a typo can select
const o = orderAt(Date.now() + 86400000);
assert.equal(orderShape(o), null, "the wall clock rejects it");
assert.equal(orderShape(o, null) !== null, true, "and only an explicit null admits it");
for (const bad of [...clocks.bad_clock_arguments_behave_as_wall_clock, NaN, Infinity, -Infinity, undefined]) {
assert.equal(orderShape(o, bad), null, `${String(bad)} must behave as the wall clock`);
}
});
for (const row of clocks.settlement_ts_rows) {
test(`clock — wrc with ${row.name}`, async () => {
const now = Date.now();
const po = orderAt(now - 60000);
const rc = signArtifact(
"wrc",
{ v: 1, t: "wrc", po, seq: "L-1", ts: now + row.ts_offset_ms, bank: bankPub },
SEED_BANK,
);
assert.equal(settlementShape(rc) !== null, row.clocked);
assert.equal((await verifySettlement(rc, bankPub)) !== null, row.clocked);
assert.equal(settlementShape(rc, null) !== null, row.clock_free);
assert.equal((await verifySettlement(rc, bankPub, null)) !== null, row.clock_free);
});
}
test("clock-free is not weaker — a settlement still cannot predate its order", async () => {
const now = Date.now();
const po = orderAt(now - 60000);
const rc = signArtifact(
"wrc",
{ v: 1, t: "wrc", po, seq: "L-1", ts: po.ts + clocks.settlement_before_its_order_ms, bank: bankPub },
SEED_BANK,
);
assert.equal(clocks.settlement_before_its_order_ms, -(SKEW_MS + 60001), "one millisecond outside the skew allowance");
assert.equal(settlementShape(rc, null), null, "the ts-relative bound survives the clock-free form");
assert.equal(await verifySettlement(rc, bankPub, null), null);
});
const issuanceAt = (ts) => signArtifact("wri", { ...unsignedIssuance(A.wri.wire), ts }, SEED_BANK);
for (const row of clocks.issuance_ts_rows) {
test(`clock — wri with ${row.name}`, async () => {
const w = issuanceAt(Date.now() + row.ts_offset_ms);
assert.equal(issuanceShape(w) !== null, row.clocked, "the wall-clock form");
assert.equal((await verifyIssuanceReceipt(w, bankPub)) !== null, row.clocked, "and verify agrees with it");
assert.equal(issuanceShape(w, null) !== null, row.clock_free, "the clock-free form");
assert.equal((await verifyIssuanceReceipt(w, bankPub, null)) !== null, row.clock_free);
});
}
test("clock — a clock-free wri verdict is the same verdict for every nowMs", () => {
// same property, same non-vacuity control as the wpo sweep: a wri has no exp
// and embeds no order, so `ts <= now + SKEW_MS` is its ONLY clock bound
const w = issuanceAt(Date.now() + 86400000);
const free = clocks.now_sweep.map(() => issuanceShape(w, null) !== null);
assert.deepEqual(free, clocks.now_sweep.map(() => true), "clock-free is constant across the sweep");
const clocked = clocks.now_sweep.map((n) => issuanceShape(w, n) !== null);
assert.ok(new Set(clocked).size > 1, "and the clocked form really does vary, so this is not vacuous");
for (const n of clocks.now_sweep) {
assert.equal(issuanceShape(w, n) !== null, w.ts <= n + SKEW_MS, `nowMs=${n} admits exactly ts <= now + SKEW_MS`);
}
assert.equal(JSON.stringify(issuanceShape(A.wri.wire, null)), A.wri.wire_json,
"the fixture receipt comes back byte-identical without a clock");
});
test("clock — a garbled nowMs on issuanceShape reads as the wall clock, never as clock-free", () => {
const w = issuanceAt(Date.now() + 86400000);
assert.equal(issuanceShape(w), null, "the wall clock rejects it");
assert.equal(issuanceShape(w, null) !== null, true, "and only an explicit null admits it");
for (const bad of [...clocks.bad_clock_arguments_behave_as_wall_clock, NaN, Infinity, -Infinity, undefined]) {
assert.equal(issuanceShape(w, bad), null, `${String(bad)} must behave as the wall clock`);
}
});
for (const row of clocks.pay_request_exp_rows) {
test(`clock — wpr with ${row.name}`, async () => {
const r = signArtifact(
"wpr",
{ ...unsignedPayRequest(A.wpr.wire), exp: Date.now() + row.exp_offset_ms },
SEED_PAYEE,
);
assert.equal(payRequestShape(r) !== null, row.clocked);
assert.equal((await verifyPayRequest(r)) !== null, row.clocked);
assert.equal(payRequestShape(r, null) !== null, row.clock_free);
assert.equal((await verifyPayRequest(r, null)) !== null, row.clock_free);
// a link is read by a person, now: parsePayFragment keeps the wall clock
if (!row.clocked) {
assert.equal(await parsePayFragment(payRequestUrl(A.wpr.wire).replace(/#pay=.*/, `#pay=${
Buffer.from(JSON.stringify(r), "utf8").toString("base64url")}`)), null,
"and a far-future request does not become readable through a link");
}
});
}
// ---- pay links --------------------------------------------------------------
test("payRequestUrl produces the fixture's exact URL", () => {
assert.equal(payRequestUrl(paylink.request), paylink.expect_url);
assert.ok(paylink.expect_url.startsWith(PAY_BASE_URL + "#pay="));
});
test("parsePayFragment accepts every form an address bar hands you", async () => {
const frag = paylink.expect_url.split("#")[1];
const forms = {
url: paylink.expect_url,
hash: `#${frag}`,
bare: frag,
prefixed: `#other=1&${frag}`,
suffixed: `#${frag}&other=1`,
};
for (const row of paylink.accept_forms) {
const v = await parsePayFragment(forms[row.form]);
assert.ok(v, row.name);
assert.equal(JSON.stringify(v), JSON.stringify(paylink.request), row.name);
}
});
for (const row of paylink.reject_forms) {
test(`pay link rejected — ${row.name}`, async () => {
assert.equal(await parsePayFragment(row.input), null);
});
}
test("a pay link is not a capability to spend", () => {
// the whole payload is the request; nothing in it is secret and nothing in it
// authorises a payment — only a wpo signed by the payer does that
const payload = JSON.parse(Buffer.from(paylink.expect_url.split("#pay=")[1], "base64url").toString("utf8"));
assert.deepEqual(Object.keys(payload), ["v", "t", "cur", "amt", "to", "tox", "ctx", "memo", "exp", "sig"]);
assert.equal(payload.cur, CUR);
});
|