@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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022-2026 statewalker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,262 @@
1
+ # @statewalker/webrun-biscuit
2
+
3
+ [Biscuit](https://biscuitsec.org) authorization tokens in pure TypeScript: the protobuf codec, the
4
+ signature chain, the Datalog engine, the text parser, the authorizer and the token builder. No WASM,
5
+ no Node built-ins in `src/`, two runtime dependencies. It runs wherever the rest of the wire runs —
6
+ Node, browsers, Workers and Durable Objects.
7
+
8
+ ## Why this package exists
9
+
10
+ There is no other pure JS/TS Biscuit implementation. npm carries `@biscuit-auth/biscuit-wasm` — the
11
+ Rust crate compiled to WebAssembly — and a web-components package built on it. Nothing else.
12
+
13
+ For a browser-first or Durable-Object context a WASM blob is a real cost, not a stylistic objection:
14
+ bundle size, an instantiation step, and a platform surface that is not available everywhere. A token
15
+ format whose whole point is that the holder can attenuate it locally should not require half a
16
+ megabyte of WebAssembly to do so.
17
+
18
+ So this is a reimplementation, and the entire testing strategy below exists because a reimplementation
19
+ of a security primitive is only worth having if you can show it agrees with the original.
20
+
21
+ ## Installing and calling it
22
+
23
+ ```sh
24
+ npm install @statewalker/webrun-biscuit
25
+ ```
26
+
27
+ Two runtime dependencies, both external to the published bundle:
28
+ [`@noble/curves`](https://www.npmjs.com/package/@noble/curves) and
29
+ [`@noble/hashes`](https://www.npmjs.com/package/@noble/hashes). ESM only.
30
+
31
+ Mint a token, attenuate it, seal it, then verify and authorize:
32
+
33
+ ```ts
34
+ import { Biscuit, generateKeypair } from "@statewalker/webrun-biscuit";
35
+
36
+ const root = generateKeypair();
37
+
38
+ const token = Biscuit.build(root.secretKey, 'user("alice"); right("file1", "read");')
39
+ .attenuate('check if operation("read");')
40
+ .seal()
41
+ .toBase64();
42
+
43
+ const verified = Biscuit.fromBase64(token).verify(root.publicKey);
44
+ const result = verified.authorize('operation("read"); allow if user("alice");');
45
+ // { kind: 'ok', policy: 0 }
46
+ ```
47
+
48
+ `verify()` throws on an inauthentic token and returns a `VerifiedBiscuit`. That is deliberate: the
49
+ type system then prevents authorizing a token whose signature chain was never checked, which is the
50
+ mistake worth designing against.
51
+
52
+ ### Examples
53
+
54
+ secp256r1 works the same way, with the algorithm passed at both ends:
55
+
56
+ ```ts
57
+ const root = generateKeypair(1);
58
+ const token = Biscuit.build(root.secretKey, 'user("alice");', { algorithm: 1 });
59
+ const verified = Biscuit.fromBase64(token.toBase64()).verify(root.publicKey, 1);
60
+ ```
61
+
62
+ For key rotation, read the issuer's key id before choosing a root key. The token is unverified at
63
+ that point, so treat the id as a hint rather than a claim:
64
+
65
+ ```ts
66
+ import { peekRootKeyId } from "@statewalker/webrun-biscuit";
67
+
68
+ const key = roots[peekRootKeyId(bytes) ?? 0];
69
+ ```
70
+
71
+ A third party can sign a block without ever holding the token:
72
+
73
+ ```ts
74
+ const request = Biscuit.fromBase64(token).thirdPartyRequest();
75
+ const response = thirdPartyBlock(request, externalSecret, 'group("admin");');
76
+ const extended = Biscuit.fromBase64(token).appendThirdParty(response);
77
+ ```
78
+
79
+ ## Internals
80
+
81
+ ### How the modules stack up
82
+
83
+ Each module depends only on the ones above it.
84
+
85
+ | path | what it does |
86
+ | --- | --- |
87
+ | `src/proto.ts` | strict proto2 codec — BigInt int64, presence tracking, UTF-8 validation, unknown-field rejection |
88
+ | `src/crypto.ts` | Ed25519 + secp256r1 chain, payload v0/v1, sealing, external signatures |
89
+ | `src/datalog.ts` | term model, canonical keys, origin-tracked fixpoint engine, expression VM |
90
+ | `src/parser.ts` | Datalog text syntax to the runtime model |
91
+ | `src/print.ts` | pretty-printer for error messages and world snapshots |
92
+ | `src/version.ts` | block version bounds and Datalog feature gates |
93
+ | `src/authorizer.ts` | symbol resolution, block loading, authorization, world snapshots |
94
+ | `src/builder.ts` | minting, attenuation, sealing, third-party blocks |
95
+ | `src/base64.ts` | URL-safe base64, runtime-agnostic |
96
+ | `src/index.ts` | the public API — `Biscuit`, `VerifiedBiscuit` |
97
+
98
+ The one awkward edge is that `builder` imports `parseAuthorizer` and `DEFAULT_SYMBOLS` from
99
+ `authorizer`. Both are block-level concerns that happen to live there; it is a naming artefact rather
100
+ than a layering problem, and it would move to a `symbols.ts` if the file grew.
101
+
102
+ ### Decisions that would otherwise get undone
103
+
104
+ **Terms are compared through canonical string keys.** JS `Map` and `Set` are reference-keyed, while
105
+ the Rust implementation leans on derived `Hash`/`Ord` over a `BTreeSet`. Every structural comparison
106
+ and every de-duplication in the engine therefore goes through `termKey()`. Bypassing it gives you a
107
+ set that silently holds duplicates.
108
+
109
+ **Symbols are resolved to strings at load time** rather than kept as indices. Simpler, and
110
+ semantically equivalent — the only observable difference is that set ordering follows string order
111
+ instead of symbol-index order, which shows up in printing and not in results.
112
+
113
+ **Facts are indexed by `name/arity`**, cached per trusted-origin set and invalidated by a generation
114
+ counter, so a join never scans unrelated predicates.
115
+
116
+ **`Uint8Array` everywhere, never `Buffer`**, in `src/`. That is what makes the package runtime-
117
+ agnostic; tests may use Node APIs, the library may not.
118
+
119
+ **Default run limits are looser than the reference.** The reference defaults `max_time` to 1
120
+ millisecond, which is unreachably tight for a cold JS engine; this one defaults to 1 second. A caller
121
+ exposed to untrusted tokens should lower it deliberately rather than treat the default as a
122
+ denial-of-service bound.
123
+
124
+ ### The corpus is fetched, not vendored
125
+
126
+ `scripts/fetch-samples.mjs` downloads the official corpus into `samples/` (gitignored), pinned to
127
+ commit `b3d3fe2` of the **specification repository**,
128
+ [`eclipse-biscuit/biscuit`](https://github.com/eclipse-biscuit/biscuit). The samples belong to the
129
+ biscuit project, so they are not copied into this repo.
130
+
131
+ The canonical corpus lives in `samples/current` there, **not** in any single implementation: the copy
132
+ shipped inside `biscuit-rust` has drifted, and differs in `samples.json` and `test034_array_map.bc`
133
+ by one check.
134
+
135
+ The deprecated `v1` and `v2` corpora are fetched too, as negative fixtures. A current implementation
136
+ must reject them: every block declares a Datalog version, and versions outside 3 to 6 are invalid.
137
+ Without that check a v2 token verifies cleanly, which is a vulnerability rather than a compatibility
138
+ nicety.
139
+
140
+ Beyond the final authorization result, `09-world-snapshot` compares the entire post-run world against
141
+ the corpus — every derived fact, with its origin set. That catches rule-evaluation errors that happen
142
+ not to change the verdict.
143
+
144
+ ### A green suite is not the claim; a suite that can fail is
145
+
146
+ ```sh
147
+ pnpm test # the main suite, 156 tests
148
+ pnpm test:cross # against the reference implementation, 55 tests
149
+ pnpm test:all # both
150
+ pnpm mutate # inject 10 known defects, require the suite to catch each
151
+ pnpm build # dist/ plus declarations
152
+ ```
153
+
154
+ | file | covers |
155
+ | --- | --- |
156
+ | `01-proto` | byte-exact round-trip of all 38 sample tokens, i64 extremes, strict rejection |
157
+ | `02-crypto` | signature chain outcomes and revocation ids for all samples |
158
+ | `03-datalog` | engine unit tests ported from the Rust `datalog` module |
159
+ | `04-parser` | terms, precedence, closures, scopes, predicate/expression disambiguation |
160
+ | `05-conformance` | the official `samples.json` corpus — 38 tokens, 50 validations |
161
+ | `06-builder` | write path, corpus rebuilt from source, forged seals, deny policies |
162
+ | `07-api` | base64 and the public facade |
163
+ | `08-hardening` | property round-trips, mutation/truncation fuzzing, join scaling |
164
+ | `09-world-snapshot` | the post-run world per origin, against the official snapshots |
165
+ | `10-versions` | version bounds, feature gates, rejection of the deprecated v1/v2 corpora |
166
+
167
+ `scripts/mutate.mjs` is the check on all of it. It injects ten defects — a lenient protobuf decoder,
168
+ wrapping i64 arithmetic, `check all` degraded to `check if`, a universally trusting authorizer, `deny`
169
+ treated as `allow`, unchecked seal signatures, missing version bounds, and more — and requires each to
170
+ break at least one test. A surviving mutation is a hole in the tests, not a success. All ten are
171
+ caught. Two of them were **not** caught when the harness was first written: nothing verified a forged
172
+ seal signature, and nothing exercised a matching `deny` policy.
173
+
174
+ Each mutation's `find` string is a literal excerpt of the source, and must match exactly once. That is
175
+ why a reformat of `src/` makes the harness fail loudly rather than quietly stop testing anything.
176
+
177
+ ### Cross-reference tests run against the reference itself
178
+
179
+ `tests/cross/` checks interoperability against the Rust `biscuit-auth` crate compiled to WebAssembly
180
+ and published as `@biscuit-auth/biscuit-wasm`. These tests are only meaningful because the other side
181
+ is genuinely upstream, not a second reading of the same spec.
182
+
183
+ | file | direction |
184
+ | --- | --- |
185
+ | `01-native-to-ts` | the reference mints, we read — including a differential check on verdict, policy index and revocation ids |
186
+ | `02-ts-to-native` | we mint, the reference reads |
187
+ | `03-interop-chains` | blocks appended alternately by both, sealing honoured across the boundary, base64 interop, agreement on the stamped Datalog version |
188
+ | `04-random-differential` | generated programs, both directions, both algorithms — the reference is the oracle |
189
+
190
+ Nothing in `04` hard-codes the expected answer: each generated program is authorized by both
191
+ implementations and the verdicts compared, so a disagreement is a finding either way. Programs are
192
+ built around a (resource, operation, user) scenario and then perturbed rather than sampled uniformly,
193
+ because uniform sampling produces mostly `noMatchingPolicy`, where the engine barely runs.
194
+
195
+ That suite is kept out of `pnpm test` on purpose, and it skips gracefully when the reference is not
196
+ installed, so it never becomes a hard dependency of the main suite.
197
+
198
+ ## What will surprise you
199
+
200
+ **The reference build's first `authorize` call reports a timeout that is not real.** Every fresh
201
+ Authorizer reports a `RunLimit` timeout on its first call even with a 60-second budget; later calls on
202
+ the same object return in under a millisecond. `referenceOutcome` retries, so a genuine timeout still
203
+ fails. The same defect shows up under CPU contention on any call, which is why `pnpm test:cross` is a
204
+ separate script: a flaky reference must never be able to redden the main suite.
205
+
206
+ **The reference's run limits are a serde `Duration`.** `max_time` must be `{ secs, nanos }`. Passing a
207
+ plain number of nanoseconds does **not** error — deserialization fails quietly and the *default* 1 ms
208
+ limit applies, which then times out. It looks exactly like a slow engine.
209
+
210
+ **`PublicKey.toBytes()` throws** in that build. `toString()` returns `"<algorithm>/<hex>"` and is the
211
+ reliable accessor.
212
+
213
+ **The reference package cannot be imported in Node.** Its entry point does
214
+ `import * as wasm from "./biscuit_bg.wasm"`, which Node will not resolve, and its `exports` map has no
215
+ `require` condition, so `require.resolve` fails too. `tests/cross/reference.ts` finds the package by
216
+ walking up to `node_modules`, reads the `.wasm`, and supplies its imports by hand. Running Node with
217
+ `--experimental-wasm-modules` does work, but needing a flag for `pnpm test` is worse.
218
+
219
+ **A failed corpus download used to poison `samples/` permanently.** `samples.json` was written before
220
+ the tokens it names while the "already fetched" guard checked only `samples.json`, so an interrupted
221
+ fetch left a directory that every later run skipped as complete — and the tests then failed on missing
222
+ files forever. The manifest is now written last, and the guard checks every file it names. Downloads
223
+ are also pooled at six with retries, because `raw.githubusercontent.com` resets connections when
224
+ several dozen requests arrive at once and surfaces it as a bare `TypeError: fetch failed`.
225
+
226
+ **`pnpm test` fetches the corpus explicitly, not through `pretest`.** pnpm does not run `pre`/`post`
227
+ scripts by default, so a `pretest` hook would silently never fire and the suite would fail on a clean
228
+ checkout with missing samples.
229
+
230
+ ## Known deviations from the reference
231
+
232
+ **Regex uses JS `RegExp`, not RE2.** Every corpus pattern matches, but backreferences and lookaround
233
+ are accepted where Rust would reject them. Documented, not enforced.
234
+
235
+ **New blocks are signed with signature payload version 1** unconditionally. Datalog block versions, by
236
+ contrast, are computed from content: a block declares the lowest version that can legally carry it.
237
+
238
+ **Arrays and maps do not trigger the v3.3 feature gate.** The reference's `contains_v3_3_term` flags
239
+ only `null` and sets containing `null`, so a block using arrays may legally declare v3.1. This looks
240
+ like an upstream oversight and is mirrored deliberately, because matching the reference matters more
241
+ than being right here. `03-interop-chains` confirms it empirically.
242
+
243
+ **Not implemented:** authorizer snapshots (`AuthorizerSnapshot` / `SnapshotBlock`); a `query(rule)` API
244
+ for pulling facts out of an evaluated world, where `authorizeDetailed` exposes the whole world more
245
+ coarsely; a revocation-checking helper, since revocation ids are exposed but comparing them against a
246
+ list is left to the caller; and wire-compatible third-party blocks — `thirdPartyRequest()` returns a
247
+ plain object rather than the `ThirdPartyBlockRequest` protobuf message, so our own end-to-end
248
+ third-party flow works and is tested, but the cross-implementation one does not.
249
+
250
+ Semi-naive evaluation is not implemented either. The fixpoint re-derives every fact each round; the
251
+ index makes that cheap at token scale, but not at request scale with large fact sets.
252
+
253
+ ## Design notes
254
+
255
+ `docs/webrun-biscuit/` in this repository carries the working documents from the implementation
256
+ sessions: how the reference actually works, why `mapbox/pbf` was rejected for the protobuf layer, what
257
+ the red-green cycle caught, the corpus findings, and what it took to make random differential testing
258
+ actually detect a defect.
259
+
260
+ ## License
261
+
262
+ MIT — see the repository `LICENSE`.
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Turns a serialized token plus authorizer code into an authorization result,
3
+ * mirroring the evaluation order of the reference implementation.
4
+ */
5
+ import { type Check, type ExternFn, type Fact, type Rule, type RunLimits, type Scope } from "./datalog.js";
6
+ export declare const DEFAULT_SYMBOLS: string[];
7
+ export declare class TokenError extends Error {
8
+ readonly kind: "Format" | "Symbol";
9
+ constructor(kind: "Format" | "Symbol", message: string);
10
+ }
11
+ export interface RuntimeBlock {
12
+ facts: Fact[];
13
+ rules: Rule[];
14
+ checks: Check[];
15
+ scopes: Scope[];
16
+ externalKey?: string;
17
+ /** variable id -> name, for printing rules in error messages */
18
+ varNames: Map<number, string>;
19
+ }
20
+ export interface LoadedToken {
21
+ blocks: RuntimeBlock[];
22
+ publicKeyToBlockIds: Map<string, number[]>;
23
+ revocationIds: string[];
24
+ /** the issuer's key identifier, when the token carries one — callers use it
25
+ * to pick the right root key during rotation */
26
+ rootKeyId?: number;
27
+ }
28
+ export declare function loadToken(bytes: Uint8Array, rootPublicKey: Uint8Array,
29
+ /** the root key's algorithm: 0 = Ed25519, 1 = secp256r1 */
30
+ rootAlgorithm?: 0 | 1): LoadedToken;
31
+ /**
32
+ * Reads the key identifier from a token **without verifying it**, so a caller
33
+ * can choose which root key to verify against. The token is untrusted at this
34
+ * point: the id is a hint, not a claim.
35
+ */
36
+ export declare function peekRootKeyId(bytes: Uint8Array): number | undefined;
37
+ export type FailedCheck = {
38
+ source: "authorizer";
39
+ checkId: number;
40
+ } | {
41
+ source: "block";
42
+ blockId: number;
43
+ checkId: number;
44
+ };
45
+ export type AuthorizationResult = {
46
+ kind: "ok";
47
+ policy: number;
48
+ } | {
49
+ kind: "unauthorized";
50
+ policy: {
51
+ allow: number;
52
+ } | {
53
+ deny: number;
54
+ };
55
+ checks: FailedCheck[];
56
+ } | {
57
+ kind: "noMatchingPolicy";
58
+ checks: FailedCheck[];
59
+ } | {
60
+ kind: "execution";
61
+ error: string;
62
+ } | {
63
+ kind: "invalidBlockRule";
64
+ blockId: number;
65
+ rule: string;
66
+ } | {
67
+ kind: "format";
68
+ error: string;
69
+ };
70
+ /** the shape of the `world` snapshot in the official sample corpus */
71
+ export interface WorldSnapshot {
72
+ facts: {
73
+ origin: (number | null)[];
74
+ facts: string[];
75
+ }[];
76
+ rules: {
77
+ origin: number;
78
+ rules: string[];
79
+ }[];
80
+ checks: {
81
+ origin: number;
82
+ checks: string[];
83
+ }[];
84
+ policies: string[];
85
+ }
86
+ export interface AuthorizeDetails {
87
+ result: AuthorizationResult;
88
+ world: WorldSnapshot;
89
+ }
90
+ interface AuthorizerCode {
91
+ facts: Fact[];
92
+ rules: Rule[];
93
+ checks: Check[];
94
+ policies: {
95
+ kind: "allow" | "deny";
96
+ queries: Rule[];
97
+ }[];
98
+ scopes: Scope[];
99
+ varNames: Map<number, string>;
100
+ }
101
+ export declare function parseAuthorizer(src: string): AuthorizerCode;
102
+ export interface AuthorizeOptions {
103
+ limits?: RunLimits;
104
+ /** extern functions callable as `.extern::name(...)` */
105
+ externs?: Map<string, ExternFn>;
106
+ }
107
+ export declare function authorize(token: LoadedToken, authorizerSrc: string, options?: AuthorizeOptions): AuthorizationResult;
108
+ /** Same as `authorize`, and also returns the post-run world, in the shape the
109
+ * official sample corpus records it. */
110
+ export declare function authorizeDetailed(token: LoadedToken, authorizerSrc: string, options?: AuthorizeOptions): AuthorizeDetails;
111
+ export {};
112
+ //# sourceMappingURL=authorizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorizer.d.ts","sourceRoot":"","sources":["../src/authorizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function toBase64(bytes: Uint8Array): string;
2
+ export declare function fromBase64(text: string): Uint8Array;
3
+ //# sourceMappingURL=base64.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64.d.ts","sourceRoot":"","sources":["../src/base64.ts"],"names":[],"mappings":"AAQA,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAclD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAiBnD"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The write path: minting tokens, attenuating them with extra blocks, and
3
+ * sealing them. New blocks are signed with signature payload version 1, and
4
+ * declare the lowest Datalog version their content legally requires.
5
+ */
6
+ import { generateKeypair } from "./crypto.js";
7
+ import type { Check, Predicate, Rule, Scope } from "./datalog.js";
8
+ import { type BlockMsg, type PublicKeyMsg } from "./proto.js";
9
+ export declare class BuilderError extends Error {
10
+ }
11
+ export interface Keypair {
12
+ secretKey: Uint8Array;
13
+ publicKey: Uint8Array;
14
+ }
15
+ export { generateKeypair };
16
+ export interface BlockContent {
17
+ facts: {
18
+ predicate: Predicate;
19
+ }[];
20
+ rules: Rule[];
21
+ checks: Check[];
22
+ scopes: Scope[];
23
+ }
24
+ export declare function buildBlockMsg(content: BlockContent, knownSymbols?: string[], knownKeys?: string[], minVersion?: number): BlockMsg;
25
+ export interface BuildOptions {
26
+ rootKeyId?: number;
27
+ /** supply a next keypair instead of generating one (tests, determinism) */
28
+ nextKeypair?: Keypair;
29
+ algorithm?: 0 | 1;
30
+ }
31
+ /** Mint a new token whose authority block holds `code`. */
32
+ export declare function buildToken(rootSecret: Uint8Array, code: string | BlockContent, options?: BuildOptions): Uint8Array;
33
+ /** Append an attenuation block. Uses the token's own proof secret to sign. */
34
+ export declare function attenuate(tokenBytes: Uint8Array, code: string | BlockContent, options?: BuildOptions): Uint8Array;
35
+ /** Seal a token so no further block can be appended. */
36
+ export declare function sealToken(tokenBytes: Uint8Array): Uint8Array;
37
+ export interface ThirdPartyRequest {
38
+ /** signature of the block this attenuation will follow */
39
+ previousSignature: Uint8Array;
40
+ }
41
+ /** What a token holder sends to a third party that will sign a block. */
42
+ export declare function thirdPartyRequest(tokenBytes: Uint8Array): ThirdPartyRequest;
43
+ export interface ThirdPartyResponse {
44
+ block: Uint8Array;
45
+ signature: Uint8Array;
46
+ publicKey: PublicKeyMsg;
47
+ }
48
+ /** The third party builds and signs a block without holding the token. */
49
+ export declare function thirdPartyBlock(request: ThirdPartyRequest, externalSecret: Uint8Array, code: string | BlockContent, algorithm?: 0 | 1): ThirdPartyResponse;
50
+ /** The token holder appends a block signed by a third party. */
51
+ export declare function appendThirdParty(tokenBytes: Uint8Array, response: ThirdPartyResponse, options?: BuildOptions): Uint8Array;
52
+ //# sourceMappingURL=builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAIL,eAAe,EAIhB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,KAAK,EAAM,SAAS,EAAE,IAAI,EAAE,KAAK,EAAQ,MAAM,cAAc,CAAC;AAC5E,OAAO,EAEL,KAAK,QAAQ,EAQb,KAAK,YAAY,EAIlB,MAAM,YAAY,CAAC;AAMpB,qBAAa,YAAa,SAAQ,KAAK;CAAG;AAE1C,MAAM,WAAW,OAAO;IACtB,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE,UAAU,CAAC;CACvB;AACD,OAAO,EAAE,eAAe,EAAE,CAAC;AAgH3B,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE;QAAE,SAAS,EAAE,SAAS,CAAA;KAAE,EAAE,CAAC;IAClC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EACrB,YAAY,GAAE,MAAM,EAAO,EAC3B,SAAS,GAAE,MAAM,EAAO,EACxB,UAAU,SAAI,GACb,QAAQ,CAkBV;AA4BD,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;CACnB;AAED,2DAA2D;AAC3D,wBAAgB,UAAU,CACxB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,MAAM,GAAG,YAAY,EAC3B,OAAO,GAAE,YAAiB,GACzB,UAAU,CAgBZ;AAED,8EAA8E;AAC9E,wBAAgB,SAAS,CACvB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,MAAM,GAAG,YAAY,EAC3B,OAAO,GAAE,YAAiB,GACzB,UAAU,CA4BZ;AAED,wDAAwD;AACxD,wBAAgB,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CAO5D;AAID,MAAM,WAAW,iBAAiB;IAChC,0DAA0D;IAC1D,iBAAiB,EAAE,UAAU,CAAC;CAC/B;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,iBAAiB,CAI3E;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE,YAAY,CAAC;CACzB;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAC7B,OAAO,EAAE,iBAAiB,EAC1B,cAAc,EAAE,UAAU,EAC1B,IAAI,EAAE,MAAM,GAAG,YAAY,EAC3B,SAAS,GAAE,CAAC,GAAG,CAAK,GACnB,kBAAkB,CAWpB;AAED,gEAAgE;AAChE,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,GAAE,YAAiB,GACzB,UAAU,CA2BZ"}
@@ -0,0 +1,22 @@
1
+ import type { BiscuitMsg, PublicKeyMsg, SignedBlock } from "./proto.js";
2
+ export declare class SignatureError extends Error {
3
+ }
4
+ declare const bytesEqual: (a: Uint8Array, b: Uint8Array) => boolean;
5
+ export declare function publicKeyFromSecret(secret: Uint8Array, algorithm: number): Uint8Array;
6
+ export declare function blockPayloadV0(data: Uint8Array, nextKey: PublicKeyMsg, externalSig?: Uint8Array): Uint8Array;
7
+ export declare function authorityPayloadV1(data: Uint8Array, nextKey: PublicKeyMsg, version: number): Uint8Array;
8
+ export declare function blockPayloadV1(data: Uint8Array, nextKey: PublicKeyMsg, externalSig: Uint8Array | undefined, previousSignature: Uint8Array, version: number): Uint8Array;
9
+ export declare function externalPayloadV1(data: Uint8Array, previousSignature: Uint8Array, version: number): Uint8Array;
10
+ export declare function sealPayloadV0(block: SignedBlock): Uint8Array;
11
+ export declare function sign(payload: Uint8Array, secret: Uint8Array, algorithm: number): Uint8Array;
12
+ /** a fresh keypair for the given algorithm */
13
+ export declare function generateKeypair(algorithm?: 0 | 1): {
14
+ secretKey: Uint8Array;
15
+ publicKey: Uint8Array;
16
+ };
17
+ export { bytesEqual };
18
+ /** Verifies the full signature chain and the proof. Throws on failure. */
19
+ export declare function verifyToken(token: BiscuitMsg, rootPublicKey: Uint8Array, rootAlgorithm?: number): void;
20
+ /** Revocation identifier of each block: its signature bytes. */
21
+ export declare function revocationIds(token: BiscuitMsg): Uint8Array[];
22
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAExE,qBAAa,cAAe,SAAQ,KAAK;CAAG;AAkB5C,QAAA,MAAM,UAAU,MAAO,UAAU,KAAK,UAAU,KAAG,OACK,CAAC;AAwBzD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,CAIrF;AAID,wBAAgB,cAAc,CAC5B,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,YAAY,EACrB,WAAW,CAAC,EAAE,UAAU,GACvB,UAAU,CAEZ;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,MAAM,GACd,UAAU,CAWZ;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,UAAU,GAAG,SAAS,EACnC,iBAAiB,EAAE,UAAU,EAC7B,OAAO,EAAE,MAAM,GACd,UAAU,CAcZ;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,UAAU,EAChB,iBAAiB,EAAE,UAAU,EAC7B,OAAO,EAAE,MAAM,GACd,UAAU,CASZ;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,CAE5D;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,CAK3F;AAED,8CAA8C;AAC9C,wBAAgB,eAAe,CAAC,SAAS,GAAE,CAAC,GAAG,CAAK,GAAG;IACrD,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE,UAAU,CAAC;CACvB,CAIA;AAED,OAAO,EAAE,UAAU,EAAE,CAAC;AAItB,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,SAAI,GAAG,IAAI,CAuDjG;AAED,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,EAAE,CAE7D"}