@bounded-authority-protocol/verifier 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +112 -0
  2. package/package.json +7 -5
package/README.md CHANGED
@@ -125,6 +125,105 @@ local-development proof profile (`localLoopbackHttpProofSigningInput` and friend
125
125
  `127.0.0.1`/`[::1]` targets only, mandatory nonce; standard `dpop+jwt` rejects its bytes).
126
126
  The full export list is [`src/index.ts`](src/index.ts).
127
127
 
128
+ ## Pairing with the signer
129
+
130
+ This package verifies; it never signs and never holds a private key. Its sibling
131
+ [`@bounded-authority-protocol/signer`](https://www.npmjs.com/package/@bounded-authority-protocol/signer)
132
+ produces the signed bytes — and it does so by calling THIS package's producer functions
133
+ (`grantSigningInput`, `proofSigningInput`, `boundaryAnchorSigningInput`,
134
+ `keyTransitionSigningInput`) and delegating the cryptography to a caller-owned key handle.
135
+ The dependency direction is one-way: the signer depends on the verifier at runtime
136
+ (`^0.2.0`), never the reverse. Compatibility between the two is governed by the wire
137
+ contract-majors — both packages carry majors 1 and 2 side by side — not by package
138
+ version numbers.
139
+
140
+ A production flow has three roles: the issuer mints a grant, the holder binds one
141
+ invocation of it with a proof, and the resource verifies the pair through this package.
142
+
143
+ ```ts
144
+ // Issuer — mint a grant. The private key never enters the signer; your handle
145
+ // routes each sign() to your own custody (KMS, HSM, or an in-process test key).
146
+ import { signGrant, signReport, type KeyHandle } from "@bounded-authority-protocol/signer";
147
+ import { checkEnvelope } from "@bounded-authority-protocol/verifier";
148
+
149
+ const issuerHandle: KeyHandle = {
150
+ sign: (message) => issuerCustody.sign(message), // Ed25519, 64 bytes
151
+ publicKey: () => issuerPublicKey32,
152
+ thumbprint: () => issuerThumbprint, // RFC 7638, base64url
153
+ // Grants additionally require an atomic issuer-role identity — the C1 gate:
154
+ // a holder-role handle fails closed before sign() is ever called.
155
+ signingIdentity: () => ({ role: "issuer", keyId: "issuer-key", publicKey: issuerPublicKey32 }),
156
+ };
157
+
158
+ // SignerResult mirrors this package's Result<T>: check ok before reading value.
159
+ const grantResult = await signGrant(
160
+ {
161
+ issuer: "https://issuer.example",
162
+ grantId: "g-1",
163
+ audiences: ["https://resource.example"],
164
+ issuedAt: 1_731_728_000,
165
+ notBefore: 1_731_728_000,
166
+ expiresAt: 1_733_728_000,
167
+ holderThumbprint, // RFC 7638 base64url of the holder's public JWK (the thumbprint primitive)
168
+ operations: [
169
+ { name: "transfer", selectors: [{ kind: "equals", path: ["amount"], value: { t: "int", v: 5000 } }] },
170
+ ],
171
+ },
172
+ issuerHandle,
173
+ );
174
+ if (!grantResult.ok) throw new Error(`signing failed: ${grantResult.error}`);
175
+ const grantCompact = grantResult.value.grant;
176
+
177
+ // Holder — bind one invocation of that grant with a holder proof. The holder's
178
+ // handle is constructed exactly like the issuer's (reports need no
179
+ // signingIdentity — proofs are holder-signed by definition), and the proof
180
+ // timestamp is pinned the same way: this verifier reads no clock.
181
+ const proofResult = await signReport(
182
+ {
183
+ grantCompact: grantCompact,
184
+ operation: "transfer",
185
+ method: "POST",
186
+ targetUri: "https://api.example.test/invoke",
187
+ invocationId: "9a1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d", // a lowercase RFC 4122 UUID — the wire requires it
188
+ castArguments: { t: "object", v: new Map([["amount", { t: "int", v: 5000 }]]) },
189
+ },
190
+ holderHandle,
191
+ { issuedAt: 1_731_728_030 },
192
+ );
193
+ if (!proofResult.ok) throw new Error(`signing failed: ${proofResult.error}`);
194
+ const envelope = proofResult.value;
195
+
196
+ // Resource — THIS package, and nothing else. Facts, never a decision.
197
+ const result = checkEnvelope(envelope.grant, envelope.proof, {
198
+ trustedIssuer: { keyId: "issuer-key", publicKey: issuerPublicKey32 },
199
+ issuer: "https://issuer.example",
200
+ audience: "https://resource.example",
201
+ method: "POST",
202
+ targetUri: "https://api.example.test/invoke",
203
+ invocationId: "9a1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
204
+ operation: "transfer",
205
+ castArguments: { t: "object", v: new Map([["amount", { t: "int", v: 5000 }]]) },
206
+ evaluationTime: 1_731_728_060,
207
+ clockSkew: 60,
208
+ proofMaxAge: 300,
209
+ nonce: { kind: "not_required" },
210
+ });
211
+ ```
212
+
213
+ Two properties make the pairing safe to build against:
214
+
215
+ - **Key custody stays at the caller.** The signer's `KeyHandle` is a callback interface
216
+ (`sign`/`publicKey`/`thumbprint`, plus atomic `keyIdentity`/`signingIdentity` snapshots
217
+ for anchors, transitions, and role-gated grant signing). A handle fault or a
218
+ wrong-key rotation race fails loudly as `signing_failed` — the signer verifies every
219
+ signature against the resolved public key before assembly. The full contract is
220
+ documented in the [signer's README](https://github.com/baselabs/bounded_authority_signer_typescript#readme).
221
+ - **The two packages cross-validate in CI.** The signer's test oracle produces
222
+ compacts through its own surface and verifies every one of them through THIS
223
+ package before release — signing-side drift is caught in the signer's CI against
224
+ the verifier version its lockfile resolves, and verifier-side changes are gated
225
+ by this package's certified conformance corpora.
226
+
128
227
  ## Certified, not self-tested
129
228
 
130
229
  Every release of this SDK is verified against the protocol's published, cryptographically
@@ -167,6 +266,18 @@ the host — a facts value is evidence, never a credential.
167
266
  - [ADR 0014 — cross-language verifier SDKs](https://github.com/baselabs/bounded_authority_protocol/blob/main/docs/adr/0014-cross-language-verifier-sdks.md) ·
168
267
  [ADR 0015 — graduation and publish topology](https://github.com/baselabs/bounded_authority_protocol/blob/main/docs/adr/0015-sdk-graduation-and-publish-topology.md)
169
268
 
269
+ ## Related packages
270
+
271
+ - [bounded_authority_protocol (monorepo)](https://github.com/baselabs/bounded_authority_protocol) —
272
+ the protocol source: specifications, ADRs, the certified conformance corpora this
273
+ package vendors, and the reference Elixir implementation.
274
+ - [@bounded-authority-protocol/signer](https://www.npmjs.com/package/@bounded-authority-protocol/signer) —
275
+ the holder/issuer companion: signs proofs, grants, boundary anchors, and key
276
+ transitions through a caller-owned key handle. This verifier holds no private keys;
277
+ the signer produces the bytes it checks (see [Pairing with the signer](#pairing-with-the-signer)).
278
+ - This package on npm:
279
+ [@bounded-authority-protocol/verifier](https://www.npmjs.com/package/@bounded-authority-protocol/verifier).
280
+
170
281
  ## Development
171
282
 
172
283
  ```bash
@@ -176,6 +287,7 @@ pnpm test # unit + struct + façade corpus-vector tests
176
287
  pnpm test:permissiveness # the mutation gate
177
288
  pnpm conformance # 283/283 + key census (vendored v1 snapshot)
178
289
  pnpm conformance:v2 # 268/268 + key census (vendored v2 snapshot)
290
+ pnpm check:currency # dependency-currency gate (latest-first)
179
291
  ```
180
292
 
181
293
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bounded-authority-protocol/verifier",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Provider-neutral, deterministic verifier SDK for bounded proof-of-possession authority — a typed TypeScript reimplementation of the Bounded Authority Protocol verification profiles (wire contract-majors 1 and 2).",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -20,6 +20,7 @@
20
20
  "typecheck": "tsc --noEmit",
21
21
  "lint": "eslint .",
22
22
  "license-check": "node tools/license-check.mjs",
23
+ "check:currency": "node tools/check-currency.mjs",
23
24
  "test": "node --test --import tsx/esm test/*.test.ts",
24
25
  "test:permissiveness": "node --test --import tsx/esm test/permissiveness.ts",
25
26
  "conformance": "tsx conformance/run.ts",
@@ -27,11 +28,12 @@
27
28
  "census": "tsx conformance/census.ts"
28
29
  },
29
30
  "devDependencies": {
30
- "@types/node": "^26.4.0",
31
- "eslint": "^10.9.1",
32
- "tsx": "^4.23.12",
31
+ "@types/node": "^26.6.1",
32
+ "eslint": "^10.10.0",
33
+ "semver": "^7.8.5",
34
+ "tsx": "^4.23.13",
33
35
  "typescript": "^6.0.3",
34
- "typescript-eslint": "^8.68.0"
36
+ "typescript-eslint": "^8.70.0"
35
37
  },
36
38
  "repository": {
37
39
  "type": "git",