@statewalker/webrun-biscuit 0.1.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/dist/index.js ADDED
@@ -0,0 +1,3130 @@
1
+ import { ed25519 } from "@noble/curves/ed25519.js";
2
+ import { p256 } from "@noble/curves/nist.js";
3
+ //#region src/crypto.ts
4
+ /**
5
+ * Biscuit cryptography: chained block signatures, sealing, third-party
6
+ * (external) signatures. Ed25519 and ECDSA/secp256r1, per SPECIFICATIONS.md.
7
+ */
8
+ var SignatureError = class extends Error {};
9
+ const ALG_ED25519 = 0;
10
+ const ALG_SECP256R1 = 1;
11
+ function concat(...parts) {
12
+ const len = parts.reduce((a, p) => a + p.length, 0);
13
+ const out = new Uint8Array(len);
14
+ let o = 0;
15
+ for (const p of parts) {
16
+ out.set(p, o);
17
+ o += p.length;
18
+ }
19
+ return out;
20
+ }
21
+ const ascii = (s) => Uint8Array.from(s, (c) => c.charCodeAt(0));
22
+ const bytesEqual = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
23
+ /** little-endian i32, as used for algorithm ids and payload versions */
24
+ function le32(n) {
25
+ const b = /* @__PURE__ */ new Uint8Array(4);
26
+ new DataView(b.buffer).setInt32(0, n, true);
27
+ return b;
28
+ }
29
+ function verifySignature(key, payload, sig) {
30
+ try {
31
+ if (key.algorithm === ALG_ED25519) return ed25519.verify(sig, payload, key.key);
32
+ if (key.algorithm === ALG_SECP256R1) return p256.verify(sig, payload, key.key, {
33
+ format: "der",
34
+ prehash: true,
35
+ lowS: false
36
+ });
37
+ return false;
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+ function publicKeyFromSecret(secret, algorithm) {
43
+ if (algorithm === ALG_ED25519) return ed25519.getPublicKey(secret);
44
+ if (algorithm === ALG_SECP256R1) return p256.getPublicKey(secret, true);
45
+ throw new SignatureError(`unknown algorithm ${algorithm}`);
46
+ }
47
+ function blockPayloadV0(data, nextKey, externalSig) {
48
+ return concat(data, externalSig ?? /* @__PURE__ */ new Uint8Array(0), le32(nextKey.algorithm), nextKey.key);
49
+ }
50
+ function authorityPayloadV1(data, nextKey, version) {
51
+ return concat(ascii("\0BLOCK\0\0VERSION\0"), le32(version), ascii("\0PAYLOAD\0"), data, ascii("\0ALGORITHM\0"), le32(nextKey.algorithm), ascii("\0NEXTKEY\0"), nextKey.key);
52
+ }
53
+ function blockPayloadV1(data, nextKey, externalSig, previousSignature, version) {
54
+ return concat(ascii("\0BLOCK\0\0VERSION\0"), le32(version), ascii("\0PAYLOAD\0"), data, ascii("\0ALGORITHM\0"), le32(nextKey.algorithm), ascii("\0NEXTKEY\0"), nextKey.key, ascii("\0PREVSIG\0"), previousSignature, ...externalSig ? [ascii("\0EXTERNALSIG\0"), externalSig] : []);
55
+ }
56
+ function externalPayloadV1(data, previousSignature, version) {
57
+ return concat(ascii("\0EXTERNAL\0\0VERSION\0"), le32(version), ascii("\0PAYLOAD\0"), data, ascii("\0PREVSIG\0"), previousSignature);
58
+ }
59
+ function sealPayloadV0(block) {
60
+ return concat(block.block, le32(block.nextKey.algorithm), block.nextKey.key, block.signature);
61
+ }
62
+ function sign(payload, secret, algorithm) {
63
+ if (algorithm === ALG_ED25519) return ed25519.sign(payload, secret);
64
+ if (algorithm === ALG_SECP256R1) return p256.sign(payload, secret, {
65
+ format: "der",
66
+ prehash: true
67
+ });
68
+ throw new SignatureError(`unknown algorithm ${algorithm}`);
69
+ }
70
+ /** a fresh keypair for the given algorithm */
71
+ function generateKeypair(algorithm = 0) {
72
+ const secretKey = algorithm === ALG_ED25519 ? ed25519.utils.randomSecretKey() : p256.utils.randomSecretKey();
73
+ return {
74
+ secretKey,
75
+ publicKey: publicKeyFromSecret(secretKey, algorithm)
76
+ };
77
+ }
78
+ /** Verifies the full signature chain and the proof. Throws on failure. */
79
+ function verifyToken(token, rootPublicKey, rootAlgorithm = 0) {
80
+ const root = {
81
+ algorithm: rootAlgorithm,
82
+ key: rootPublicKey
83
+ };
84
+ if (token.authority.externalSignature) throw new SignatureError("the authority block must not carry an external signature");
85
+ const authVersion = token.authority.version ?? 0;
86
+ if (!verifySignature(root, authVersion === 0 ? blockPayloadV0(token.authority.block, token.authority.nextKey) : authVersion === 1 ? authorityPayloadV1(token.authority.block, token.authority.nextKey, authVersion) : (() => {
87
+ throw new SignatureError(`unsupported block version ${authVersion}`);
88
+ })(), token.authority.signature)) throw new SignatureError("invalid authority block signature");
89
+ let currentKey = token.authority.nextKey;
90
+ let previousSignature = token.authority.signature;
91
+ for (const block of token.blocks) {
92
+ const version = block.version ?? 0;
93
+ const externalSig = block.externalSignature?.signature;
94
+ let payload;
95
+ if (version === 0) payload = blockPayloadV0(block.block, block.nextKey, externalSig);
96
+ else if (version === 1) payload = blockPayloadV1(block.block, block.nextKey, externalSig, previousSignature, version);
97
+ else throw new SignatureError(`unsupported block version ${version}`);
98
+ if (!verifySignature(currentKey, payload, block.signature)) throw new SignatureError("invalid block signature");
99
+ if (block.externalSignature) {
100
+ if (version !== 1) throw new SignatureError("unsupported third party block version");
101
+ const ext = externalPayloadV1(block.block, previousSignature, version);
102
+ if (!verifySignature(block.externalSignature.publicKey, ext, block.externalSignature.signature)) throw new SignatureError("invalid external signature");
103
+ }
104
+ currentKey = block.nextKey;
105
+ previousSignature = block.signature;
106
+ }
107
+ if (token.proof.kind === "nextSecret") {
108
+ const derived = publicKeyFromSecret(token.proof.value, currentKey.algorithm);
109
+ if (!bytesEqual(derived, currentKey.key)) throw new SignatureError("the last public key does not match the private key");
110
+ } else {
111
+ const last = token.blocks.length ? token.blocks[token.blocks.length - 1] : token.authority;
112
+ if (!verifySignature(currentKey, sealPayloadV0(last), token.proof.value)) throw new SignatureError("invalid seal signature");
113
+ }
114
+ }
115
+ /** Revocation identifier of each block: its signature bytes. */
116
+ function revocationIds(token) {
117
+ return [token.authority, ...token.blocks].map((b) => b.signature);
118
+ }
119
+ //#endregion
120
+ //#region src/datalog.ts
121
+ /**
122
+ * Biscuit Datalog: term model, fact/rule evaluation with origin tracking, and
123
+ * the stack-based expression virtual machine.
124
+ */
125
+ const AUTHORIZER = 4294967295;
126
+ const hex$2 = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
127
+ /** Canonical string key: JS Map/Set are reference-keyed, so every structural
128
+ * comparison and every de-duplication in the engine goes through this. */
129
+ function termKey(t) {
130
+ switch (t.t) {
131
+ case "var": return `v${t.v}`;
132
+ case "int": return `i${t.v}`;
133
+ case "str": return `s${t.v.length}:${t.v}`;
134
+ case "date": return `d${t.v}`;
135
+ case "bytes": return `b${hex$2(t.v)}`;
136
+ case "bool": return t.v ? "T" : "F";
137
+ case "null": return "N";
138
+ case "set": return `S[${t.v.map(termKey).sort().join(",")}]`;
139
+ case "array": return `A[${t.v.map(termKey).join(",")}]`;
140
+ case "map": return `M[${t.v.map(([k, v]) => `${mapKeyKey(k)}=>${termKey(v)}`).sort().join(",")}]`;
141
+ }
142
+ }
143
+ const mapKeyKey = (k) => k.t === "int" ? `i${k.v}` : `s${k.v.length}:${k.v}`;
144
+ const termEq = (a, b) => termKey(a) === termKey(b);
145
+ /** de-duplicated, deterministically ordered set contents */
146
+ function normalizeSet(items) {
147
+ const seen = /* @__PURE__ */ new Map();
148
+ for (const i of items) seen.set(termKey(i), i);
149
+ return [...seen.entries()].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0).map((e) => e[1]);
150
+ }
151
+ const factKey = (f) => `${f.predicate.name}/${f.predicate.terms.length}(${f.predicate.terms.map(termKey).join(",")})`;
152
+ var Origin = class Origin {
153
+ ids;
154
+ constructor(ids = []) {
155
+ this.ids = ids;
156
+ }
157
+ static of(...ids) {
158
+ return new Origin([...new Set(ids)].sort((a, b) => a - b));
159
+ }
160
+ union(other) {
161
+ return Origin.of(...this.ids, ...other.ids);
162
+ }
163
+ with(id) {
164
+ return Origin.of(...this.ids, id);
165
+ }
166
+ get key() {
167
+ return this.ids.join(",");
168
+ }
169
+ };
170
+ var TrustedOrigins = class TrustedOrigins {
171
+ set;
172
+ constructor(ids) {
173
+ this.set = new Set(ids);
174
+ }
175
+ static default() {
176
+ return new TrustedOrigins([AUTHORIZER, 0]);
177
+ }
178
+ /** the trusted set is a superset of the fact's origin */
179
+ contains(origin) {
180
+ for (const id of origin.ids) if (!this.set.has(id)) return false;
181
+ return true;
182
+ }
183
+ get key() {
184
+ return [...this.set].sort((a, b) => a - b).join(",");
185
+ }
186
+ /** the trusted block ids, for deriving a new set from this one */
187
+ ids() {
188
+ return this.set;
189
+ }
190
+ };
191
+ function trustedOriginsFromScopes(ruleScopes, defaults, currentBlock, publicKeyToBlockIds) {
192
+ if (ruleScopes.length === 0) {
193
+ const ids = new Set(defaults.ids());
194
+ ids.add(currentBlock);
195
+ ids.add(AUTHORIZER);
196
+ return new TrustedOrigins(ids);
197
+ }
198
+ const ids = /* @__PURE__ */ new Set([AUTHORIZER, currentBlock]);
199
+ for (const scope of ruleScopes) if (scope.kind === "authority") ids.add(0);
200
+ else if (scope.kind === "previous") {
201
+ if (currentBlock !== 4294967295) for (let i = 0; i <= currentBlock; i++) ids.add(i);
202
+ } else for (const id of publicKeyToBlockIds.get(scope.key) ?? []) ids.add(id);
203
+ return new TrustedOrigins(ids);
204
+ }
205
+ var ExecutionError = class extends Error {
206
+ kind;
207
+ constructor(kind, message) {
208
+ super(message ?? kind);
209
+ this.kind = kind;
210
+ }
211
+ };
212
+ const U = {
213
+ Negate: 0,
214
+ Parens: 1,
215
+ Length: 2,
216
+ TypeOf: 3,
217
+ Ffi: 4
218
+ };
219
+ const B = {
220
+ LessThan: 0,
221
+ GreaterThan: 1,
222
+ LessOrEqual: 2,
223
+ GreaterOrEqual: 3,
224
+ Equal: 4,
225
+ Contains: 5,
226
+ Prefix: 6,
227
+ Suffix: 7,
228
+ Regex: 8,
229
+ Add: 9,
230
+ Sub: 10,
231
+ Mul: 11,
232
+ Div: 12,
233
+ And: 13,
234
+ Or: 14,
235
+ Intersection: 15,
236
+ Union: 16,
237
+ BitwiseAnd: 17,
238
+ BitwiseOr: 18,
239
+ BitwiseXor: 19,
240
+ NotEqual: 20,
241
+ HeterogeneousEqual: 21,
242
+ HeterogeneousNotEqual: 22,
243
+ LazyAnd: 23,
244
+ LazyOr: 24,
245
+ All: 25,
246
+ Any: 26,
247
+ Get: 27,
248
+ Ffi: 28,
249
+ TryOr: 29
250
+ };
251
+ const BinaryOp = B;
252
+ const UnaryOp = U;
253
+ const I64_MIN = -(2n ** 63n);
254
+ const I64_MAX = 2n ** 63n - 1n;
255
+ function checkedI64(v) {
256
+ if (v < I64_MIN || v > I64_MAX) throw new ExecutionError("Overflow");
257
+ return {
258
+ t: "int",
259
+ v
260
+ };
261
+ }
262
+ const utf8len = (s) => new TextEncoder().encode(s).length;
263
+ function typeName(t) {
264
+ switch (t.t) {
265
+ case "int": return "integer";
266
+ case "str": return "string";
267
+ case "date": return "date";
268
+ case "bytes": return "bytes";
269
+ case "bool": return "bool";
270
+ case "set": return "set";
271
+ case "null": return "null";
272
+ case "array": return "array";
273
+ case "map": return "map";
274
+ default: throw new ExecutionError("InvalidType");
275
+ }
276
+ }
277
+ function unary(op, ffi, v, externs) {
278
+ switch (op) {
279
+ case U.Negate:
280
+ if (v.t !== "bool") throw new ExecutionError("InvalidType");
281
+ return {
282
+ t: "bool",
283
+ v: !v.v
284
+ };
285
+ case U.Parens: return v;
286
+ case U.Length:
287
+ if (v.t === "str") return {
288
+ t: "int",
289
+ v: BigInt(utf8len(v.v))
290
+ };
291
+ if (v.t === "bytes") return {
292
+ t: "int",
293
+ v: BigInt(v.v.length)
294
+ };
295
+ if (v.t === "set" || v.t === "array" || v.t === "map") return {
296
+ t: "int",
297
+ v: BigInt(v.v.length)
298
+ };
299
+ throw new ExecutionError("InvalidType");
300
+ case U.TypeOf: return {
301
+ t: "str",
302
+ v: typeName(v)
303
+ };
304
+ case U.Ffi: {
305
+ const f = externs.get(ffi);
306
+ if (!f) throw new ExecutionError("UndefinedExtern", ffi);
307
+ return f(v);
308
+ }
309
+ default: throw new ExecutionError("InvalidType");
310
+ }
311
+ }
312
+ function setContains(set, value) {
313
+ const k = termKey(value);
314
+ return set.some((t) => termKey(t) === k);
315
+ }
316
+ function binary(op, ffi, l, r, externs) {
317
+ const bool = (v) => ({
318
+ t: "bool",
319
+ v
320
+ });
321
+ const strictEqable = l.t === r.t && l.t !== "var" || l.t === "set" && r.t === "set" || l.t === "map" && r.t === "map";
322
+ switch (op) {
323
+ case B.LessThan:
324
+ case B.GreaterThan:
325
+ case B.LessOrEqual:
326
+ case B.GreaterOrEqual: {
327
+ if (!(l.t === "int" && r.t === "int" || l.t === "date" && r.t === "date")) throw new ExecutionError("InvalidType");
328
+ const a = l.v;
329
+ const b = r.v;
330
+ return bool(op === B.LessThan ? a < b : op === B.GreaterThan ? a > b : op === B.LessOrEqual ? a <= b : a >= b);
331
+ }
332
+ case B.Equal:
333
+ case B.NotEqual: {
334
+ if (!strictEqable) throw new ExecutionError("InvalidType");
335
+ const eq = termEq(l, r);
336
+ return bool(op === B.Equal ? eq : !eq);
337
+ }
338
+ case B.HeterogeneousEqual: return bool(l.t === r.t && termEq(l, r));
339
+ case B.HeterogeneousNotEqual: return bool(!(l.t === r.t && termEq(l, r)));
340
+ case B.Add:
341
+ if (l.t === "int" && r.t === "int") return checkedI64(l.v + r.v);
342
+ if (l.t === "str" && r.t === "str") return {
343
+ t: "str",
344
+ v: l.v + r.v
345
+ };
346
+ throw new ExecutionError("InvalidType");
347
+ case B.Sub:
348
+ if (l.t !== "int" || r.t !== "int") throw new ExecutionError("InvalidType");
349
+ return checkedI64(l.v - r.v);
350
+ case B.Mul:
351
+ if (l.t !== "int" || r.t !== "int") throw new ExecutionError("InvalidType");
352
+ return checkedI64(l.v * r.v);
353
+ case B.Div:
354
+ if (l.t !== "int" || r.t !== "int") throw new ExecutionError("InvalidType");
355
+ if (r.v === 0n) throw new ExecutionError("DivideByZero");
356
+ return checkedI64(l.v / r.v);
357
+ case B.BitwiseAnd:
358
+ if (l.t !== "int" || r.t !== "int") throw new ExecutionError("InvalidType");
359
+ return {
360
+ t: "int",
361
+ v: BigInt.asIntN(64, l.v & r.v)
362
+ };
363
+ case B.BitwiseOr:
364
+ if (l.t !== "int" || r.t !== "int") throw new ExecutionError("InvalidType");
365
+ return {
366
+ t: "int",
367
+ v: BigInt.asIntN(64, l.v | r.v)
368
+ };
369
+ case B.BitwiseXor:
370
+ if (l.t !== "int" || r.t !== "int") throw new ExecutionError("InvalidType");
371
+ return {
372
+ t: "int",
373
+ v: BigInt.asIntN(64, l.v ^ r.v)
374
+ };
375
+ case B.And:
376
+ if (l.t !== "bool" || r.t !== "bool") throw new ExecutionError("InvalidType");
377
+ return bool(l.v && r.v);
378
+ case B.Or:
379
+ if (l.t !== "bool" || r.t !== "bool") throw new ExecutionError("InvalidType");
380
+ return bool(l.v || r.v);
381
+ case B.Prefix:
382
+ if (l.t === "str" && r.t === "str") return bool(l.v.startsWith(r.v));
383
+ if (l.t === "array" && r.t === "array") return bool(r.v.every((x, i) => i < l.v.length && termEq(l.v[i], x)));
384
+ throw new ExecutionError("InvalidType");
385
+ case B.Suffix:
386
+ if (l.t === "str" && r.t === "str") return bool(l.v.endsWith(r.v));
387
+ if (l.t === "array" && r.t === "array") {
388
+ const off = l.v.length - r.v.length;
389
+ return bool(off >= 0 && r.v.every((x, i) => termEq(l.v[off + i], x)));
390
+ }
391
+ throw new ExecutionError("InvalidType");
392
+ case B.Regex:
393
+ if (l.t !== "str" || r.t !== "str") throw new ExecutionError("InvalidType");
394
+ try {
395
+ return bool(new RegExp(r.v).test(l.v));
396
+ } catch {
397
+ return bool(false);
398
+ }
399
+ case B.Contains:
400
+ if (l.t === "str" && r.t === "str") return bool(l.v.includes(r.v));
401
+ if (l.t === "set" && r.t === "set") return bool(r.v.every((x) => setContains(l.v, x)));
402
+ if (l.t === "set") return bool(setContains(l.v, r));
403
+ if (l.t === "array") return bool(setContains(l.v, r));
404
+ if (l.t === "map") return bool(l.v.some(([k]) => r.t === "int" ? k.t === "int" && k.v === r.v : r.t === "str" && k.t === "str" && k.v === r.v));
405
+ throw new ExecutionError("InvalidType");
406
+ case B.Intersection:
407
+ if (l.t !== "set" || r.t !== "set") throw new ExecutionError("InvalidType");
408
+ return {
409
+ t: "set",
410
+ v: normalizeSet(l.v.filter((x) => setContains(r.v, x)))
411
+ };
412
+ case B.Union:
413
+ if (l.t !== "set" || r.t !== "set") throw new ExecutionError("InvalidType");
414
+ return {
415
+ t: "set",
416
+ v: normalizeSet([...l.v, ...r.v])
417
+ };
418
+ case B.Get:
419
+ if (l.t === "array" && r.t === "int") {
420
+ const i = r.v < 0n || r.v >= BigInt(l.v.length) ? -1 : Number(r.v);
421
+ return i < 0 ? { t: "null" } : l.v[i];
422
+ }
423
+ if (l.t === "map" && (r.t === "int" || r.t === "str")) {
424
+ const found = l.v.find(([k]) => r.t === "int" ? k.t === "int" && k.v === r.v : k.t === "str" && k.v === r.v);
425
+ return found ? found[1] : { t: "null" };
426
+ }
427
+ throw new ExecutionError("InvalidType");
428
+ case B.Ffi: {
429
+ const f = externs.get(ffi);
430
+ if (!f) throw new ExecutionError("UndefinedExtern", ffi);
431
+ return f(l, r);
432
+ }
433
+ default: throw new ExecutionError("InvalidType");
434
+ }
435
+ }
436
+ function evaluateExpression(ops, values, externs = /* @__PURE__ */ new Map()) {
437
+ const stack = [];
438
+ for (const op of ops) switch (op.kind) {
439
+ case "value":
440
+ if (op.value.t === "var") {
441
+ const bound = values.get(op.value.v);
442
+ if (bound === void 0) throw new ExecutionError("UnknownVariable", String(op.value.v));
443
+ stack.push({
444
+ s: "term",
445
+ v: bound
446
+ });
447
+ } else stack.push({
448
+ s: "term",
449
+ v: op.value
450
+ });
451
+ break;
452
+ case "unary": {
453
+ const a = stack.pop();
454
+ if (!a || a.s !== "term") throw new ExecutionError("InvalidStack");
455
+ stack.push({
456
+ s: "term",
457
+ v: unary(op.op, op.ffi, a.v, externs)
458
+ });
459
+ break;
460
+ }
461
+ case "closure":
462
+ stack.push({
463
+ s: "closure",
464
+ params: op.params,
465
+ ops: op.ops
466
+ });
467
+ break;
468
+ case "binary": {
469
+ const right = stack.pop();
470
+ const left = stack.pop();
471
+ if (!right || !left) throw new ExecutionError("InvalidStack");
472
+ if (right.s === "term" && left.s === "term") stack.push({
473
+ s: "term",
474
+ v: binary(op.op, op.ffi, left.v, right.v, externs)
475
+ });
476
+ else {
477
+ const closure = right.s === "closure" ? right : left.s === "closure" ? left : null;
478
+ const term = right.s === "term" ? right : left.s === "term" ? left : null;
479
+ if (!closure || !term) throw new ExecutionError("InvalidStack");
480
+ for (const p of closure.params) if (values.has(p)) throw new ExecutionError("ShadowedVariable", String(p));
481
+ stack.push({
482
+ s: "term",
483
+ v: evaluateWithClosure(op.op, term.v, closure.ops, closure.params, values, externs)
484
+ });
485
+ }
486
+ break;
487
+ }
488
+ }
489
+ if (stack.length !== 1) throw new ExecutionError("InvalidStack");
490
+ const top = stack[0];
491
+ if (top.s !== "term") throw new ExecutionError("InvalidStack");
492
+ return top.v;
493
+ }
494
+ function evaluateWithClosure(op, left, ops, params, values, externs) {
495
+ if (op === B.TryOr && params.length === 0) try {
496
+ return evaluateExpression(ops, values, externs);
497
+ } catch {
498
+ return left;
499
+ }
500
+ if ((op === B.LazyOr || op === B.LazyAnd) && params.length === 0) {
501
+ if (left.t !== "bool") throw new ExecutionError("InvalidType");
502
+ if (op === B.LazyOr && left.v) return {
503
+ t: "bool",
504
+ v: true
505
+ };
506
+ if (op === B.LazyAnd && !left.v) return {
507
+ t: "bool",
508
+ v: false
509
+ };
510
+ return evaluateExpression(ops, values, externs);
511
+ }
512
+ if ((op === B.All || op === B.Any) && params.length === 1) {
513
+ const param = params[0];
514
+ let items;
515
+ if (left.t === "set" || left.t === "array") items = left.v;
516
+ else if (left.t === "map") items = left.v.map(([k, v]) => ({
517
+ t: "array",
518
+ v: [k.t === "int" ? {
519
+ t: "int",
520
+ v: k.v
521
+ } : {
522
+ t: "str",
523
+ v: k.v
524
+ }, v]
525
+ }));
526
+ else throw new ExecutionError("InvalidType");
527
+ const wanted = op === B.All;
528
+ for (const item of items) {
529
+ const scoped = new Map(values);
530
+ scoped.set(param, item);
531
+ const res = evaluateExpression(ops, scoped, externs);
532
+ if (res.t !== "bool") throw new ExecutionError("InvalidType");
533
+ if (res.v !== wanted) return {
534
+ t: "bool",
535
+ v: !wanted
536
+ };
537
+ }
538
+ return {
539
+ t: "bool",
540
+ v: wanted
541
+ };
542
+ }
543
+ throw new ExecutionError("InvalidType");
544
+ }
545
+ /**
546
+ * Note the divergence: the reference defaults `max_time` to **1 millisecond**,
547
+ * which is unreachably tight for a cold JS engine and would make ordinary
548
+ * tokens fail non-deterministically. 1 second is generous by comparison, so a
549
+ * caller exposed to untrusted tokens should lower it deliberately rather than
550
+ * rely on this default as a denial-of-service bound.
551
+ */
552
+ const DEFAULT_LIMITS = {
553
+ maxFacts: 1e3,
554
+ maxIterations: 100,
555
+ maxTimeMs: 1e3
556
+ };
557
+ function matchPredicate(rule, fact) {
558
+ if (rule.name !== fact.name || rule.terms.length !== fact.terms.length) return false;
559
+ for (let i = 0; i < rule.terms.length; i++) {
560
+ const rt = rule.terms[i];
561
+ const ft = fact.terms[i];
562
+ if (ft.t === "var") return false;
563
+ if (rt.t === "var") continue;
564
+ if (rt.t !== ft.t || !termEq(rt, ft)) return false;
565
+ }
566
+ return true;
567
+ }
568
+ function variablesOf(rule) {
569
+ const out = /* @__PURE__ */ new Set();
570
+ for (const p of rule.body) for (const t of p.terms) if (t.t === "var") out.add(t.v);
571
+ return out;
572
+ }
573
+ /** backtracking join over the rule body, mirroring Rust's CombineIt */
574
+ function* combine(predicates, facts, bindings, variables) {
575
+ if (predicates.length === 0) {
576
+ for (const v of variables) if (!bindings.has(v)) return;
577
+ yield [Origin.of(), new Map(bindings)];
578
+ return;
579
+ }
580
+ const [head, ...rest] = predicates;
581
+ for (const [origin, fact] of facts.get(`${head.name}/${head.terms.length}`) ?? []) {
582
+ if (!matchPredicate(head, fact.predicate)) continue;
583
+ const next = new Map(bindings);
584
+ let ok = true;
585
+ for (let i = 0; i < head.terms.length; i++) {
586
+ const rt = head.terms[i];
587
+ if (rt.t !== "var") continue;
588
+ const existing = next.get(rt.v);
589
+ if (existing === void 0) next.set(rt.v, fact.predicate.terms[i]);
590
+ else if (!termEq(existing, fact.predicate.terms[i])) {
591
+ ok = false;
592
+ break;
593
+ }
594
+ }
595
+ if (!ok) continue;
596
+ for (const [subOrigin, result] of combine(rest, facts, next, variables)) yield [subOrigin.union(origin), result];
597
+ }
598
+ }
599
+ var World = class {
600
+ /** facts indexed by origin key */
601
+ facts = /* @__PURE__ */ new Map();
602
+ rules = [];
603
+ externs = /* @__PURE__ */ new Map();
604
+ iterations = 0;
605
+ generation = 0;
606
+ indexCache = /* @__PURE__ */ new Map();
607
+ addFact(origin, fact) {
608
+ let bucket = this.facts.get(origin.key);
609
+ if (!bucket) {
610
+ bucket = {
611
+ origin,
612
+ items: /* @__PURE__ */ new Map()
613
+ };
614
+ this.facts.set(origin.key, bucket);
615
+ }
616
+ const key = factKey(fact);
617
+ if (!bucket.items.has(key)) {
618
+ bucket.items.set(key, fact);
619
+ this.generation++;
620
+ }
621
+ }
622
+ addRule(origin, trusted, rule) {
623
+ this.rules.push({
624
+ origin,
625
+ trusted,
626
+ rule
627
+ });
628
+ }
629
+ factCount() {
630
+ let n = 0;
631
+ for (const b of this.facts.values()) n += b.items.size;
632
+ return n;
633
+ }
634
+ visible(trusted) {
635
+ const cached = this.indexCache.get(trusted.key);
636
+ if (cached && cached.generation === this.generation) return cached.index;
637
+ const index = /* @__PURE__ */ new Map();
638
+ for (const bucket of this.facts.values()) {
639
+ if (!trusted.contains(bucket.origin)) continue;
640
+ for (const f of bucket.items.values()) {
641
+ const key = `${f.predicate.name}/${f.predicate.terms.length}`;
642
+ const list = index.get(key);
643
+ if (list) list.push([bucket.origin, f]);
644
+ else index.set(key, [[bucket.origin, f]]);
645
+ }
646
+ }
647
+ this.indexCache.set(trusted.key, {
648
+ generation: this.generation,
649
+ index
650
+ });
651
+ return index;
652
+ }
653
+ /** naive fixpoint: apply every rule until no new fact appears */
654
+ run(limits = DEFAULT_LIMITS) {
655
+ const deadline = Date.now() + limits.maxTimeMs;
656
+ let index = 0;
657
+ for (;;) {
658
+ const generated = [];
659
+ for (const { origin, trusted, rule } of this.rules) {
660
+ const facts = this.visible(trusted);
661
+ for (const [o, f] of this.apply(rule, facts, origin)) generated.push([o, f]);
662
+ }
663
+ const before = this.factCount();
664
+ for (const [o, f] of generated) this.addFact(o, f);
665
+ if (this.factCount() === before) break;
666
+ index++;
667
+ if (index === limits.maxIterations) throw new ExecutionError("TooManyIterations");
668
+ if (this.factCount() >= limits.maxFacts) throw new ExecutionError("TooManyFacts");
669
+ if (Date.now() >= deadline) throw new ExecutionError("Timeout");
670
+ }
671
+ this.iterations += index;
672
+ }
673
+ *apply(rule, facts, ruleOrigin) {
674
+ const variables = variablesOf(rule);
675
+ for (const [origin, bindings] of combine(rule.body, facts, /* @__PURE__ */ new Map(), variables)) {
676
+ let pass = true;
677
+ for (const ops of rule.expressions) {
678
+ const res = evaluateExpression(ops, bindings, this.externs);
679
+ if (res.t !== "bool") throw new ExecutionError("InvalidType");
680
+ if (!res.v) {
681
+ pass = false;
682
+ break;
683
+ }
684
+ }
685
+ if (!pass) continue;
686
+ const terms = [];
687
+ let complete = true;
688
+ for (const t of rule.head.terms) if (t.t === "var") {
689
+ const bound = bindings.get(t.v);
690
+ if (bound === void 0) {
691
+ complete = false;
692
+ break;
693
+ }
694
+ terms.push(bound);
695
+ } else terms.push(t);
696
+ if (!complete) continue;
697
+ yield [origin.with(ruleOrigin), { predicate: {
698
+ name: rule.head.name,
699
+ terms
700
+ } }];
701
+ }
702
+ }
703
+ /** `check if` / policies: does at least one combination match? */
704
+ queryMatch(rule, origin, trusted) {
705
+ for (const _ of this.apply(rule, this.visible(trusted), origin)) return true;
706
+ return false;
707
+ }
708
+ /** `check all`: every matching combination must satisfy the expressions */
709
+ queryMatchAll(rule, trusted) {
710
+ const variables = variablesOf(rule);
711
+ let found = false;
712
+ for (const [, bindings] of combine(rule.body, this.visible(trusted), /* @__PURE__ */ new Map(), variables)) {
713
+ found = true;
714
+ for (const ops of rule.expressions) {
715
+ const res = evaluateExpression(ops, bindings, this.externs);
716
+ if (res.t !== "bool") throw new ExecutionError("InvalidType");
717
+ if (!res.v) return false;
718
+ }
719
+ }
720
+ return found;
721
+ }
722
+ };
723
+ //#endregion
724
+ //#region src/parser.ts
725
+ /**
726
+ * Parser for the Biscuit Datalog text syntax (SPECIFICATIONS.md grammar).
727
+ * Hand-written recursive descent + precedence climbing; expressions are
728
+ * compiled straight to the opcode form the VM executes.
729
+ */
730
+ var ParseError = class extends Error {};
731
+ const NAME_START = /[\p{L}]/u;
732
+ const NAME_CHAR = /[\p{L}\p{N}_:]/u;
733
+ var Parser = class {
734
+ src;
735
+ i = 0;
736
+ vars;
737
+ constructor(src, vars) {
738
+ this.src = src;
739
+ this.vars = vars ?? /* @__PURE__ */ new Map();
740
+ }
741
+ /** id -> name, for printing rules back out */
742
+ variableNames() {
743
+ const out = /* @__PURE__ */ new Map();
744
+ for (const [name, id] of this.vars) out.set(id, name);
745
+ return out;
746
+ }
747
+ varId(name) {
748
+ let id = this.vars.get(name);
749
+ if (id === void 0) {
750
+ id = this.vars.size + 1;
751
+ this.vars.set(name, id);
752
+ }
753
+ return id;
754
+ }
755
+ ws() {
756
+ for (;;) {
757
+ while (this.i < this.src.length && /\s/.test(this.src[this.i])) this.i++;
758
+ if (this.src.startsWith("//", this.i)) {
759
+ const nl = this.src.indexOf("\n", this.i);
760
+ this.i = nl === -1 ? this.src.length : nl + 1;
761
+ continue;
762
+ }
763
+ if (this.src.startsWith("/*", this.i)) {
764
+ const end = this.src.indexOf("*/", this.i);
765
+ this.i = end === -1 ? this.src.length : end + 2;
766
+ continue;
767
+ }
768
+ return;
769
+ }
770
+ }
771
+ get eof() {
772
+ this.ws();
773
+ return this.i >= this.src.length;
774
+ }
775
+ peek(s) {
776
+ this.ws();
777
+ return this.src.startsWith(s, this.i);
778
+ }
779
+ eat(s) {
780
+ if (!this.peek(s)) return false;
781
+ this.i += s.length;
782
+ return true;
783
+ }
784
+ expect(s) {
785
+ if (!this.eat(s)) throw new ParseError(`expected "${s}" at offset ${this.i}`);
786
+ }
787
+ /** keyword: matches only if not followed by a name character */
788
+ keyword(k) {
789
+ this.ws();
790
+ if (!this.src.startsWith(k, this.i)) return false;
791
+ const after = this.src[this.i + k.length];
792
+ if (after !== void 0 && NAME_CHAR.test(after)) return false;
793
+ this.i += k.length;
794
+ return true;
795
+ }
796
+ /** variable names, unlike predicate names, may start with a digit ($0) */
797
+ variableName() {
798
+ this.ws();
799
+ const start = this.i;
800
+ while (this.i < this.src.length && NAME_CHAR.test(this.src[this.i])) this.i++;
801
+ if (this.i === start) throw new ParseError(`expected a variable name at offset ${this.i}`);
802
+ return this.src.slice(start, this.i);
803
+ }
804
+ name() {
805
+ this.ws();
806
+ const start = this.i;
807
+ if (this.i >= this.src.length || !NAME_START.test(this.src[this.i])) throw new ParseError(`expected a name at offset ${this.i}`);
808
+ this.i++;
809
+ while (this.i < this.src.length && NAME_CHAR.test(this.src[this.i])) this.i++;
810
+ return this.src.slice(start, this.i);
811
+ }
812
+ string() {
813
+ this.expect("\"");
814
+ let out = "";
815
+ for (;;) {
816
+ if (this.i >= this.src.length) throw new ParseError("unterminated string");
817
+ const c = this.src[this.i++];
818
+ if (c === "\"") return out;
819
+ if (c !== "\\") {
820
+ out += c;
821
+ continue;
822
+ }
823
+ const e = this.src[this.i++];
824
+ if (e === "n") out += "\n";
825
+ else if (e === "t") out += " ";
826
+ else if (e === "r") out += "\r";
827
+ else if (e === "0") out += "\0";
828
+ else if (e === "\"") out += "\"";
829
+ else if (e === "\\") out += "\\";
830
+ else if (e === "u") {
831
+ const m = /^\{([0-9a-fA-F]+)\}/.exec(this.src.slice(this.i));
832
+ if (m) {
833
+ out += String.fromCodePoint(parseInt(m[1], 16));
834
+ this.i += m[0].length;
835
+ } else {
836
+ out += String.fromCharCode(parseInt(this.src.substr(this.i, 4), 16));
837
+ this.i += 4;
838
+ }
839
+ } else out += e;
840
+ }
841
+ }
842
+ tryDate() {
843
+ this.ws();
844
+ const m = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})/.exec(this.src.slice(this.i));
845
+ if (!m) return null;
846
+ const ms = Date.parse(m[0]);
847
+ if (Number.isNaN(ms)) throw new ParseError(`invalid date ${m[0]}`);
848
+ this.i += m[0].length;
849
+ return BigInt(Math.floor(ms / 1e3));
850
+ }
851
+ /** a term that may be a variable (rule/check context) */
852
+ term(allowVariables = true) {
853
+ this.ws();
854
+ const date = this.tryDate();
855
+ if (date !== null) return {
856
+ t: "date",
857
+ v: date
858
+ };
859
+ if (this.peek("\"")) return {
860
+ t: "str",
861
+ v: this.string()
862
+ };
863
+ if (this.eat("$")) {
864
+ if (!allowVariables) throw new ParseError("variables are not allowed in facts");
865
+ return {
866
+ t: "var",
867
+ v: this.varId(this.variableName())
868
+ };
869
+ }
870
+ if (this.keyword("true")) return {
871
+ t: "bool",
872
+ v: true
873
+ };
874
+ if (this.keyword("false")) return {
875
+ t: "bool",
876
+ v: false
877
+ };
878
+ if (this.keyword("null")) return { t: "null" };
879
+ if (this.peek("hex:")) {
880
+ this.i += 4;
881
+ const start = this.i;
882
+ while (this.i < this.src.length && /[0-9a-fA-F]/.test(this.src[this.i])) this.i++;
883
+ const h = this.src.slice(start, this.i);
884
+ if (h.length % 2) throw new ParseError("odd-length hex literal");
885
+ const bytes = new Uint8Array(h.length / 2);
886
+ for (let j = 0; j < bytes.length; j++) bytes[j] = parseInt(h.substr(j * 2, 2), 16);
887
+ return {
888
+ t: "bytes",
889
+ v: bytes
890
+ };
891
+ }
892
+ if (this.peek("[")) return this.array(allowVariables);
893
+ if (this.peek("{")) return this.setOrMap(allowVariables);
894
+ const m = /^-?\d+/.exec(this.src.slice(this.i));
895
+ if (m) {
896
+ this.i += m[0].length;
897
+ return {
898
+ t: "int",
899
+ v: BigInt(m[0])
900
+ };
901
+ }
902
+ throw new ParseError(`unexpected term at offset ${this.i}: ${this.src.slice(this.i, this.i + 20)}`);
903
+ }
904
+ array(allowVariables) {
905
+ this.expect("[");
906
+ const items = [];
907
+ if (this.eat("]")) return {
908
+ t: "array",
909
+ v: items
910
+ };
911
+ do
912
+ items.push(this.term(allowVariables));
913
+ while (this.eat(","));
914
+ this.expect("]");
915
+ return {
916
+ t: "array",
917
+ v: items
918
+ };
919
+ }
920
+ setOrMap(allowVariables) {
921
+ this.expect("{");
922
+ if (this.eat(",")) {
923
+ this.expect("}");
924
+ return {
925
+ t: "set",
926
+ v: []
927
+ };
928
+ }
929
+ if (this.eat("}")) return {
930
+ t: "map",
931
+ v: []
932
+ };
933
+ const first = this.term(allowVariables);
934
+ if (this.eat(":")) {
935
+ const entries = [[this.asMapKey(first), this.term(allowVariables)]];
936
+ while (this.eat(",")) {
937
+ if (this.peek("}")) break;
938
+ const k = this.asMapKey(this.term(allowVariables));
939
+ this.expect(":");
940
+ entries.push([k, this.term(allowVariables)]);
941
+ }
942
+ this.expect("}");
943
+ return {
944
+ t: "map",
945
+ v: entries
946
+ };
947
+ }
948
+ const items = [first];
949
+ while (this.eat(",")) {
950
+ if (this.peek("}")) break;
951
+ items.push(this.term(allowVariables));
952
+ }
953
+ this.expect("}");
954
+ return {
955
+ t: "set",
956
+ v: normalizeSet(items)
957
+ };
958
+ }
959
+ asMapKey(t) {
960
+ if (t.t === "int") return {
961
+ t: "int",
962
+ v: t.v
963
+ };
964
+ if (t.t === "str") return {
965
+ t: "str",
966
+ v: t.v
967
+ };
968
+ throw new ParseError("map keys must be integers or strings");
969
+ }
970
+ predicate(allowVariables = true) {
971
+ const name = this.name();
972
+ this.expect("(");
973
+ const terms = [];
974
+ if (!this.peek(")")) do
975
+ terms.push(this.term(allowVariables));
976
+ while (this.eat(","));
977
+ this.expect(")");
978
+ return {
979
+ name,
980
+ terms
981
+ };
982
+ }
983
+ /** precedence climbing; each level returns opcodes in postfix order */
984
+ expression() {
985
+ return this.orExpr();
986
+ }
987
+ orExpr() {
988
+ let left = this.andExpr();
989
+ while (this.peek("||")) {
990
+ this.i += 2;
991
+ const right = this.andExpr();
992
+ left = [
993
+ ...left,
994
+ {
995
+ kind: "closure",
996
+ params: [],
997
+ ops: right
998
+ },
999
+ {
1000
+ kind: "binary",
1001
+ op: BinaryOp.LazyOr
1002
+ }
1003
+ ];
1004
+ }
1005
+ return left;
1006
+ }
1007
+ andExpr() {
1008
+ let left = this.comparison();
1009
+ while (this.peek("&&")) {
1010
+ this.i += 2;
1011
+ const right = this.comparison();
1012
+ left = [
1013
+ ...left,
1014
+ {
1015
+ kind: "closure",
1016
+ params: [],
1017
+ ops: right
1018
+ },
1019
+ {
1020
+ kind: "binary",
1021
+ op: BinaryOp.LazyAnd
1022
+ }
1023
+ ];
1024
+ }
1025
+ return left;
1026
+ }
1027
+ comparison() {
1028
+ const left = this.bitXor();
1029
+ for (const [tok, op] of [
1030
+ ["<=", BinaryOp.LessOrEqual],
1031
+ [">=", BinaryOp.GreaterOrEqual],
1032
+ ["===", BinaryOp.Equal],
1033
+ ["!==", BinaryOp.NotEqual],
1034
+ ["==", BinaryOp.HeterogeneousEqual],
1035
+ ["!=", BinaryOp.HeterogeneousNotEqual],
1036
+ ["<", BinaryOp.LessThan],
1037
+ [">", BinaryOp.GreaterThan]
1038
+ ]) if (this.peekOperator(tok)) {
1039
+ this.i += tok.length;
1040
+ const right = this.bitXor();
1041
+ return [
1042
+ ...left,
1043
+ ...right,
1044
+ {
1045
+ kind: "binary",
1046
+ op
1047
+ }
1048
+ ];
1049
+ }
1050
+ return left;
1051
+ }
1052
+ /** avoids matching "==" when the source really has "===" */
1053
+ peekOperator(tok) {
1054
+ this.ws();
1055
+ if (!this.src.startsWith(tok, this.i)) return false;
1056
+ const next = this.src[this.i + tok.length];
1057
+ if ((tok === "==" || tok === "!=") && next === "=") return false;
1058
+ if ((tok === "<" || tok === ">") && next === "=") return false;
1059
+ return true;
1060
+ }
1061
+ bitXor() {
1062
+ let left = this.bitOr();
1063
+ while (this.peekOperator("^")) {
1064
+ this.i += 1;
1065
+ left = [
1066
+ ...left,
1067
+ ...this.bitOr(),
1068
+ {
1069
+ kind: "binary",
1070
+ op: BinaryOp.BitwiseXor
1071
+ }
1072
+ ];
1073
+ }
1074
+ return left;
1075
+ }
1076
+ bitOr() {
1077
+ let left = this.bitAnd();
1078
+ while (this.peek("|") && !this.peek("||")) {
1079
+ this.i += 1;
1080
+ left = [
1081
+ ...left,
1082
+ ...this.bitAnd(),
1083
+ {
1084
+ kind: "binary",
1085
+ op: BinaryOp.BitwiseOr
1086
+ }
1087
+ ];
1088
+ }
1089
+ return left;
1090
+ }
1091
+ bitAnd() {
1092
+ let left = this.additive();
1093
+ while (this.peek("&") && !this.peek("&&")) {
1094
+ this.i += 1;
1095
+ left = [
1096
+ ...left,
1097
+ ...this.additive(),
1098
+ {
1099
+ kind: "binary",
1100
+ op: BinaryOp.BitwiseAnd
1101
+ }
1102
+ ];
1103
+ }
1104
+ return left;
1105
+ }
1106
+ additive() {
1107
+ let left = this.multiplicative();
1108
+ for (;;) if (this.peekSign("+")) {
1109
+ this.i += 1;
1110
+ left = [
1111
+ ...left,
1112
+ ...this.multiplicative(),
1113
+ {
1114
+ kind: "binary",
1115
+ op: BinaryOp.Add
1116
+ }
1117
+ ];
1118
+ } else if (this.peekSign("-")) {
1119
+ this.i += 1;
1120
+ left = [
1121
+ ...left,
1122
+ ...this.multiplicative(),
1123
+ {
1124
+ kind: "binary",
1125
+ op: BinaryOp.Sub
1126
+ }
1127
+ ];
1128
+ } else return left;
1129
+ }
1130
+ /** `-` starts a negative literal only when it is not an infix position */
1131
+ peekSign(tok) {
1132
+ this.ws();
1133
+ return this.src.startsWith(tok, this.i);
1134
+ }
1135
+ multiplicative() {
1136
+ let left = this.unary();
1137
+ for (;;) if (this.peek("*")) {
1138
+ this.i += 1;
1139
+ left = [
1140
+ ...left,
1141
+ ...this.unary(),
1142
+ {
1143
+ kind: "binary",
1144
+ op: BinaryOp.Mul
1145
+ }
1146
+ ];
1147
+ } else if (this.peek("/")) {
1148
+ this.i += 1;
1149
+ left = [
1150
+ ...left,
1151
+ ...this.unary(),
1152
+ {
1153
+ kind: "binary",
1154
+ op: BinaryOp.Div
1155
+ }
1156
+ ];
1157
+ } else return left;
1158
+ }
1159
+ unary() {
1160
+ if (this.eat("!")) return [...this.unary(), {
1161
+ kind: "unary",
1162
+ op: UnaryOp.Negate
1163
+ }];
1164
+ return this.methods(this.primary());
1165
+ }
1166
+ primary() {
1167
+ this.ws();
1168
+ if (this.peek("(")) {
1169
+ this.i += 1;
1170
+ const inner = this.expression();
1171
+ this.expect(")");
1172
+ return inner;
1173
+ }
1174
+ return [{
1175
+ kind: "value",
1176
+ value: this.term()
1177
+ }];
1178
+ }
1179
+ methods(target) {
1180
+ let out = target;
1181
+ while (this.peek(".")) {
1182
+ this.i += 1;
1183
+ if (this.peek("extern::")) {
1184
+ this.i += 8;
1185
+ const fn = this.name();
1186
+ this.expect("(");
1187
+ if (this.eat(")")) out = [...out, {
1188
+ kind: "unary",
1189
+ op: UnaryOp.Ffi,
1190
+ ffi: fn
1191
+ }];
1192
+ else {
1193
+ const arg = this.expression();
1194
+ this.expect(")");
1195
+ out = [
1196
+ ...out,
1197
+ ...arg,
1198
+ {
1199
+ kind: "binary",
1200
+ op: BinaryOp.Ffi,
1201
+ ffi: fn
1202
+ }
1203
+ ];
1204
+ }
1205
+ continue;
1206
+ }
1207
+ const method = this.name();
1208
+ this.expect("(");
1209
+ switch (method) {
1210
+ case "length":
1211
+ this.expect(")");
1212
+ out = [...out, {
1213
+ kind: "unary",
1214
+ op: UnaryOp.Length
1215
+ }];
1216
+ break;
1217
+ case "type":
1218
+ this.expect(")");
1219
+ out = [...out, {
1220
+ kind: "unary",
1221
+ op: UnaryOp.TypeOf
1222
+ }];
1223
+ break;
1224
+ case "any":
1225
+ case "all": {
1226
+ const closure = this.closure();
1227
+ this.expect(")");
1228
+ out = [
1229
+ ...out,
1230
+ closure,
1231
+ {
1232
+ kind: "binary",
1233
+ op: method === "any" ? BinaryOp.Any : BinaryOp.All
1234
+ }
1235
+ ];
1236
+ break;
1237
+ }
1238
+ case "try_or": {
1239
+ const fallback = this.expression();
1240
+ this.expect(")");
1241
+ out = [
1242
+ {
1243
+ kind: "closure",
1244
+ params: [],
1245
+ ops: out
1246
+ },
1247
+ ...fallback,
1248
+ {
1249
+ kind: "binary",
1250
+ op: BinaryOp.TryOr
1251
+ }
1252
+ ];
1253
+ break;
1254
+ }
1255
+ default: {
1256
+ const arg = this.expression();
1257
+ this.expect(")");
1258
+ const op = {
1259
+ contains: BinaryOp.Contains,
1260
+ starts_with: BinaryOp.Prefix,
1261
+ ends_with: BinaryOp.Suffix,
1262
+ matches: BinaryOp.Regex,
1263
+ intersection: BinaryOp.Intersection,
1264
+ union: BinaryOp.Union,
1265
+ get: BinaryOp.Get
1266
+ }[method];
1267
+ if (op === void 0) throw new ParseError(`unknown method ${method}`);
1268
+ out = [
1269
+ ...out,
1270
+ ...arg,
1271
+ {
1272
+ kind: "binary",
1273
+ op
1274
+ }
1275
+ ];
1276
+ }
1277
+ }
1278
+ }
1279
+ return out;
1280
+ }
1281
+ closure() {
1282
+ const params = [];
1283
+ this.ws();
1284
+ if (this.eat("(")) this.expect(")");
1285
+ else do {
1286
+ this.expect("$");
1287
+ params.push(this.varId(this.variableName()));
1288
+ } while (this.eat(","));
1289
+ this.expect("->");
1290
+ return {
1291
+ kind: "closure",
1292
+ params,
1293
+ ops: this.expression()
1294
+ };
1295
+ }
1296
+ scopes() {
1297
+ const out = [];
1298
+ do {
1299
+ this.ws();
1300
+ if (this.keyword("authority")) out.push({ kind: "authority" });
1301
+ else if (this.keyword("previous")) out.push({ kind: "previous" });
1302
+ else {
1303
+ const alg = this.name();
1304
+ this.expect("/");
1305
+ const start = this.i;
1306
+ while (this.i < this.src.length && /[0-9a-fA-F]/.test(this.src[this.i])) this.i++;
1307
+ out.push({
1308
+ kind: "publicKey",
1309
+ key: `${alg}/${this.src.slice(start, this.i).toLowerCase()}`
1310
+ });
1311
+ }
1312
+ } while (this.eat(","));
1313
+ return out;
1314
+ }
1315
+ /** body of a rule / check / policy: predicates, expressions, trusting */
1316
+ ruleBody() {
1317
+ const body = [];
1318
+ const expressions = [];
1319
+ let scopes = [];
1320
+ do {
1321
+ this.ws();
1322
+ if (this.keyword("trusting")) {
1323
+ scopes = this.scopes();
1324
+ break;
1325
+ }
1326
+ const save = this.i;
1327
+ const asPredicate = this.tryPredicate();
1328
+ if (asPredicate) body.push(asPredicate);
1329
+ else {
1330
+ this.i = save;
1331
+ expressions.push(this.expression());
1332
+ }
1333
+ this.ws();
1334
+ if (this.keyword("trusting")) {
1335
+ scopes = this.scopes();
1336
+ break;
1337
+ }
1338
+ } while (this.eat(","));
1339
+ return {
1340
+ body,
1341
+ expressions,
1342
+ scopes
1343
+ };
1344
+ }
1345
+ /** a predicate is only a predicate if nothing operator-like follows it */
1346
+ tryPredicate() {
1347
+ const save = this.i;
1348
+ try {
1349
+ this.ws();
1350
+ if (!NAME_START.test(this.src[this.i] ?? "")) return null;
1351
+ const p = this.predicate();
1352
+ this.ws();
1353
+ const rest = this.src.slice(this.i);
1354
+ if (rest === "" || /^[,;)]/.test(rest) || /^(or|trusting)\b/.test(rest)) return p;
1355
+ this.i = save;
1356
+ return null;
1357
+ } catch {
1358
+ this.i = save;
1359
+ return null;
1360
+ }
1361
+ }
1362
+ queries() {
1363
+ const out = [];
1364
+ do {
1365
+ const { body, expressions, scopes } = this.ruleBody();
1366
+ out.push({
1367
+ head: {
1368
+ name: "query",
1369
+ terms: []
1370
+ },
1371
+ body,
1372
+ expressions,
1373
+ scopes
1374
+ });
1375
+ } while (this.keyword("or"));
1376
+ return out;
1377
+ }
1378
+ statement() {
1379
+ this.ws();
1380
+ if (this.keyword("check")) {
1381
+ let kind;
1382
+ if (this.keyword("if")) kind = "one";
1383
+ else if (this.keyword("all")) kind = "all";
1384
+ else throw new ParseError("expected \"if\" or \"all\" after \"check\"");
1385
+ return {
1386
+ k: "check",
1387
+ check: {
1388
+ queries: this.queries(),
1389
+ kind
1390
+ }
1391
+ };
1392
+ }
1393
+ if (this.keyword("reject")) {
1394
+ if (!this.keyword("if")) throw new ParseError("expected \"if\" after \"reject\"");
1395
+ return {
1396
+ k: "check",
1397
+ check: {
1398
+ queries: this.queries(),
1399
+ kind: "reject"
1400
+ }
1401
+ };
1402
+ }
1403
+ if (this.keyword("allow")) {
1404
+ if (!this.keyword("if")) throw new ParseError("expected \"if\" after \"allow\"");
1405
+ return {
1406
+ k: "policy",
1407
+ kind: "allow",
1408
+ queries: this.queries()
1409
+ };
1410
+ }
1411
+ if (this.keyword("deny")) {
1412
+ if (!this.keyword("if")) throw new ParseError("expected \"if\" after \"deny\"");
1413
+ return {
1414
+ k: "policy",
1415
+ kind: "deny",
1416
+ queries: this.queries()
1417
+ };
1418
+ }
1419
+ if (this.keyword("trusting")) return {
1420
+ k: "blockScope",
1421
+ scopes: this.scopes()
1422
+ };
1423
+ const head = this.predicate();
1424
+ this.ws();
1425
+ if (this.eat("<-")) {
1426
+ const { body, expressions, scopes } = this.ruleBody();
1427
+ return {
1428
+ k: "rule",
1429
+ rule: {
1430
+ head,
1431
+ body,
1432
+ expressions,
1433
+ scopes
1434
+ }
1435
+ };
1436
+ }
1437
+ if (head.terms.some((t) => t.t === "var")) throw new ParseError("a fact cannot contain variables");
1438
+ return {
1439
+ k: "fact",
1440
+ fact: { predicate: head }
1441
+ };
1442
+ }
1443
+ parse() {
1444
+ const out = [];
1445
+ while (!this.eof) {
1446
+ out.push(this.statement());
1447
+ this.ws();
1448
+ this.expect(";");
1449
+ }
1450
+ return out;
1451
+ }
1452
+ };
1453
+ //#endregion
1454
+ //#region src/print.ts
1455
+ /** Datalog pretty-printer (used for error messages and world snapshots). */
1456
+ const hex$1 = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
1457
+ /** Datalog string literal: only the quote and the backslash are escaped.
1458
+ * Control characters are emitted raw, unlike JSON.stringify. */
1459
+ const quote = (v) => `"${v.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
1460
+ function printTerm(t, varName) {
1461
+ switch (t.t) {
1462
+ case "var": return `$${varName(t.v)}`;
1463
+ case "int": return t.v.toString();
1464
+ case "str": return quote(t.v);
1465
+ case "date": return (/* @__PURE__ */ new Date(Number(t.v) * 1e3)).toISOString().replace(".000Z", "Z");
1466
+ case "bytes": return `hex:${hex$1(t.v)}`;
1467
+ case "bool": return t.v ? "true" : "false";
1468
+ case "null": return "null";
1469
+ case "set": return t.v.length === 0 ? "{,}" : `{${t.v.map((x) => printTerm(x, varName)).join(", ")}}`;
1470
+ case "array": return `[${t.v.map((x) => printTerm(x, varName)).join(", ")}]`;
1471
+ case "map": return t.v.length === 0 ? "{}" : `{${t.v.map(([k, v]) => `${k.t === "int" ? k.v : quote(k.v)}: ${printTerm(v, varName)}`).join(", ")}}`;
1472
+ }
1473
+ }
1474
+ const printPredicate = (p, varName) => `${p.name}(${p.terms.map((t) => printTerm(t, varName)).join(", ")})`;
1475
+ const UNARY = {
1476
+ [UnaryOp.Negate]: (a) => `!${a}`,
1477
+ [UnaryOp.Parens]: (a) => `(${a})`,
1478
+ [UnaryOp.Length]: (a) => `${a}.length()`,
1479
+ [UnaryOp.TypeOf]: (a) => `${a}.type()`
1480
+ };
1481
+ const BINARY = {
1482
+ [BinaryOp.LessThan]: (a, b) => `${a} < ${b}`,
1483
+ [BinaryOp.GreaterThan]: (a, b) => `${a} > ${b}`,
1484
+ [BinaryOp.LessOrEqual]: (a, b) => `${a} <= ${b}`,
1485
+ [BinaryOp.GreaterOrEqual]: (a, b) => `${a} >= ${b}`,
1486
+ [BinaryOp.Equal]: (a, b) => `${a} === ${b}`,
1487
+ [BinaryOp.NotEqual]: (a, b) => `${a} !== ${b}`,
1488
+ [BinaryOp.HeterogeneousEqual]: (a, b) => `${a} == ${b}`,
1489
+ [BinaryOp.HeterogeneousNotEqual]: (a, b) => `${a} != ${b}`,
1490
+ [BinaryOp.Contains]: (a, b) => `${a}.contains(${b})`,
1491
+ [BinaryOp.Prefix]: (a, b) => `${a}.starts_with(${b})`,
1492
+ [BinaryOp.Suffix]: (a, b) => `${a}.ends_with(${b})`,
1493
+ [BinaryOp.Regex]: (a, b) => `${a}.matches(${b})`,
1494
+ [BinaryOp.Add]: (a, b) => `${a} + ${b}`,
1495
+ [BinaryOp.Sub]: (a, b) => `${a} - ${b}`,
1496
+ [BinaryOp.Mul]: (a, b) => `${a} * ${b}`,
1497
+ [BinaryOp.Div]: (a, b) => `${a} / ${b}`,
1498
+ [BinaryOp.And]: (a, b) => `${a} &&! ${b}`,
1499
+ [BinaryOp.Or]: (a, b) => `${a} ||! ${b}`,
1500
+ [BinaryOp.Intersection]: (a, b) => `${a}.intersection(${b})`,
1501
+ [BinaryOp.Union]: (a, b) => `${a}.union(${b})`,
1502
+ [BinaryOp.BitwiseAnd]: (a, b) => `${a} & ${b}`,
1503
+ [BinaryOp.BitwiseOr]: (a, b) => `${a} | ${b}`,
1504
+ [BinaryOp.BitwiseXor]: (a, b) => `${a} ^ ${b}`,
1505
+ [BinaryOp.LazyAnd]: (a, b) => `${a} && ${b}`,
1506
+ [BinaryOp.LazyOr]: (a, b) => `${a} || ${b}`,
1507
+ [BinaryOp.All]: (a, b) => `${a}.all(${b})`,
1508
+ [BinaryOp.Any]: (a, b) => `${a}.any(${b})`,
1509
+ [BinaryOp.Get]: (a, b) => `${a}.get(${b})`,
1510
+ [BinaryOp.TryOr]: (a, b) => `${a}.try_or(${b})`
1511
+ };
1512
+ function printExpression(ops, varName) {
1513
+ const stack = [];
1514
+ for (const op of ops) if (op.kind === "value") stack.push(printTerm(op.value, varName));
1515
+ else if (op.kind === "closure") stack.push(`${op.params.map((p) => `$${varName(p)}`).join(", ")}${op.params.length ? " -> " : ""}${printExpression(op.ops, varName)}`);
1516
+ else if (op.kind === "unary") {
1517
+ const a = stack.pop() ?? "";
1518
+ stack.push(op.ffi ? `${a}.extern::${op.ffi}()` : UNARY[op.op]?.(a) ?? a);
1519
+ } else {
1520
+ const b = stack.pop() ?? "";
1521
+ const a = stack.pop() ?? "";
1522
+ stack.push(op.ffi ? `${a}.extern::${op.ffi}(${b})` : BINARY[op.op]?.(a, b) ?? `${a} ? ${b}`);
1523
+ }
1524
+ return stack.join(" ");
1525
+ }
1526
+ function printScopes(scopes) {
1527
+ if (scopes.length === 0) return "";
1528
+ return ` trusting ${scopes.map((s) => s.kind === "authority" ? "authority" : s.kind === "previous" ? "previous" : s.key).join(", ")}`;
1529
+ }
1530
+ /** the body of a rule, check or policy: predicates, then expressions */
1531
+ function printQuery(r, varName) {
1532
+ return [...r.body.map((p) => printPredicate(p, varName)), ...r.expressions.map((e) => printExpression(e, varName))].join(", ") + printScopes(r.scopes);
1533
+ }
1534
+ function printCheck(c, varName) {
1535
+ return (c.kind === "all" ? "check all " : c.kind === "reject" ? "reject if " : "check if ") + c.queries.map((q) => printQuery(q, varName)).join(" or ");
1536
+ }
1537
+ function printPolicy(kind, queries, varName) {
1538
+ return `${kind} if ` + queries.map((q) => printQuery(q, varName)).join(" or ");
1539
+ }
1540
+ function printRule(r, varName) {
1541
+ const parts = [...r.body.map((p) => printPredicate(p, varName)), ...r.expressions.map((e) => printExpression(e, varName))];
1542
+ return `${printPredicate(r.head, varName)} <- ${parts.join(", ")}` + printScopes(r.scopes);
1543
+ }
1544
+ //#endregion
1545
+ //#region src/proto.ts
1546
+ /**
1547
+ * Strict proto2 codec for the Biscuit schema.
1548
+ *
1549
+ * Written against src/schema.proto. Unlike a generated codec it: decodes
1550
+ * int64/uint64 as BigInt, tracks proto2 `optional` presence, validates UTF-8
1551
+ * strictly, and rejects unknown fields, wrong wire types and missing
1552
+ * `required` fields.
1553
+ */
1554
+ var ProtoError = class extends Error {};
1555
+ const utf8 = new TextDecoder("utf-8", { fatal: true });
1556
+ const utf8enc = new TextEncoder();
1557
+ var Reader = class Reader {
1558
+ buf;
1559
+ end;
1560
+ pos;
1561
+ constructor(buf, pos = 0, end = buf.length) {
1562
+ this.buf = buf;
1563
+ this.end = end;
1564
+ this.pos = pos;
1565
+ }
1566
+ get done() {
1567
+ return this.pos >= this.end;
1568
+ }
1569
+ byte() {
1570
+ if (this.pos >= this.end) throw new ProtoError("unexpected end of buffer");
1571
+ return this.buf[this.pos++];
1572
+ }
1573
+ varint() {
1574
+ let result = 0n;
1575
+ let shift = 0n;
1576
+ let b;
1577
+ do {
1578
+ if (shift > 63n) throw new ProtoError("varint overflow");
1579
+ b = this.byte();
1580
+ result |= BigInt(b & 127) << shift;
1581
+ shift += 7n;
1582
+ } while (b & 128);
1583
+ return BigInt.asUintN(64, result);
1584
+ }
1585
+ varintNum() {
1586
+ const v = this.varint();
1587
+ if (v > BigInt(Number.MAX_SAFE_INTEGER)) throw new ProtoError("varint too large for a number");
1588
+ return Number(v);
1589
+ }
1590
+ bytes() {
1591
+ const len = this.varintNum();
1592
+ if (this.pos + len > this.end) throw new ProtoError("length-delimited field overruns buffer");
1593
+ const out = this.buf.subarray(this.pos, this.pos + len);
1594
+ this.pos += len;
1595
+ return out;
1596
+ }
1597
+ string() {
1598
+ try {
1599
+ return utf8.decode(this.bytes());
1600
+ } catch {
1601
+ throw new ProtoError("invalid UTF-8 in string field");
1602
+ }
1603
+ }
1604
+ sub() {
1605
+ const b = this.bytes();
1606
+ return new Reader(b, 0, b.length);
1607
+ }
1608
+ };
1609
+ function readFields(r, handler) {
1610
+ while (!r.done) {
1611
+ const tag = r.varintNum();
1612
+ const field = tag >> 3;
1613
+ const wire = tag & 7;
1614
+ if (field === 0) throw new ProtoError("invalid field number 0");
1615
+ handler(field, wire, r);
1616
+ }
1617
+ }
1618
+ function expect(wire, want, field) {
1619
+ if (wire !== want) throw new ProtoError(`wrong wire type ${wire} for field ${field}`);
1620
+ }
1621
+ function required(v, name) {
1622
+ if (v === void 0) throw new ProtoError(`missing required field ${name}`);
1623
+ return v;
1624
+ }
1625
+ var Writer = class Writer {
1626
+ buf = /* @__PURE__ */ new Uint8Array(256);
1627
+ pos = 0;
1628
+ grow(n) {
1629
+ if (this.pos + n <= this.buf.length) return;
1630
+ let len = this.buf.length;
1631
+ while (len < this.pos + n) len *= 2;
1632
+ const next = new Uint8Array(len);
1633
+ next.set(this.buf.subarray(0, this.pos));
1634
+ this.buf = next;
1635
+ }
1636
+ varint(v) {
1637
+ let x = BigInt.asUintN(64, BigInt(v));
1638
+ this.grow(10);
1639
+ do {
1640
+ let b = Number(x & 127n);
1641
+ x >>= 7n;
1642
+ if (x) b |= 128;
1643
+ this.buf[this.pos++] = b;
1644
+ } while (x);
1645
+ }
1646
+ tag(field, wire) {
1647
+ this.varint(field << 3 | wire);
1648
+ }
1649
+ varintField(field, v) {
1650
+ this.tag(field, 0);
1651
+ this.varint(v);
1652
+ }
1653
+ bytesField(field, v) {
1654
+ this.tag(field, 2);
1655
+ this.varint(v.length);
1656
+ this.grow(v.length);
1657
+ this.buf.set(v, this.pos);
1658
+ this.pos += v.length;
1659
+ }
1660
+ stringField(field, v) {
1661
+ this.bytesField(field, utf8enc.encode(v));
1662
+ }
1663
+ messageField(field, write, v) {
1664
+ const w = new Writer();
1665
+ write(v, w);
1666
+ this.bytesField(field, w.finish());
1667
+ }
1668
+ finish() {
1669
+ return this.buf.slice(0, this.pos);
1670
+ }
1671
+ };
1672
+ function readPublicKey(r) {
1673
+ let algorithm;
1674
+ let key;
1675
+ readFields(r, (f, w, rr) => {
1676
+ if (f === 1) {
1677
+ expect(w, 0, f);
1678
+ algorithm = rr.varintNum();
1679
+ } else if (f === 2) {
1680
+ expect(w, 2, f);
1681
+ key = rr.bytes();
1682
+ } else throw new ProtoError(`unknown field ${f} in PublicKey`);
1683
+ });
1684
+ const alg = required(algorithm, "PublicKey.algorithm");
1685
+ if (alg !== 0 && alg !== 1) throw new ProtoError(`unknown signature algorithm ${alg}`);
1686
+ return {
1687
+ algorithm: alg,
1688
+ key: required(key, "PublicKey.key")
1689
+ };
1690
+ }
1691
+ function readExternalSignature(r) {
1692
+ let signature;
1693
+ let publicKey;
1694
+ readFields(r, (f, w, rr) => {
1695
+ if (f === 1) {
1696
+ expect(w, 2, f);
1697
+ signature = rr.bytes();
1698
+ } else if (f === 2) {
1699
+ expect(w, 2, f);
1700
+ publicKey = readPublicKey(rr.sub());
1701
+ } else throw new ProtoError(`unknown field ${f} in ExternalSignature`);
1702
+ });
1703
+ return {
1704
+ signature: required(signature, "ExternalSignature.signature"),
1705
+ publicKey: required(publicKey, "ExternalSignature.publicKey")
1706
+ };
1707
+ }
1708
+ function readSignedBlock(r) {
1709
+ const out = {};
1710
+ readFields(r, (f, w, rr) => {
1711
+ if (f === 1) {
1712
+ expect(w, 2, f);
1713
+ out.block = rr.bytes();
1714
+ } else if (f === 2) {
1715
+ expect(w, 2, f);
1716
+ out.nextKey = readPublicKey(rr.sub());
1717
+ } else if (f === 3) {
1718
+ expect(w, 2, f);
1719
+ out.signature = rr.bytes();
1720
+ } else if (f === 4) {
1721
+ expect(w, 2, f);
1722
+ out.externalSignature = readExternalSignature(rr.sub());
1723
+ } else if (f === 5) {
1724
+ expect(w, 0, f);
1725
+ out.version = rr.varintNum();
1726
+ } else throw new ProtoError(`unknown field ${f} in SignedBlock`);
1727
+ });
1728
+ return {
1729
+ block: required(out.block, "SignedBlock.block"),
1730
+ nextKey: required(out.nextKey, "SignedBlock.nextKey"),
1731
+ signature: required(out.signature, "SignedBlock.signature"),
1732
+ externalSignature: out.externalSignature,
1733
+ version: out.version
1734
+ };
1735
+ }
1736
+ function decodeBiscuit(buf) {
1737
+ const r = new Reader(buf);
1738
+ let rootKeyId;
1739
+ let authority;
1740
+ const blocks = [];
1741
+ let proof;
1742
+ readFields(r, (f, w, rr) => {
1743
+ if (f === 1) {
1744
+ expect(w, 0, f);
1745
+ rootKeyId = rr.varintNum();
1746
+ } else if (f === 2) {
1747
+ expect(w, 2, f);
1748
+ authority = readSignedBlock(rr.sub());
1749
+ } else if (f === 3) {
1750
+ expect(w, 2, f);
1751
+ blocks.push(readSignedBlock(rr.sub()));
1752
+ } else if (f === 4) {
1753
+ expect(w, 2, f);
1754
+ readFields(rr.sub(), (pf, pw, pr) => {
1755
+ if (pf === 1) {
1756
+ expect(pw, 2, pf);
1757
+ proof = {
1758
+ kind: "nextSecret",
1759
+ value: pr.bytes()
1760
+ };
1761
+ } else if (pf === 2) {
1762
+ expect(pw, 2, pf);
1763
+ proof = {
1764
+ kind: "finalSignature",
1765
+ value: pr.bytes()
1766
+ };
1767
+ } else throw new ProtoError(`unknown field ${pf} in Proof`);
1768
+ });
1769
+ } else throw new ProtoError(`unknown field ${f} in Biscuit`);
1770
+ });
1771
+ return {
1772
+ rootKeyId,
1773
+ authority: required(authority, "Biscuit.authority"),
1774
+ blocks,
1775
+ proof: required(proof, "Biscuit.proof")
1776
+ };
1777
+ }
1778
+ function readTerm(r) {
1779
+ let out;
1780
+ readFields(r, (f, w, rr) => {
1781
+ switch (f) {
1782
+ case 1:
1783
+ expect(w, 0, f);
1784
+ out = {
1785
+ kind: "variable",
1786
+ value: rr.varintNum()
1787
+ };
1788
+ break;
1789
+ case 2:
1790
+ expect(w, 0, f);
1791
+ out = {
1792
+ kind: "integer",
1793
+ value: BigInt.asIntN(64, rr.varint())
1794
+ };
1795
+ break;
1796
+ case 3:
1797
+ expect(w, 0, f);
1798
+ out = {
1799
+ kind: "string",
1800
+ value: rr.varintNum()
1801
+ };
1802
+ break;
1803
+ case 4:
1804
+ expect(w, 0, f);
1805
+ out = {
1806
+ kind: "date",
1807
+ value: rr.varint()
1808
+ };
1809
+ break;
1810
+ case 5:
1811
+ expect(w, 2, f);
1812
+ out = {
1813
+ kind: "bytes",
1814
+ value: rr.bytes()
1815
+ };
1816
+ break;
1817
+ case 6:
1818
+ expect(w, 0, f);
1819
+ out = {
1820
+ kind: "bool",
1821
+ value: rr.varintNum() !== 0
1822
+ };
1823
+ break;
1824
+ case 7: {
1825
+ expect(w, 2, f);
1826
+ const set = [];
1827
+ readFields(rr.sub(), (sf, sw, sr) => {
1828
+ if (sf !== 1) throw new ProtoError(`unknown field ${sf} in TermSet`);
1829
+ expect(sw, 2, sf);
1830
+ set.push(readTerm(sr.sub()));
1831
+ });
1832
+ out = {
1833
+ kind: "set",
1834
+ value: set
1835
+ };
1836
+ break;
1837
+ }
1838
+ case 8:
1839
+ expect(w, 2, f);
1840
+ rr.bytes();
1841
+ out = { kind: "null" };
1842
+ break;
1843
+ case 9: {
1844
+ expect(w, 2, f);
1845
+ const arr = [];
1846
+ readFields(rr.sub(), (af, aw, ar) => {
1847
+ if (af !== 1) throw new ProtoError(`unknown field ${af} in Array`);
1848
+ expect(aw, 2, af);
1849
+ arr.push(readTerm(ar.sub()));
1850
+ });
1851
+ out = {
1852
+ kind: "array",
1853
+ value: arr
1854
+ };
1855
+ break;
1856
+ }
1857
+ case 10: {
1858
+ expect(w, 2, f);
1859
+ const entries = [];
1860
+ readFields(rr.sub(), (mf, mw, mr) => {
1861
+ if (mf !== 1) throw new ProtoError(`unknown field ${mf} in Map`);
1862
+ expect(mw, 2, mf);
1863
+ entries.push(readMapEntry(mr.sub()));
1864
+ });
1865
+ out = {
1866
+ kind: "map",
1867
+ value: entries
1868
+ };
1869
+ break;
1870
+ }
1871
+ default: throw new ProtoError(`unknown field ${f} in Term`);
1872
+ }
1873
+ });
1874
+ return required(out, "Term.Content");
1875
+ }
1876
+ function readMapEntry(r) {
1877
+ let key;
1878
+ let value;
1879
+ readFields(r, (f, w, rr) => {
1880
+ if (f === 1) {
1881
+ expect(w, 2, f);
1882
+ readFields(rr.sub(), (kf, kw, kr) => {
1883
+ if (kf === 1) {
1884
+ expect(kw, 0, kf);
1885
+ key = {
1886
+ kind: "integer",
1887
+ value: BigInt.asIntN(64, kr.varint())
1888
+ };
1889
+ } else if (kf === 2) {
1890
+ expect(kw, 0, kf);
1891
+ key = {
1892
+ kind: "string",
1893
+ value: kr.varintNum()
1894
+ };
1895
+ } else throw new ProtoError(`unknown field ${kf} in MapKey`);
1896
+ });
1897
+ } else if (f === 2) {
1898
+ expect(w, 2, f);
1899
+ value = readTerm(rr.sub());
1900
+ } else throw new ProtoError(`unknown field ${f} in MapEntry`);
1901
+ });
1902
+ return {
1903
+ key: required(key, "MapEntry.key"),
1904
+ value: required(value, "MapEntry.value")
1905
+ };
1906
+ }
1907
+ function readPredicate(r) {
1908
+ let name;
1909
+ const terms = [];
1910
+ readFields(r, (f, w, rr) => {
1911
+ if (f === 1) {
1912
+ expect(w, 0, f);
1913
+ name = rr.varintNum();
1914
+ } else if (f === 2) {
1915
+ expect(w, 2, f);
1916
+ terms.push(readTerm(rr.sub()));
1917
+ } else throw new ProtoError(`unknown field ${f} in Predicate`);
1918
+ });
1919
+ return {
1920
+ name: required(name, "Predicate.name"),
1921
+ terms
1922
+ };
1923
+ }
1924
+ function readOp(r) {
1925
+ let out;
1926
+ readFields(r, (f, w, rr) => {
1927
+ if (f === 1) {
1928
+ expect(w, 2, f);
1929
+ out = {
1930
+ kind: "value",
1931
+ value: readTerm(rr.sub())
1932
+ };
1933
+ } else if (f === 2 || f === 3) {
1934
+ expect(w, 2, f);
1935
+ let op;
1936
+ let ffiName;
1937
+ readFields(rr.sub(), (of_, ow, or_) => {
1938
+ if (of_ === 1) {
1939
+ expect(ow, 0, of_);
1940
+ op = or_.varintNum();
1941
+ } else if (of_ === 2) {
1942
+ expect(ow, 0, of_);
1943
+ ffiName = or_.varintNum();
1944
+ } else throw new ProtoError(`unknown field ${of_} in Op`);
1945
+ });
1946
+ out = {
1947
+ kind: f === 2 ? "unary" : "binary",
1948
+ op: required(op, "Op.kind"),
1949
+ ffiName
1950
+ };
1951
+ } else if (f === 4) {
1952
+ expect(w, 2, f);
1953
+ const params = [];
1954
+ const ops = [];
1955
+ readFields(rr.sub(), (cf, cw, cr) => {
1956
+ if (cf === 1) {
1957
+ if (cw === 0) params.push(cr.varintNum());
1958
+ else if (cw === 2) {
1959
+ const packed = cr.sub();
1960
+ while (!packed.done) params.push(packed.varintNum());
1961
+ } else throw new ProtoError("bad wire type for OpClosure.params");
1962
+ } else if (cf === 2) {
1963
+ expect(cw, 2, cf);
1964
+ ops.push(readOp(cr.sub()));
1965
+ } else throw new ProtoError(`unknown field ${cf} in OpClosure`);
1966
+ });
1967
+ out = {
1968
+ kind: "closure",
1969
+ params,
1970
+ ops
1971
+ };
1972
+ } else throw new ProtoError(`unknown field ${f} in Op`);
1973
+ });
1974
+ return required(out, "Op.Content");
1975
+ }
1976
+ function readScope(r) {
1977
+ let out;
1978
+ readFields(r, (f, w, rr) => {
1979
+ if (f === 1) {
1980
+ expect(w, 0, f);
1981
+ const v = rr.varintNum();
1982
+ if (v !== 0 && v !== 1) throw new ProtoError(`unknown scope type ${v}`);
1983
+ out = {
1984
+ kind: "type",
1985
+ value: v
1986
+ };
1987
+ } else if (f === 2) {
1988
+ expect(w, 0, f);
1989
+ out = {
1990
+ kind: "publicKey",
1991
+ value: Number(BigInt.asIntN(64, rr.varint()))
1992
+ };
1993
+ } else throw new ProtoError(`unknown field ${f} in Scope`);
1994
+ });
1995
+ return required(out, "Scope.Content");
1996
+ }
1997
+ function readRule(r) {
1998
+ let head;
1999
+ const body = [];
2000
+ const expressions = [];
2001
+ const scope = [];
2002
+ readFields(r, (f, w, rr) => {
2003
+ if (f === 1) {
2004
+ expect(w, 2, f);
2005
+ head = readPredicate(rr.sub());
2006
+ } else if (f === 2) {
2007
+ expect(w, 2, f);
2008
+ body.push(readPredicate(rr.sub()));
2009
+ } else if (f === 3) {
2010
+ expect(w, 2, f);
2011
+ const ops = [];
2012
+ readFields(rr.sub(), (ef, ew, er) => {
2013
+ if (ef !== 1) throw new ProtoError(`unknown field ${ef} in Expression`);
2014
+ expect(ew, 2, ef);
2015
+ ops.push(readOp(er.sub()));
2016
+ });
2017
+ expressions.push(ops);
2018
+ } else if (f === 4) {
2019
+ expect(w, 2, f);
2020
+ scope.push(readScope(rr.sub()));
2021
+ } else throw new ProtoError(`unknown field ${f} in Rule`);
2022
+ });
2023
+ return {
2024
+ head: required(head, "Rule.head"),
2025
+ body,
2026
+ expressions,
2027
+ scope
2028
+ };
2029
+ }
2030
+ function decodeBlock(buf) {
2031
+ const out = {
2032
+ symbols: [],
2033
+ facts: [],
2034
+ rules: [],
2035
+ checks: [],
2036
+ scope: [],
2037
+ publicKeys: []
2038
+ };
2039
+ readFields(new Reader(buf), (f, w, rr) => {
2040
+ switch (f) {
2041
+ case 1:
2042
+ expect(w, 2, f);
2043
+ out.symbols.push(rr.string());
2044
+ break;
2045
+ case 2:
2046
+ expect(w, 2, f);
2047
+ out.context = rr.string();
2048
+ break;
2049
+ case 3:
2050
+ expect(w, 0, f);
2051
+ out.version = rr.varintNum();
2052
+ break;
2053
+ case 4: {
2054
+ expect(w, 2, f);
2055
+ let pred;
2056
+ readFields(rr.sub(), (ff, fw, fr) => {
2057
+ if (ff !== 1) throw new ProtoError(`unknown field ${ff} in Fact`);
2058
+ expect(fw, 2, ff);
2059
+ pred = readPredicate(fr.sub());
2060
+ });
2061
+ out.facts.push(required(pred, "Fact.predicate"));
2062
+ break;
2063
+ }
2064
+ case 5:
2065
+ expect(w, 2, f);
2066
+ out.rules.push(readRule(rr.sub()));
2067
+ break;
2068
+ case 6: {
2069
+ expect(w, 2, f);
2070
+ const queries = [];
2071
+ let kind;
2072
+ readFields(rr.sub(), (cf, cw, cr) => {
2073
+ if (cf === 1) {
2074
+ expect(cw, 2, cf);
2075
+ queries.push(readRule(cr.sub()));
2076
+ } else if (cf === 2) {
2077
+ expect(cw, 0, cf);
2078
+ kind = cr.varintNum();
2079
+ } else throw new ProtoError(`unknown field ${cf} in Check`);
2080
+ });
2081
+ if (kind !== void 0 && kind !== 0 && kind !== 1 && kind !== 2) throw new ProtoError(`unknown check kind ${kind}`);
2082
+ out.checks.push({
2083
+ queries,
2084
+ kind
2085
+ });
2086
+ break;
2087
+ }
2088
+ case 7:
2089
+ expect(w, 2, f);
2090
+ out.scope.push(readScope(rr.sub()));
2091
+ break;
2092
+ case 8:
2093
+ expect(w, 2, f);
2094
+ out.publicKeys.push(readPublicKey(rr.sub()));
2095
+ break;
2096
+ default: throw new ProtoError(`unknown field ${f} in Block`);
2097
+ }
2098
+ });
2099
+ return out;
2100
+ }
2101
+ function writePublicKey(v, w) {
2102
+ w.varintField(1, v.algorithm);
2103
+ w.bytesField(2, v.key);
2104
+ }
2105
+ function writeSignedBlock(v, w) {
2106
+ w.bytesField(1, v.block);
2107
+ w.messageField(2, writePublicKey, v.nextKey);
2108
+ w.bytesField(3, v.signature);
2109
+ if (v.externalSignature !== void 0) w.messageField(4, (e, ww) => {
2110
+ ww.bytesField(1, e.signature);
2111
+ ww.messageField(2, writePublicKey, e.publicKey);
2112
+ }, v.externalSignature);
2113
+ if (v.version !== void 0) w.varintField(5, v.version);
2114
+ }
2115
+ function encodeBiscuit(v) {
2116
+ const w = new Writer();
2117
+ if (v.rootKeyId !== void 0) w.varintField(1, v.rootKeyId);
2118
+ w.messageField(2, writeSignedBlock, v.authority);
2119
+ for (const b of v.blocks) w.messageField(3, writeSignedBlock, b);
2120
+ w.messageField(4, (p, ww) => ww.bytesField(p.kind === "nextSecret" ? 1 : 2, p.value), v.proof);
2121
+ return w.finish();
2122
+ }
2123
+ function writeTerm(t, w) {
2124
+ switch (t.kind) {
2125
+ case "variable":
2126
+ w.varintField(1, t.value);
2127
+ break;
2128
+ case "integer":
2129
+ w.varintField(2, t.value);
2130
+ break;
2131
+ case "string":
2132
+ w.varintField(3, t.value);
2133
+ break;
2134
+ case "date":
2135
+ w.varintField(4, t.value);
2136
+ break;
2137
+ case "bytes":
2138
+ w.bytesField(5, t.value);
2139
+ break;
2140
+ case "bool":
2141
+ w.varintField(6, t.value ? 1 : 0);
2142
+ break;
2143
+ case "set":
2144
+ w.messageField(7, (items, ww) => {
2145
+ for (const i of items) ww.messageField(1, writeTerm, i);
2146
+ }, t.value);
2147
+ break;
2148
+ case "null":
2149
+ w.bytesField(8, /* @__PURE__ */ new Uint8Array(0));
2150
+ break;
2151
+ case "array":
2152
+ w.messageField(9, (items, ww) => {
2153
+ for (const i of items) ww.messageField(1, writeTerm, i);
2154
+ }, t.value);
2155
+ break;
2156
+ case "map": w.messageField(10, (entries, ww) => {
2157
+ for (const e of entries) ww.messageField(1, (en, w3) => {
2158
+ w3.messageField(1, (k, w4) => w4.varintField(k.kind === "integer" ? 1 : 2, k.value), en.key);
2159
+ w3.messageField(2, writeTerm, en.value);
2160
+ }, e);
2161
+ }, t.value);
2162
+ }
2163
+ }
2164
+ function writePredicate(p, w) {
2165
+ w.varintField(1, p.name);
2166
+ for (const t of p.terms) w.messageField(2, writeTerm, t);
2167
+ }
2168
+ function writeOp(op, w) {
2169
+ switch (op.kind) {
2170
+ case "value":
2171
+ w.messageField(1, writeTerm, op.value);
2172
+ break;
2173
+ case "unary":
2174
+ case "binary":
2175
+ w.messageField(op.kind === "unary" ? 2 : 3, (o, ww) => {
2176
+ ww.varintField(1, o.op);
2177
+ if (o.ffiName !== void 0) ww.varintField(2, o.ffiName);
2178
+ }, op);
2179
+ break;
2180
+ case "closure": w.messageField(4, (c, ww) => {
2181
+ for (const p of c.params) ww.varintField(1, p);
2182
+ for (const o of c.ops) ww.messageField(2, writeOp, o);
2183
+ }, op);
2184
+ }
2185
+ }
2186
+ function writeScope(s, w) {
2187
+ w.varintField(s.kind === "type" ? 1 : 2, s.value);
2188
+ }
2189
+ function writeRule(r, w) {
2190
+ w.messageField(1, writePredicate, r.head);
2191
+ for (const p of r.body) w.messageField(2, writePredicate, p);
2192
+ for (const e of r.expressions) w.messageField(3, (ops, ww) => {
2193
+ for (const o of ops) ww.messageField(1, writeOp, o);
2194
+ }, e);
2195
+ for (const s of r.scope) w.messageField(4, writeScope, s);
2196
+ }
2197
+ function encodeBlock(b) {
2198
+ const w = new Writer();
2199
+ for (const s of b.symbols) w.stringField(1, s);
2200
+ if (b.context !== void 0) w.stringField(2, b.context);
2201
+ if (b.version !== void 0) w.varintField(3, b.version);
2202
+ for (const f of b.facts) w.messageField(4, (p, ww) => ww.messageField(1, writePredicate, p), f);
2203
+ for (const r of b.rules) w.messageField(5, writeRule, r);
2204
+ for (const c of b.checks) w.messageField(6, (ch, ww) => {
2205
+ for (const q of ch.queries) ww.messageField(1, writeRule, q);
2206
+ if (ch.kind !== void 0) ww.varintField(2, ch.kind);
2207
+ }, c);
2208
+ for (const s of b.scope) w.messageField(7, writeScope, s);
2209
+ for (const k of b.publicKeys) w.messageField(8, writePublicKey, k);
2210
+ return w.finish();
2211
+ }
2212
+ //#endregion
2213
+ //#region src/version.ts
2214
+ /**
2215
+ * Datalog block versioning.
2216
+ *
2217
+ * Every block declares the Datalog version it was generated at. An
2218
+ * implementation must reject versions outside the supported window, and must
2219
+ * reject a block that uses a feature newer than the version it declares —
2220
+ * otherwise a token could smuggle newer semantics past an older verifier.
2221
+ *
2222
+ * Mirrors `get_schema_version` / `check_compatibility` in the reference
2223
+ * implementation (biscuit-auth `src/datalog/mod.rs`).
2224
+ */
2225
+ const MIN_SCHEMA_VERSION = 3;
2226
+ const MAX_SCHEMA_VERSION = 6;
2227
+ const DATALOG_3_1 = 4;
2228
+ const DATALOG_3_2 = 5;
2229
+ const DATALOG_3_3 = 6;
2230
+ var VersionError = class extends Error {};
2231
+ /** null, or a set containing null — arrays and maps are deliberately not
2232
+ * flagged here, matching the reference implementation */
2233
+ const isV33Term = (t) => t.t === "null" || t.t === "set" && t.v.some((x) => x.t === "null");
2234
+ const hasV33Predicate = (p) => p.terms.some(isV33Term);
2235
+ const hasV31Op = (expressions) => expressions.some((ops) => ops.some((op) => op.kind === "binary" && (op.op === BinaryOp.BitwiseAnd || op.op === BinaryOp.BitwiseOr || op.op === BinaryOp.BitwiseXor || op.op === BinaryOp.NotEqual)));
2236
+ const hasV33Op = (expressions) => expressions.some((ops) => ops.some((op) => {
2237
+ if (op.kind === "value") return isV33Term(op.value);
2238
+ if (op.kind === "closure") return true;
2239
+ if (op.kind === "unary") return op.op === UnaryOp.TypeOf || op.op === UnaryOp.Ffi;
2240
+ return op.op === BinaryOp.HeterogeneousEqual || op.op === BinaryOp.HeterogeneousNotEqual || op.op === BinaryOp.LazyAnd || op.op === BinaryOp.LazyOr || op.op === BinaryOp.All || op.op === BinaryOp.Any || op.op === BinaryOp.Ffi;
2241
+ }));
2242
+ function blockFeatures(c) {
2243
+ const queries = c.checks.flatMap((ch) => ch.queries);
2244
+ const scopes = c.scopes.length > 0 || c.rules.some((r) => r.scopes.length > 0) || queries.some((q) => q.scopes.length > 0);
2245
+ const checkAll = c.checks.some((ch) => ch.kind === "all");
2246
+ let v33 = c.checks.some((ch) => ch.kind === "reject");
2247
+ const v31 = c.rules.some((r) => hasV31Op(r.expressions)) || queries.some((q) => hasV31Op(q.expressions));
2248
+ if (!v33) v33 = c.rules.some((r) => hasV33Predicate(r.head) || r.body.some(hasV33Predicate) || hasV33Op(r.expressions)) || queries.some((q) => q.body.some(hasV33Predicate) || hasV33Op(q.expressions));
2249
+ if (!v33) v33 = c.facts.some((f) => hasV33Predicate(f.predicate));
2250
+ return {
2251
+ scopes,
2252
+ v31,
2253
+ checkAll,
2254
+ v33
2255
+ };
2256
+ }
2257
+ /** the lowest version that can legally carry this block's content */
2258
+ function requiredVersion(c) {
2259
+ const f = blockFeatures(c);
2260
+ if (f.v33) return 6;
2261
+ if (f.scopes || f.v31 || f.checkAll) return 4;
2262
+ return 3;
2263
+ }
2264
+ /**
2265
+ * Rejects a block whose declared version is out of range, or which uses a
2266
+ * feature newer than that version. `rawChecks` is needed because the gate on
2267
+ * check kinds distinguishes an absent kind from an explicit `One`.
2268
+ */
2269
+ function validateBlockVersion(declared, rawChecks, hasExternalKey, content) {
2270
+ const version = declared ?? 0;
2271
+ if (version < 3 || version > 6) throw new VersionError(`unsupported datalog version ${version}: supported versions are 3 to 6`);
2272
+ if (version < 6) for (const c of rawChecks) {
2273
+ if (version < 4 && c.kind !== void 0) throw new VersionError("check kinds are only supported on datalog v3.1+ blocks");
2274
+ if (version < 6 && c.kind === 2) throw new VersionError("reject if is only supported in datalog v3.3+");
2275
+ }
2276
+ if (version < 5 && hasExternalKey) throw new VersionError("third-party blocks are only supported in datalog v3.2+");
2277
+ const f = blockFeatures(content);
2278
+ if (version < 4) {
2279
+ if (f.scopes) throw new VersionError("scopes are only supported in datalog v3.1+");
2280
+ if (f.v31) throw new VersionError("bitwise operators and != are only supported in datalog v3.1+");
2281
+ if (f.checkAll) throw new VersionError("check all is only supported in datalog v3.1+");
2282
+ } else if (version < 6 && f.v33) throw new VersionError("maps, arrays, null, closures are only supported in datalog v3.3+");
2283
+ }
2284
+ //#endregion
2285
+ //#region src/authorizer.ts
2286
+ /**
2287
+ * Turns a serialized token plus authorizer code into an authorization result,
2288
+ * mirroring the evaluation order of the reference implementation.
2289
+ */
2290
+ const DEFAULT_SYMBOLS = [
2291
+ "read",
2292
+ "write",
2293
+ "resource",
2294
+ "operation",
2295
+ "right",
2296
+ "time",
2297
+ "role",
2298
+ "owner",
2299
+ "tenant",
2300
+ "namespace",
2301
+ "user",
2302
+ "team",
2303
+ "service",
2304
+ "admin",
2305
+ "email",
2306
+ "group",
2307
+ "member",
2308
+ "ip_address",
2309
+ "client",
2310
+ "client_ip",
2311
+ "domain",
2312
+ "path",
2313
+ "version",
2314
+ "cluster",
2315
+ "node",
2316
+ "hostname",
2317
+ "nonce",
2318
+ "query"
2319
+ ];
2320
+ const OFFSET$1 = 1024;
2321
+ var TokenError = class extends Error {
2322
+ kind;
2323
+ constructor(kind, message) {
2324
+ super(message);
2325
+ this.kind = kind;
2326
+ }
2327
+ };
2328
+ const hex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
2329
+ const keyString = (algorithm, key) => `${algorithm === 0 ? "ed25519" : "secp256r1"}/${hex(key)}`;
2330
+ var Symbols = class {
2331
+ table;
2332
+ keys;
2333
+ constructor(table, keys) {
2334
+ this.table = table;
2335
+ this.keys = keys;
2336
+ }
2337
+ get(index) {
2338
+ if (index < OFFSET$1) {
2339
+ const s = DEFAULT_SYMBOLS[index];
2340
+ if (s === void 0) throw new TokenError("Symbol", `unknown default symbol ${index}`);
2341
+ return s;
2342
+ }
2343
+ const s = this.table[index - OFFSET$1];
2344
+ if (s === void 0) throw new TokenError("Symbol", `unknown symbol ${index}`);
2345
+ return s;
2346
+ }
2347
+ publicKey(index) {
2348
+ const k = this.keys[index];
2349
+ if (k === void 0) throw new TokenError("Symbol", `unknown public key ${index}`);
2350
+ return k;
2351
+ }
2352
+ };
2353
+ function convTerm(t, s) {
2354
+ switch (t.kind) {
2355
+ case "variable": return {
2356
+ t: "var",
2357
+ v: t.value
2358
+ };
2359
+ case "integer": return {
2360
+ t: "int",
2361
+ v: t.value
2362
+ };
2363
+ case "string": return {
2364
+ t: "str",
2365
+ v: s.get(t.value)
2366
+ };
2367
+ case "date": return {
2368
+ t: "date",
2369
+ v: t.value
2370
+ };
2371
+ case "bytes": return {
2372
+ t: "bytes",
2373
+ v: t.value
2374
+ };
2375
+ case "bool": return {
2376
+ t: "bool",
2377
+ v: t.value
2378
+ };
2379
+ case "null": return { t: "null" };
2380
+ case "set": return {
2381
+ t: "set",
2382
+ v: normalizeSet(t.value.map((x) => convTerm(x, s)))
2383
+ };
2384
+ case "array": return {
2385
+ t: "array",
2386
+ v: t.value.map((x) => convTerm(x, s))
2387
+ };
2388
+ case "map": return {
2389
+ t: "map",
2390
+ v: t.value.map((e) => [e.key.kind === "integer" ? {
2391
+ t: "int",
2392
+ v: e.key.value
2393
+ } : {
2394
+ t: "str",
2395
+ v: s.get(e.key.value)
2396
+ }, convTerm(e.value, s)])
2397
+ };
2398
+ }
2399
+ }
2400
+ const convPredicate = (p, s) => ({
2401
+ name: s.get(p.name),
2402
+ terms: p.terms.map((t) => convTerm(t, s))
2403
+ });
2404
+ function convOps(ops, s) {
2405
+ return ops.map((op) => {
2406
+ switch (op.kind) {
2407
+ case "value": return {
2408
+ kind: "value",
2409
+ value: convTerm(op.value, s)
2410
+ };
2411
+ case "unary": return {
2412
+ kind: "unary",
2413
+ op: op.op,
2414
+ ffi: op.ffiName !== void 0 ? s.get(op.ffiName) : void 0
2415
+ };
2416
+ case "binary": return {
2417
+ kind: "binary",
2418
+ op: op.op,
2419
+ ffi: op.ffiName !== void 0 ? s.get(op.ffiName) : void 0
2420
+ };
2421
+ case "closure": return {
2422
+ kind: "closure",
2423
+ params: op.params,
2424
+ ops: convOps(op.ops, s)
2425
+ };
2426
+ default: throw new ProtoError(`unknown expression op kind ${op.kind}`);
2427
+ }
2428
+ });
2429
+ }
2430
+ const convScope = (sc, s) => sc.kind === "type" ? sc.value === 0 ? { kind: "authority" } : { kind: "previous" } : {
2431
+ kind: "publicKey",
2432
+ key: s.publicKey(sc.value)
2433
+ };
2434
+ const convRule = (r, s) => ({
2435
+ head: convPredicate(r.head, s),
2436
+ body: r.body.map((p) => convPredicate(p, s)),
2437
+ expressions: r.expressions.map((e) => convOps(e, s)),
2438
+ scopes: r.scope.map((sc) => convScope(sc, s))
2439
+ });
2440
+ function convBlock(b, s, externalKey) {
2441
+ const varNames = /* @__PURE__ */ new Map();
2442
+ const collectTerm = (t) => {
2443
+ if (t.kind === "variable") varNames.set(t.value, s.get(t.value));
2444
+ else if (t.kind === "set" || t.kind === "array") t.value.forEach(collectTerm);
2445
+ else if (t.kind === "map") t.value.forEach((e) => {
2446
+ collectTerm(e.value);
2447
+ });
2448
+ };
2449
+ const collectOps = (ops) => {
2450
+ for (const op of ops) if (op.kind === "value") collectTerm(op.value);
2451
+ else if (op.kind === "closure") {
2452
+ for (const param of op.params) varNames.set(param, s.get(param));
2453
+ collectOps(op.ops);
2454
+ }
2455
+ };
2456
+ const collect = (r) => {
2457
+ for (const p of [r.head, ...r.body]) p.terms.forEach(collectTerm);
2458
+ r.expressions.forEach(collectOps);
2459
+ };
2460
+ for (const r of b.rules) collect(r);
2461
+ for (const c of b.checks) for (const q of c.queries) collect(q);
2462
+ return {
2463
+ varNames,
2464
+ facts: b.facts.map((f) => ({ predicate: convPredicate(f, s) })),
2465
+ rules: b.rules.map((r) => convRule(r, s)),
2466
+ checks: b.checks.map((c) => ({
2467
+ queries: c.queries.map((q) => convRule(q, s)),
2468
+ kind: c.kind === 1 ? "all" : c.kind === 2 ? "reject" : "one"
2469
+ })),
2470
+ scopes: b.scope.map((sc) => convScope(sc, s)),
2471
+ externalKey
2472
+ };
2473
+ }
2474
+ function loadToken(bytes, rootPublicKey, rootAlgorithm = 0) {
2475
+ const token = decodeBiscuit(bytes);
2476
+ verifyToken(token, rootPublicKey, rootAlgorithm);
2477
+ const signed = [token.authority, ...token.blocks];
2478
+ const decoded = signed.map((sb) => decodeBlock(sb.block));
2479
+ const globalSymbols = [];
2480
+ const globalKeys = [];
2481
+ const extend = (list, items) => {
2482
+ for (const i of items) if (!list.includes(i)) list.push(i);
2483
+ };
2484
+ const blocks = [];
2485
+ const publicKeyToBlockIds = /* @__PURE__ */ new Map();
2486
+ for (let id = 0; id < decoded.length; id++) {
2487
+ const raw = decoded[id];
2488
+ const ext = signed[id].externalSignature;
2489
+ const externalKey = ext ? keyString(ext.publicKey.algorithm, ext.publicKey.key) : void 0;
2490
+ const blockKeys = raw.publicKeys.map((k) => keyString(k.algorithm, k.key));
2491
+ if (externalKey === void 0) {
2492
+ for (const sym of raw.symbols) {
2493
+ if (globalSymbols.includes(sym)) throw new TokenError("Format", "symbol table overlap");
2494
+ globalSymbols.push(sym);
2495
+ }
2496
+ extend(globalKeys, blockKeys);
2497
+ const block = convBlock(raw, new Symbols(globalSymbols, globalKeys), void 0);
2498
+ validateBlockVersion(raw.version, raw.checks, false, block);
2499
+ blocks.push(block);
2500
+ } else {
2501
+ const block = convBlock(raw, new Symbols(raw.symbols, blockKeys), externalKey);
2502
+ validateBlockVersion(raw.version, raw.checks, true, block);
2503
+ blocks.push(block);
2504
+ extend(globalKeys, blockKeys);
2505
+ const ids = publicKeyToBlockIds.get(externalKey) ?? [];
2506
+ ids.push(id);
2507
+ publicKeyToBlockIds.set(externalKey, ids);
2508
+ }
2509
+ }
2510
+ return {
2511
+ blocks,
2512
+ publicKeyToBlockIds,
2513
+ revocationIds: revocationIds(token).map(hex),
2514
+ rootKeyId: token.rootKeyId
2515
+ };
2516
+ }
2517
+ /**
2518
+ * Reads the key identifier from a token **without verifying it**, so a caller
2519
+ * can choose which root key to verify against. The token is untrusted at this
2520
+ * point: the id is a hint, not a claim.
2521
+ */
2522
+ function peekRootKeyId(bytes) {
2523
+ return decodeBiscuit(bytes).rootKeyId;
2524
+ }
2525
+ /** a block rule whose head uses a variable the body never binds is invalid */
2526
+ function headVariablesAreBound(rule) {
2527
+ const bound = /* @__PURE__ */ new Set();
2528
+ for (const p of rule.body) for (const t of p.terms) if (t.t === "var") bound.add(t.v);
2529
+ return rule.head.terms.every((t) => t.t !== "var" || bound.has(t.v));
2530
+ }
2531
+ function parseAuthorizer(src) {
2532
+ const out = {
2533
+ facts: [],
2534
+ rules: [],
2535
+ checks: [],
2536
+ policies: [],
2537
+ scopes: [],
2538
+ varNames: /* @__PURE__ */ new Map()
2539
+ };
2540
+ const parser = new Parser(src);
2541
+ const statements = parser.parse();
2542
+ out.varNames = parser.variableNames();
2543
+ for (const st of statements) if (st.k === "fact") out.facts.push(st.fact);
2544
+ else if (st.k === "rule") out.rules.push(st.rule);
2545
+ else if (st.k === "check") out.checks.push(st.check);
2546
+ else if (st.k === "policy") out.policies.push({
2547
+ kind: st.kind,
2548
+ queries: st.queries
2549
+ });
2550
+ else out.scopes.push(...st.scopes);
2551
+ return out;
2552
+ }
2553
+ function authorize(token, authorizerSrc, options = {}) {
2554
+ return authorizeDetailed(token, authorizerSrc, options).result;
2555
+ }
2556
+ const EMPTY_WORLD = {
2557
+ facts: [],
2558
+ rules: [],
2559
+ checks: [],
2560
+ policies: []
2561
+ };
2562
+ /** Same as `authorize`, and also returns the post-run world, in the shape the
2563
+ * official sample corpus records it. */
2564
+ function authorizeDetailed(token, authorizerSrc, options = {}) {
2565
+ const limits = options.limits ?? DEFAULT_LIMITS;
2566
+ let code;
2567
+ try {
2568
+ code = parseAuthorizer(authorizerSrc);
2569
+ } catch (e) {
2570
+ return {
2571
+ result: {
2572
+ kind: "format",
2573
+ error: e.message
2574
+ },
2575
+ world: EMPTY_WORLD
2576
+ };
2577
+ }
2578
+ const world = new World();
2579
+ if (options.externs) world.externs = options.externs;
2580
+ const keys = token.publicKeyToBlockIds;
2581
+ const blockTrusted = [];
2582
+ for (let id = 0; id < token.blocks.length; id++) {
2583
+ const block = token.blocks[id];
2584
+ const trusted = trustedOriginsFromScopes(block.scopes, TrustedOrigins.default(), id, keys);
2585
+ blockTrusted.push(trusted);
2586
+ const origin = Origin.of(id);
2587
+ for (const f of block.facts) world.addFact(origin, f);
2588
+ for (const r of block.rules) {
2589
+ if (!headVariablesAreBound(r)) return {
2590
+ result: {
2591
+ kind: "invalidBlockRule",
2592
+ blockId: 0,
2593
+ rule: printRule(r, (v) => block.varNames.get(v) ?? String(v))
2594
+ },
2595
+ world: EMPTY_WORLD
2596
+ };
2597
+ world.addRule(id, trustedOriginsFromScopes(r.scopes, trusted, id, keys), r);
2598
+ }
2599
+ }
2600
+ const authorizerTrusted = trustedOriginsFromScopes(code.scopes, TrustedOrigins.default(), AUTHORIZER, keys);
2601
+ const authorizerOrigin = Origin.of(AUTHORIZER);
2602
+ for (const f of code.facts) world.addFact(authorizerOrigin, f);
2603
+ for (const r of code.rules) world.addRule(AUTHORIZER, trustedOriginsFromScopes(r.scopes, authorizerTrusted, AUTHORIZER, keys), r);
2604
+ const name = (id) => id === 4294967295 ? (v) => code.varNames.get(v) ?? String(v) : (v) => token.blocks[id]?.varNames.get(v) ?? String(v);
2605
+ const blockId = (id) => id === 4294967295 ? null : id;
2606
+ const AUTHORIZER_U64 = Number(18446744073709551615n);
2607
+ const ruleOrigin = (id) => id === 4294967295 ? AUTHORIZER_U64 : id;
2608
+ const snapshot = () => {
2609
+ const factGroups = [...world.facts.values()].filter((b) => b.items.size > 0).map((b) => ({
2610
+ origin: b.origin.ids.map(blockId).sort((x, y) => (x ?? -1) - (y ?? -1)),
2611
+ facts: [...b.items.values()].map((f) => printPredicate(f.predicate, () => "?")).sort()
2612
+ })).sort((a, x) => compareOrigins(a.origin, x.origin));
2613
+ const ruleGroups = /* @__PURE__ */ new Map();
2614
+ for (const { origin, rule } of world.rules) {
2615
+ const list = ruleGroups.get(origin) ?? [];
2616
+ list.push(printRule(rule, name(origin)));
2617
+ ruleGroups.set(origin, list);
2618
+ }
2619
+ const checkGroups = [];
2620
+ code.checks.forEach((c) => {
2621
+ const entry = checkGroups.find((g) => g.origin === AUTHORIZER_U64);
2622
+ const text = printCheck(c, name(AUTHORIZER));
2623
+ if (entry) entry.checks.push(text);
2624
+ else checkGroups.push({
2625
+ origin: AUTHORIZER_U64,
2626
+ checks: [text]
2627
+ });
2628
+ });
2629
+ token.blocks.forEach((b, id) => {
2630
+ if (b.checks.length === 0) return;
2631
+ checkGroups.push({
2632
+ origin: id,
2633
+ checks: b.checks.map((c) => printCheck(c, name(id)))
2634
+ });
2635
+ });
2636
+ return {
2637
+ facts: factGroups,
2638
+ rules: [...ruleGroups.entries()].map(([origin, rules]) => ({
2639
+ origin: ruleOrigin(origin),
2640
+ rules: rules.sort()
2641
+ })).sort((a, b) => (a.origin ?? -1) - (b.origin ?? -1)),
2642
+ checks: checkGroups.sort((a, b) => (a.origin ?? -1) - (b.origin ?? -1)),
2643
+ policies: code.policies.map((p) => printPolicy(p.kind, p.queries, name(AUTHORIZER)))
2644
+ };
2645
+ };
2646
+ try {
2647
+ world.run(limits);
2648
+ } catch (e) {
2649
+ if (e instanceof ExecutionError) return {
2650
+ result: {
2651
+ kind: "execution",
2652
+ error: e.kind
2653
+ },
2654
+ world: snapshot()
2655
+ };
2656
+ throw e;
2657
+ }
2658
+ const errors = [];
2659
+ const runCheck = (check, blockId, defaults) => {
2660
+ for (const query of check.queries) {
2661
+ const trusted = trustedOriginsFromScopes(query.scopes, defaults, blockId, keys);
2662
+ if (check.kind === "all" ? world.queryMatchAll(query, trusted) : check.kind === "reject" ? !world.queryMatch(query, blockId, trusted) : world.queryMatch(query, blockId, trusted)) return true;
2663
+ }
2664
+ return false;
2665
+ };
2666
+ try {
2667
+ code.checks.forEach((check, i) => {
2668
+ if (!runCheck(check, 4294967295, authorizerTrusted)) errors.push({
2669
+ source: "authorizer",
2670
+ checkId: i
2671
+ });
2672
+ });
2673
+ token.blocks[0]?.checks.forEach((check, j) => {
2674
+ if (!runCheck(check, 0, blockTrusted[0])) errors.push({
2675
+ source: "block",
2676
+ blockId: 0,
2677
+ checkId: j
2678
+ });
2679
+ });
2680
+ let policyResult = null;
2681
+ outer: for (const [i, policy] of code.policies.entries()) for (const query of policy.queries) {
2682
+ const trusted = trustedOriginsFromScopes(query.scopes, authorizerTrusted, AUTHORIZER, keys);
2683
+ if (world.queryMatch(query, 4294967295, trusted)) {
2684
+ policyResult = policy.kind === "allow" ? { allow: i } : { deny: i };
2685
+ break outer;
2686
+ }
2687
+ }
2688
+ for (let id = 1; id < token.blocks.length; id++) token.blocks[id].checks.forEach((check, j) => {
2689
+ if (!runCheck(check, id, blockTrusted[id])) errors.push({
2690
+ source: "block",
2691
+ blockId: id,
2692
+ checkId: j
2693
+ });
2694
+ });
2695
+ return {
2696
+ result: policyResult === null ? {
2697
+ kind: "noMatchingPolicy",
2698
+ checks: errors
2699
+ } : "allow" in policyResult && errors.length === 0 ? {
2700
+ kind: "ok",
2701
+ policy: policyResult.allow
2702
+ } : {
2703
+ kind: "unauthorized",
2704
+ policy: policyResult,
2705
+ checks: errors
2706
+ },
2707
+ world: snapshot()
2708
+ };
2709
+ } catch (e) {
2710
+ if (e instanceof ExecutionError) return {
2711
+ result: {
2712
+ kind: "execution",
2713
+ error: e.kind
2714
+ },
2715
+ world: snapshot()
2716
+ };
2717
+ throw e;
2718
+ }
2719
+ }
2720
+ function compareOrigins(a, b) {
2721
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
2722
+ const x = a[i] === void 0 ? -Infinity : a[i] ?? -1;
2723
+ const y = b[i] === void 0 ? -Infinity : b[i] ?? -1;
2724
+ if (x !== y) return x - y;
2725
+ }
2726
+ return 0;
2727
+ }
2728
+ //#endregion
2729
+ //#region src/base64.ts
2730
+ /** URL-safe base64 without padding, the wire form of a Biscuit token.
2731
+ * Implemented directly so the library stays runtime-agnostic (no Buffer,
2732
+ * no atob/btoa). */
2733
+ const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
2734
+ const REVERSE = new Map([...ALPHABET].map((c, i) => [c, i]));
2735
+ REVERSE.set("+", 62);
2736
+ REVERSE.set("/", 63);
2737
+ function toBase64(bytes) {
2738
+ let out = "";
2739
+ for (let i = 0; i < bytes.length; i += 3) {
2740
+ const b0 = bytes[i];
2741
+ const b1 = bytes[i + 1];
2742
+ const b2 = bytes[i + 2];
2743
+ out += ALPHABET[b0 >> 2];
2744
+ out += ALPHABET[(b0 & 3) << 4 | (b1 ?? 0) >> 4];
2745
+ if (b1 === void 0) break;
2746
+ out += ALPHABET[(b1 & 15) << 2 | (b2 ?? 0) >> 6];
2747
+ if (b2 === void 0) break;
2748
+ out += ALPHABET[b2 & 63];
2749
+ }
2750
+ return out;
2751
+ }
2752
+ function fromBase64(text) {
2753
+ const s = text.trim().replace(/=+$/, "");
2754
+ const out = new Uint8Array(Math.floor(s.length * 3 / 4));
2755
+ let o = 0;
2756
+ let acc = 0;
2757
+ let bits = 0;
2758
+ for (const c of s) {
2759
+ const v = REVERSE.get(c);
2760
+ if (v === void 0) throw new Error(`invalid base64 character ${JSON.stringify(c)}`);
2761
+ acc = acc << 6 | v;
2762
+ bits += 6;
2763
+ if (bits >= 8) {
2764
+ bits -= 8;
2765
+ out[o++] = acc >> bits & 255;
2766
+ }
2767
+ }
2768
+ return out.subarray(0, o);
2769
+ }
2770
+ //#endregion
2771
+ //#region src/builder.ts
2772
+ /**
2773
+ * The write path: minting tokens, attenuating them with extra blocks, and
2774
+ * sealing them. New blocks are signed with signature payload version 1, and
2775
+ * declare the lowest Datalog version their content legally requires.
2776
+ */
2777
+ const SIGNATURE_VERSION = 1;
2778
+ const OFFSET = 1024;
2779
+ var BuilderError = class extends Error {};
2780
+ var SymbolWriter = class {
2781
+ known;
2782
+ knownKeys;
2783
+ /** symbols added by this block, in insertion order */
2784
+ added = [];
2785
+ addedKeys = [];
2786
+ constructor(known = [], knownKeys = []) {
2787
+ this.known = known;
2788
+ this.knownKeys = knownKeys;
2789
+ }
2790
+ insert(s) {
2791
+ const d = DEFAULT_SYMBOLS.indexOf(s);
2792
+ if (d >= 0) return d;
2793
+ const k = this.known.indexOf(s);
2794
+ if (k >= 0) return OFFSET + k;
2795
+ let i = this.added.indexOf(s);
2796
+ if (i < 0) i = this.added.push(s) - 1;
2797
+ return OFFSET + this.known.length + i;
2798
+ }
2799
+ insertKey(key) {
2800
+ const k = this.knownKeys.indexOf(key);
2801
+ if (k >= 0) return k;
2802
+ let i = this.addedKeys.indexOf(key);
2803
+ if (i < 0) i = this.addedKeys.push(key) - 1;
2804
+ return this.knownKeys.length + i;
2805
+ }
2806
+ };
2807
+ const parseKeyString = (key) => {
2808
+ const [alg, h] = key.split("/");
2809
+ const bytes = new Uint8Array(h.length / 2);
2810
+ for (let i = 0; i < bytes.length; i++) bytes[i] = parseInt(h.substr(i * 2, 2), 16);
2811
+ return {
2812
+ algorithm: alg === "secp256r1" ? 1 : 0,
2813
+ key: bytes
2814
+ };
2815
+ };
2816
+ function toTerm(t, w) {
2817
+ switch (t.t) {
2818
+ case "var": return {
2819
+ kind: "variable",
2820
+ value: t.v
2821
+ };
2822
+ case "int": return {
2823
+ kind: "integer",
2824
+ value: t.v
2825
+ };
2826
+ case "str": return {
2827
+ kind: "string",
2828
+ value: w.insert(t.v)
2829
+ };
2830
+ case "date": return {
2831
+ kind: "date",
2832
+ value: t.v
2833
+ };
2834
+ case "bytes": return {
2835
+ kind: "bytes",
2836
+ value: t.v
2837
+ };
2838
+ case "bool": return {
2839
+ kind: "bool",
2840
+ value: t.v
2841
+ };
2842
+ case "null": return { kind: "null" };
2843
+ case "set": return {
2844
+ kind: "set",
2845
+ value: t.v.map((x) => toTerm(x, w))
2846
+ };
2847
+ case "array": return {
2848
+ kind: "array",
2849
+ value: t.v.map((x) => toTerm(x, w))
2850
+ };
2851
+ case "map": return {
2852
+ kind: "map",
2853
+ value: t.v.map((e) => ({
2854
+ key: e[0].t === "int" ? {
2855
+ kind: "integer",
2856
+ value: e[0].v
2857
+ } : {
2858
+ kind: "string",
2859
+ value: w.insert(e[0].v)
2860
+ },
2861
+ value: toTerm(e[1], w)
2862
+ }))
2863
+ };
2864
+ }
2865
+ }
2866
+ const toPredicate = (p, w) => ({
2867
+ name: w.insert(p.name),
2868
+ terms: p.terms.map((t) => toTerm(t, w))
2869
+ });
2870
+ const toOps = (ops, w) => ops.map((op) => {
2871
+ switch (op.kind) {
2872
+ case "value": return {
2873
+ kind: "value",
2874
+ value: toTerm(op.value, w)
2875
+ };
2876
+ case "unary": return {
2877
+ kind: "unary",
2878
+ op: op.op,
2879
+ ffiName: op.ffi ? w.insert(op.ffi) : void 0
2880
+ };
2881
+ case "binary": return {
2882
+ kind: "binary",
2883
+ op: op.op,
2884
+ ffiName: op.ffi ? w.insert(op.ffi) : void 0
2885
+ };
2886
+ case "closure": return {
2887
+ kind: "closure",
2888
+ params: op.params,
2889
+ ops: toOps(op.ops, w)
2890
+ };
2891
+ default: throw new Error(`unknown expression op kind ${op.kind}`);
2892
+ }
2893
+ });
2894
+ const toScope = (s, w) => s.kind === "authority" ? {
2895
+ kind: "type",
2896
+ value: 0
2897
+ } : s.kind === "previous" ? {
2898
+ kind: "type",
2899
+ value: 1
2900
+ } : {
2901
+ kind: "publicKey",
2902
+ value: w.insertKey(s.key)
2903
+ };
2904
+ const toRule = (r, w) => ({
2905
+ head: toPredicate(r.head, w),
2906
+ body: r.body.map((p) => toPredicate(p, w)),
2907
+ expressions: r.expressions.map((e) => toOps(e, w)),
2908
+ scope: r.scopes.map((s) => toScope(s, w))
2909
+ });
2910
+ function buildBlockMsg(content, knownSymbols = [], knownKeys = [], minVersion = 0) {
2911
+ const w = new SymbolWriter(knownSymbols, knownKeys);
2912
+ const facts = content.facts.map((f) => toPredicate(f.predicate, w));
2913
+ const rules = content.rules.map((r) => toRule(r, w));
2914
+ const checks = content.checks.map((c) => ({
2915
+ queries: c.queries.map((q) => toRule(q, w)),
2916
+ kind: c.kind === "all" ? 1 : c.kind === "reject" ? 2 : void 0
2917
+ }));
2918
+ const scope = content.scopes.map((s) => toScope(s, w));
2919
+ return {
2920
+ symbols: w.added,
2921
+ version: Math.max(requiredVersion(content), minVersion),
2922
+ facts,
2923
+ rules,
2924
+ checks,
2925
+ scope,
2926
+ publicKeys: w.addedKeys.map(parseKeyString)
2927
+ };
2928
+ }
2929
+ const contentFromCode = (code) => {
2930
+ const parsed = parseAuthorizer(code);
2931
+ if (parsed.policies.length) throw new BuilderError("allow/deny policies belong to the authorizer, not to a block");
2932
+ return {
2933
+ facts: parsed.facts,
2934
+ rules: parsed.rules,
2935
+ checks: parsed.checks,
2936
+ scopes: parsed.scopes
2937
+ };
2938
+ };
2939
+ function knownTables(token) {
2940
+ const symbols = [];
2941
+ const keys = [];
2942
+ const signed = [token.authority, ...token.blocks];
2943
+ for (const sb of signed) {
2944
+ const b = decodeBlock(sb.block);
2945
+ if (!sb.externalSignature) symbols.push(...b.symbols);
2946
+ for (const k of b.publicKeys) {
2947
+ const s = `${k.algorithm === 1 ? "secp256r1" : "ed25519"}/${Array.from(k.key, (x) => x.toString(16).padStart(2, "0")).join("")}`;
2948
+ if (!keys.includes(s)) keys.push(s);
2949
+ }
2950
+ }
2951
+ return {
2952
+ symbols,
2953
+ keys
2954
+ };
2955
+ }
2956
+ /** Mint a new token whose authority block holds `code`. */
2957
+ function buildToken(rootSecret, code, options = {}) {
2958
+ const algorithm = options.algorithm ?? 0;
2959
+ const blockBytes = encodeBlock(buildBlockMsg(typeof code === "string" ? contentFromCode(code) : code));
2960
+ const next = options.nextKeypair ?? generateKeypair(algorithm);
2961
+ const nextKey = {
2962
+ algorithm,
2963
+ key: next.publicKey
2964
+ };
2965
+ const signature = sign(authorityPayloadV1(blockBytes, nextKey, SIGNATURE_VERSION), rootSecret, algorithm);
2966
+ return encodeBiscuit({
2967
+ rootKeyId: options.rootKeyId,
2968
+ authority: {
2969
+ block: blockBytes,
2970
+ nextKey,
2971
+ signature,
2972
+ version: SIGNATURE_VERSION
2973
+ },
2974
+ blocks: [],
2975
+ proof: {
2976
+ kind: "nextSecret",
2977
+ value: next.secretKey
2978
+ }
2979
+ });
2980
+ }
2981
+ /** Append an attenuation block. Uses the token's own proof secret to sign. */
2982
+ function attenuate(tokenBytes, code, options = {}) {
2983
+ const token = decodeBiscuit(tokenBytes);
2984
+ if (token.proof.kind !== "nextSecret") throw new BuilderError("the token is sealed and cannot be attenuated");
2985
+ const algorithm = options.algorithm ?? 0;
2986
+ const currentSecret = token.proof.value;
2987
+ const previous = token.blocks.length ? token.blocks[token.blocks.length - 1] : token.authority;
2988
+ const currentAlgorithm = previous.nextKey.algorithm;
2989
+ const tables = knownTables(token);
2990
+ const blockBytes = encodeBlock(buildBlockMsg(typeof code === "string" ? contentFromCode(code) : code, tables.symbols, tables.keys));
2991
+ const next = options.nextKeypair ?? generateKeypair(algorithm);
2992
+ const nextKey = {
2993
+ algorithm,
2994
+ key: next.publicKey
2995
+ };
2996
+ const signature = sign(blockPayloadV1(blockBytes, nextKey, void 0, previous.signature, SIGNATURE_VERSION), currentSecret, currentAlgorithm);
2997
+ token.blocks.push({
2998
+ block: blockBytes,
2999
+ nextKey,
3000
+ signature,
3001
+ version: SIGNATURE_VERSION
3002
+ });
3003
+ token.proof = {
3004
+ kind: "nextSecret",
3005
+ value: next.secretKey
3006
+ };
3007
+ return encodeBiscuit(token);
3008
+ }
3009
+ /** Seal a token so no further block can be appended. */
3010
+ function sealToken(tokenBytes) {
3011
+ const token = decodeBiscuit(tokenBytes);
3012
+ if (token.proof.kind !== "nextSecret") return tokenBytes;
3013
+ const last = token.blocks.length ? token.blocks[token.blocks.length - 1] : token.authority;
3014
+ token.proof = {
3015
+ kind: "finalSignature",
3016
+ value: sign(sealPayloadV0(last), token.proof.value, last.nextKey.algorithm)
3017
+ };
3018
+ return encodeBiscuit(token);
3019
+ }
3020
+ /** What a token holder sends to a third party that will sign a block. */
3021
+ function thirdPartyRequest(tokenBytes) {
3022
+ const token = decodeBiscuit(tokenBytes);
3023
+ return { previousSignature: (token.blocks.length ? token.blocks[token.blocks.length - 1] : token.authority).signature };
3024
+ }
3025
+ /** The third party builds and signs a block without holding the token. */
3026
+ function thirdPartyBlock(request, externalSecret, code, algorithm = 0) {
3027
+ const blockBytes = encodeBlock(buildBlockMsg(typeof code === "string" ? contentFromCode(code) : code, [], [], 5));
3028
+ return {
3029
+ block: blockBytes,
3030
+ signature: sign(externalPayloadV1(blockBytes, request.previousSignature, SIGNATURE_VERSION), externalSecret, algorithm),
3031
+ publicKey: {
3032
+ algorithm,
3033
+ key: publicKeyFromSecret(externalSecret, algorithm)
3034
+ }
3035
+ };
3036
+ }
3037
+ /** The token holder appends a block signed by a third party. */
3038
+ function appendThirdParty(tokenBytes, response, options = {}) {
3039
+ const token = decodeBiscuit(tokenBytes);
3040
+ if (token.proof.kind !== "nextSecret") throw new BuilderError("the token is sealed and cannot be attenuated");
3041
+ const previous = token.blocks.length ? token.blocks[token.blocks.length - 1] : token.authority;
3042
+ const algorithm = options.algorithm ?? 0;
3043
+ const next = options.nextKeypair ?? generateKeypair(algorithm);
3044
+ const nextKey = {
3045
+ algorithm,
3046
+ key: next.publicKey
3047
+ };
3048
+ const signature = sign(blockPayloadV1(response.block, nextKey, response.signature, previous.signature, SIGNATURE_VERSION), token.proof.value, previous.nextKey.algorithm);
3049
+ token.blocks.push({
3050
+ block: response.block,
3051
+ nextKey,
3052
+ signature,
3053
+ externalSignature: {
3054
+ signature: response.signature,
3055
+ publicKey: response.publicKey
3056
+ },
3057
+ version: SIGNATURE_VERSION
3058
+ });
3059
+ token.proof = {
3060
+ kind: "nextSecret",
3061
+ value: next.secretKey
3062
+ };
3063
+ return encodeBiscuit(token);
3064
+ }
3065
+ //#endregion
3066
+ //#region src/index.ts
3067
+ /**
3068
+ * biscuit-ts — pure TypeScript Biscuit tokens.
3069
+ *
3070
+ * const root = generateKeypair();
3071
+ * const token = Biscuit.build(root.secretKey, 'user("alice");');
3072
+ * const text = token.toBase64();
3073
+ * const ok = Biscuit.fromBase64(text).verify(root.publicKey).authorize('allow if user("alice");');
3074
+ */
3075
+ /** An unverified token: it can be attenuated and re-serialized. */
3076
+ var Biscuit = class Biscuit {
3077
+ bytes;
3078
+ constructor(bytes) {
3079
+ this.bytes = bytes;
3080
+ }
3081
+ static build(rootSecret, code, options) {
3082
+ return new Biscuit(buildToken(rootSecret, code, options));
3083
+ }
3084
+ static fromBytes(bytes) {
3085
+ return new Biscuit(bytes);
3086
+ }
3087
+ static fromBase64(text) {
3088
+ return new Biscuit(fromBase64(text));
3089
+ }
3090
+ attenuate(code, options) {
3091
+ return new Biscuit(attenuate(this.bytes, code, options));
3092
+ }
3093
+ appendThirdParty(response, options) {
3094
+ return new Biscuit(appendThirdParty(this.bytes, response, options));
3095
+ }
3096
+ thirdPartyRequest() {
3097
+ return thirdPartyRequest(this.bytes);
3098
+ }
3099
+ seal() {
3100
+ return new Biscuit(sealToken(this.bytes));
3101
+ }
3102
+ toBase64() {
3103
+ return toBase64(this.bytes);
3104
+ }
3105
+ /** Verify the signature chain. Throws if the token is not authentic. */
3106
+ verify(rootPublicKey, rootAlgorithm = 0) {
3107
+ return new VerifiedBiscuit(loadToken(this.bytes, rootPublicKey, rootAlgorithm), this.bytes);
3108
+ }
3109
+ };
3110
+ /** A token whose signature chain has been checked against a root key. */
3111
+ var VerifiedBiscuit = class {
3112
+ token;
3113
+ bytes;
3114
+ constructor(token, bytes) {
3115
+ this.token = token;
3116
+ this.bytes = bytes;
3117
+ }
3118
+ get revocationIds() {
3119
+ return this.token.revocationIds;
3120
+ }
3121
+ /** the issuer's key identifier, when the token carries one */
3122
+ get rootKeyId() {
3123
+ return this.token.rootKeyId;
3124
+ }
3125
+ authorize(authorizerCode, options) {
3126
+ return authorize(this.token, authorizerCode, options);
3127
+ }
3128
+ };
3129
+ //#endregion
3130
+ export { Biscuit, BuilderError, DATALOG_3_1, DATALOG_3_2, DATALOG_3_3, DEFAULT_SYMBOLS, ExecutionError, MAX_SCHEMA_VERSION, MIN_SCHEMA_VERSION, ParseError, ProtoError, SignatureError, TokenError, VerifiedBiscuit, VersionError, appendThirdParty, attenuate, authorize, authorizeDetailed, blockFeatures, buildBlockMsg, buildToken, fromBase64, generateKeypair, loadToken, parseAuthorizer, peekRootKeyId, requiredVersion, sealToken, thirdPartyBlock, thirdPartyRequest, toBase64, validateBlockVersion };