authproof-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AuthProof
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,382 @@
1
+ # AuthProof
2
+
3
+ **Cryptographically signed delegation receipts for AI agents.**
4
+
5
+ Define exactly what an AI agent can and can't do. Sign it. Share it. Verify it anywhere.
6
+
7
+ ```js
8
+ import AuthProof from 'authproof';
9
+
10
+ const { privateKey, publicJwk } = await AuthProof.generateKey();
11
+
12
+ const { receiptId, systemPrompt } = await AuthProof.create({
13
+ scope: 'Search the web for competitor pricing and summarize findings.',
14
+ boundaries: 'Do not send emails. Do not make purchases.',
15
+ instructions: 'Cite every source. Keep the report under 500 words.',
16
+ ttlHours: 4,
17
+ privateKey,
18
+ publicJwk,
19
+ });
20
+
21
+ // Paste systemPrompt into any AI chat or agent system prompt
22
+ ```
23
+
24
+ ---
25
+
26
+ ## The Problem
27
+
28
+ AI agents are taking real actions — browsing the web, scheduling meetings, sending emails, making purchases. But there's no standard way to define what an agent is authorized to do, or prove it after the fact.
29
+
30
+ AuthProof fixes that with a simple, tamper-proof delegation receipt:
31
+
32
+ - **Cryptographically signed** — ECDSA P-256 via the native Web Crypto API
33
+ - **Content-addressed** — SHA-256 proof ID that changes if anything is modified
34
+ - **Time-windowed** — auto-expires, no open-ended permissions
35
+ - **Instantly revocable** — one call to invalidate
36
+ - **Zero dependencies** — works in Node.js, browsers, Deno, Bun, Cloudflare Workers
37
+
38
+ ---
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ npm install authproof
44
+ ```
45
+
46
+ Or load directly in a browser / Deno:
47
+
48
+ ```js
49
+ import AuthProof from 'https://esm.sh/authproof@1';
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Quick Start
55
+
56
+ ### 1. Generate a key pair
57
+
58
+ ```js
59
+ import AuthProof from 'authproof';
60
+
61
+ const { privateKey, publicJwk, privateJwk } = await AuthProof.generateKey();
62
+
63
+ // Store privateJwk securely (env var, secrets manager, etc.)
64
+ // publicJwk is safe to store anywhere
65
+ ```
66
+
67
+ ### 2. Create a receipt
68
+
69
+ ```js
70
+ const { receipt, receiptId, systemPrompt } = await AuthProof.create({
71
+ scope: 'Read the user\'s calendar for the next 7 days and identify conflicts.',
72
+ boundaries: 'Do not create, edit, or delete calendar events. Do not read emails.',
73
+ instructions: 'Present the 3 best available slots with a brief reason for each.',
74
+ ttlHours: 1, // Expires in 1 hour
75
+ privateKey,
76
+ publicJwk,
77
+ agentId: 'calendar-agent-v1', // optional
78
+ metadata: { requestedBy: 'user@example.com' }, // optional
79
+ });
80
+
81
+ console.log(receiptId); // 64-char SHA-256 proof ID
82
+ console.log(systemPrompt); // ready-to-paste AI system prompt
83
+ ```
84
+
85
+ ### 3. Use the system prompt
86
+
87
+ Paste `systemPrompt` into any AI:
88
+
89
+ ```
90
+ You are authorized to act within the following scope:
91
+
92
+ Read the user's calendar for the next 7 days and identify conflicts.
93
+
94
+ You must not:
95
+ Do not create, edit, or delete calendar events. Do not read emails.
96
+
97
+ Operator instructions:
98
+ Present the 3 best available slots with a brief reason for each.
99
+
100
+ This authorization is valid until: Apr 5, 2026, 3:00 PM
101
+ Authorization ID: a3f9c2...
102
+ ```
103
+
104
+ ### 4. Verify a receipt
105
+
106
+ ```js
107
+ const result = await AuthProof.verify(receipt, receiptId, {
108
+ action: 'Check calendar for next Tuesday and find a free hour',
109
+ });
110
+
111
+ if (result.authorized) {
112
+ console.log('✓ Action authorized');
113
+ } else {
114
+ console.log('✗ Denied:', result.checks.filter(c => !c.passed));
115
+ }
116
+ ```
117
+
118
+ ---
119
+
120
+ ## API Reference
121
+
122
+ ### `AuthProof.generateKey()`
123
+
124
+ Generate a new ECDSA P-256 signing key pair.
125
+
126
+ ```js
127
+ const { privateKey, publicJwk, privateJwk } = await AuthProof.generateKey();
128
+ ```
129
+
130
+ Returns:
131
+ - `privateKey` — `CryptoKey` for signing
132
+ - `publicJwk` — plain object, safe to store publicly
133
+ - `privateJwk` — plain object, **store securely**
134
+
135
+ ---
136
+
137
+ ### `AuthProof.importPrivateKey(privateJwk)`
138
+
139
+ Load a signing key from a stored JWK (e.g., from an env var).
140
+
141
+ ```js
142
+ const privateJwk = JSON.parse(process.env.AUTHPROOF_PRIVATE_JWK);
143
+ const privateKey = await AuthProof.importPrivateKey(privateJwk);
144
+ ```
145
+
146
+ ---
147
+
148
+ ### `AuthProof.create(options)`
149
+
150
+ Create a signed delegation receipt.
151
+
152
+ | Option | Type | Required | Description |
153
+ |---|---|---|---|
154
+ | `scope` | string | ✓ | What the AI is authorized to do |
155
+ | `boundaries` | string | ✓ | What the AI must never do |
156
+ | `instructions` | string | ✓ | How the AI should approach the task |
157
+ | `ttlHours` | number | — | Validity window in hours (default: `1`) |
158
+ | `privateKey` | CryptoKey | ✓ | ECDSA P-256 signing key |
159
+ | `publicJwk` | object | ✓ | Corresponding public key JWK |
160
+ | `agentId` | string | — | Optional agent identifier |
161
+ | `metadata` | object | — | Optional arbitrary metadata |
162
+
163
+ Returns `{ receipt, receiptId, systemPrompt }`.
164
+
165
+ ---
166
+
167
+ ### `AuthProof.verify(receipt, receiptId, options?)`
168
+
169
+ Verify a receipt locally — no network required.
170
+
171
+ ```js
172
+ const result = await AuthProof.verify(receipt, receiptId, {
173
+ revoked: false, // pass true if you've marked it revoked in your store
174
+ action: 'Proposed action text to scope-check',
175
+ });
176
+ ```
177
+
178
+ Returns:
179
+ ```js
180
+ {
181
+ authorized: true | false,
182
+ result: 'Human-readable verdict',
183
+ checks: [
184
+ { name: 'Not revoked', passed: true, detail: 'Active' },
185
+ { name: 'Within time window',passed: true, detail: 'Expires Apr 5, 2026' },
186
+ { name: 'Signature valid', passed: true, detail: 'ECDSA P-256 verified' },
187
+ { name: 'Receipt ID matches',passed: true, detail: 'SHA-256 hash verified' },
188
+ { name: 'Instructions intact',passed: true, detail: 'Instructions hash verified' },
189
+ // if action provided:
190
+ { name: 'Action within scope', passed: true, detail: '72% keyword match' },
191
+ { name: 'Action not blocked', passed: true, detail: '8% overlap with boundaries' },
192
+ ],
193
+ receiptContext: { scope, boundaries, operatorInstructions, timeWindow, ... }
194
+ }
195
+ ```
196
+
197
+ ---
198
+
199
+ ### `AuthProof.buildSystemPrompt(receipt, receiptId, verifyUrl?)`
200
+
201
+ Build a ready-to-use system prompt from a receipt.
202
+
203
+ ```js
204
+ const prompt = AuthProof.buildSystemPrompt(receipt, receiptId, 'https://authproof.dev');
205
+ ```
206
+
207
+ ---
208
+
209
+ ### `AuthProof.checkScope(action, receipt)`
210
+
211
+ Quick keyword-overlap scope check without full verification.
212
+
213
+ ```js
214
+ const { withinScope, scopeScore, boundaryScore } = AuthProof.checkScope(
215
+ 'Search Google for competitor pricing',
216
+ receipt
217
+ );
218
+ // { withinScope: true, scopeScore: 72, boundaryScore: 5 }
219
+ ```
220
+
221
+ ---
222
+
223
+ ### `AuthProof.isActive(receipt, revoked?)`
224
+
225
+ Returns `true` if the receipt is currently within its time window and not revoked.
226
+
227
+ ```js
228
+ AuthProof.isActive(receipt); // true
229
+ AuthProof.isActive(receipt, true); // false (revoked)
230
+ ```
231
+
232
+ ---
233
+
234
+ ### `AuthProof.secondsRemaining(receipt)`
235
+
236
+ Seconds until the receipt expires. Returns `0` if already expired.
237
+
238
+ ```js
239
+ const secs = AuthProof.secondsRemaining(receipt);
240
+ console.log(`Expires in ${Math.floor(secs / 60)} minutes`);
241
+ ```
242
+
243
+ ---
244
+
245
+ ### `AuthProof.receiptId(receipt)`
246
+
247
+ Compute the SHA-256 receipt ID from a receipt object.
248
+
249
+ ```js
250
+ const id = await AuthProof.receiptId(receipt);
251
+ ```
252
+
253
+ ---
254
+
255
+ ## Integrations
256
+
257
+ ### LangChain / LangGraph
258
+
259
+ ```js
260
+ const { systemPrompt, receiptId } = await AuthProof.create({ ... });
261
+
262
+ const messages = [
263
+ { role: 'system', content: systemPrompt },
264
+ { role: 'user', content: userMessage },
265
+ ];
266
+
267
+ // Pass to your LangChain chain or LangGraph node
268
+ ```
269
+
270
+ ### n8n
271
+
272
+ In a **Code node** before your AI node:
273
+
274
+ ```js
275
+ const AuthProof = require('authproof');
276
+ const privateKey = await AuthProof.importPrivateKey(JSON.parse($env.AUTHPROOF_PRIVATE_JWK));
277
+ const publicJwk = JSON.parse($env.AUTHPROOF_PUBLIC_JWK);
278
+
279
+ const { systemPrompt, receiptId } = await AuthProof.create({
280
+ scope: $input.item.json.scope,
281
+ boundaries: $input.item.json.boundaries,
282
+ instructions: $input.item.json.instructions,
283
+ ttlHours: 1,
284
+ privateKey,
285
+ publicJwk,
286
+ });
287
+
288
+ return [{ json: { ...$input.item.json, systemPrompt, receiptId } }];
289
+ ```
290
+
291
+ ### Express.js Middleware
292
+
293
+ ```js
294
+ import { authproofMiddleware } from 'authproof/examples/integrations.js';
295
+
296
+ app.use('/api/agent', authproofMiddleware(receiptStore));
297
+ // Agent must send: X-AuthProof-Receipt-Id header
298
+ ```
299
+
300
+ ### Cloudflare Workers / Deno
301
+
302
+ ```js
303
+ import AuthProof from 'https://esm.sh/authproof@1';
304
+ // All APIs work identically — uses native Web Crypto
305
+ ```
306
+
307
+ ---
308
+
309
+ ## Receipt Schema
310
+
311
+ ```json
312
+ {
313
+ "delegationId": "auth-1712345678901-a3f9c",
314
+ "issuedAt": "2026-04-05T14:00:00.000Z",
315
+ "scope": "Search the web for competitor pricing...",
316
+ "boundaries": "Do not send emails. Do not make purchases.",
317
+ "timeWindow": {
318
+ "start": "2026-04-05T14:00:00.000Z",
319
+ "end": "2026-04-05T18:00:00.000Z"
320
+ },
321
+ "operatorInstructions": "Cite every source. Keep under 500 words.",
322
+ "instructionsHash": "a3f9c2d1...",
323
+ "signerPublicKey": {
324
+ "kty": "EC", "crv": "P-256", "x": "...", "y": "..."
325
+ },
326
+ "agentId": "my-agent-v1",
327
+ "metadata": {},
328
+ "signature": "3045022100..."
329
+ }
330
+ ```
331
+
332
+ The **receipt ID** is `SHA-256(JSON.stringify(receipt))` — if any field changes, the ID changes.
333
+
334
+ ---
335
+
336
+ ## Supabase Backend (optional)
337
+
338
+ For persistent receipts with remote verify/revoke, deploy the included Edge Functions.
339
+ See the SQL schema and function code in [`examples/integrations.js`](examples/integrations.js).
340
+
341
+ ```sql
342
+ create table receipts (
343
+ hash text primary key,
344
+ delegation_id text not null,
345
+ scope text not null,
346
+ boundaries text not null,
347
+ time_window_start timestamptz not null,
348
+ time_window_end timestamptz not null,
349
+ operator_instructions text not null,
350
+ instructions_hash text not null,
351
+ signer_public_key jsonb not null,
352
+ signature text not null,
353
+ issued_at timestamptz not null,
354
+ revoked boolean default false,
355
+ revoked_at timestamptz
356
+ );
357
+ ```
358
+
359
+ ---
360
+
361
+ ## Interactive Tool
362
+
363
+ Not a developer? Use the **[AuthProof web app](https://authproof.dev)** — no code required.
364
+ Create, verify, and manage receipts visually in your browser.
365
+
366
+ ---
367
+
368
+ ## License
369
+
370
+ MIT — free for personal and commercial use.
371
+
372
+ ---
373
+
374
+ ## Contributing
375
+
376
+ PRs welcome. Run tests with:
377
+
378
+ ```bash
379
+ node tests/authproof.test.js
380
+ ```
381
+
382
+ 40 tests, zero dependencies, zero build step required.
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "authproof-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Cryptographically signed delegation receipts for AI agents. Define exactly what an AI can and can't do — signed, verifiable, tamper-proof.",
5
+ "main": "src/authproof.js",
6
+ "module": "src/authproof.js",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./src/authproof.js",
11
+ "require": "./src/authproof.cjs.js",
12
+ "default": "./src/authproof.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "test": "node --experimental-vm-modules tests/authproof.test.js",
17
+ "build:cjs": "node scripts/build-cjs.js"
18
+ },
19
+ "keywords": [
20
+ "ai",
21
+ "agents",
22
+ "authorization",
23
+ "delegation",
24
+ "receipt",
25
+ "cryptography",
26
+ "ecdsa",
27
+ "permissions",
28
+ "ai-safety",
29
+ "llm",
30
+ "agent-security"
31
+ ],
32
+ "author": "AuthProof",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/Commonguy25/authproof-sdk"
37
+ },
38
+ "homepage": "https://authproof.dev",
39
+ "bugs": {
40
+ "url": "https://github.com/Commonguy25/authproof-sdk/issues"
41
+ },
42
+ "engines": {
43
+ "node": ">=18.0.0"
44
+ },
45
+ "files": [
46
+ "src/",
47
+ "README.md",
48
+ "LICENSE"
49
+ ],
50
+ "devDependencies": {}
51
+ }
@@ -0,0 +1,446 @@
1
+ /**
2
+ * AuthProof SDK
3
+ * Cryptographically signed delegation receipts for AI agents.
4
+ *
5
+ * Works in: Node.js 18+, modern browsers, Deno, Bun, Cloudflare Workers
6
+ * Zero dependencies. Uses native Web Crypto API everywhere.
7
+ *
8
+ * @version 1.0.0
9
+ * @license MIT
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ // ─────────────────────────────────────────────
15
+ // CRYPTO PRIMITIVES
16
+ // ─────────────────────────────────────────────
17
+
18
+ const _enc = s => new TextEncoder().encode(s);
19
+ const _hex = b => Array.from(new Uint8Array(b)).map(x => x.toString(16).padStart(2, '0')).join('');
20
+ const _fromHex = h => new Uint8Array(h.match(/.{2}/g).map(b => parseInt(b, 16)));
21
+
22
+ async function _sha256(str) {
23
+ const buf = await crypto.subtle.digest('SHA-256', _enc(str));
24
+ return _hex(buf);
25
+ }
26
+
27
+ async function _generateKeyPair() {
28
+ return crypto.subtle.generateKey(
29
+ { name: 'ECDSA', namedCurve: 'P-256' },
30
+ true,
31
+ ['sign', 'verify']
32
+ );
33
+ }
34
+
35
+ async function _exportPublicJwk(publicKey) {
36
+ const jwk = await crypto.subtle.exportKey('jwk', publicKey);
37
+ const { kty, crv, x, y } = jwk;
38
+ return { kty, crv, x, y };
39
+ }
40
+
41
+ async function _sign(privateKey, str) {
42
+ const sig = await crypto.subtle.sign(
43
+ { name: 'ECDSA', hash: 'SHA-256' },
44
+ privateKey,
45
+ _enc(str)
46
+ );
47
+ return _hex(sig);
48
+ }
49
+
50
+ async function _verify(publicJwk, signature, str) {
51
+ try {
52
+ const pk = await crypto.subtle.importKey(
53
+ 'jwk',
54
+ { ...publicJwk, key_ops: ['verify'] },
55
+ { name: 'ECDSA', namedCurve: 'P-256' },
56
+ false,
57
+ ['verify']
58
+ );
59
+ return crypto.subtle.verify(
60
+ { name: 'ECDSA', hash: 'SHA-256' },
61
+ pk,
62
+ _fromHex(signature),
63
+ _enc(str)
64
+ );
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ // ─────────────────────────────────────────────
71
+ // SCOPE MATCHING
72
+ // ─────────────────────────────────────────────
73
+
74
+ const STOP_WORDS = new Set([
75
+ 'the','a','an','and','or','but','in','on','at','to','for','of','with',
76
+ 'by','from','is','are','was','were','be','been','have','has','had','do',
77
+ 'does','did','will','would','could','should','may','might','can','not',
78
+ 'no','this','that','these','those','i','you','he','she','it','we','they',
79
+ 'what','which','who','all','any','each','some','only','just','as','if',
80
+ 'then','up','out'
81
+ ]);
82
+
83
+ function _tokenize(text) {
84
+ return new Set(
85
+ text.toLowerCase()
86
+ .split(/[\s,;:.()\[\]{}'"""]+/)
87
+ .filter(w => w.length > 3 && !STOP_WORDS.has(w))
88
+ );
89
+ }
90
+
91
+ function _overlapScore(a, b) {
92
+ const ta = _tokenize(a);
93
+ const tb = _tokenize(b);
94
+ if (ta.size === 0) return 0;
95
+ let hits = 0;
96
+ for (const w of ta) if (tb.has(w)) hits++;
97
+ return hits / ta.size;
98
+ }
99
+
100
+ /**
101
+ * Check if a proposed action falls within scope and doesn't violate boundaries.
102
+ * Returns { withinScope: boolean, scopeScore: number, boundaryScore: number }
103
+ */
104
+ function checkScope(action, receipt) {
105
+ const scopeScore = _overlapScore(action, receipt.scope);
106
+ const boundaryScore = _overlapScore(action, receipt.boundaries);
107
+ return {
108
+ withinScope: scopeScore >= 0.3 && boundaryScore < 0.5,
109
+ scopeScore: Math.round(scopeScore * 100),
110
+ boundaryScore: Math.round(boundaryScore * 100),
111
+ };
112
+ }
113
+
114
+ // ─────────────────────────────────────────────
115
+ // KEY MANAGEMENT
116
+ // ─────────────────────────────────────────────
117
+
118
+ /**
119
+ * Generate a new ECDSA P-256 signing key pair.
120
+ * Returns exportable JWK representations for storage.
121
+ *
122
+ * @returns {{ privateJwk: object, publicJwk: object, privateKey: CryptoKey, publicKey: CryptoKey }}
123
+ */
124
+ async function generateKey() {
125
+ const kp = await _generateKeyPair();
126
+ const privateJwk = await crypto.subtle.exportKey('jwk', kp.privateKey);
127
+ const publicJwk = await _exportPublicJwk(kp.publicKey);
128
+ return {
129
+ privateKey: kp.privateKey,
130
+ publicKey: kp.publicKey,
131
+ privateJwk,
132
+ publicJwk,
133
+ };
134
+ }
135
+
136
+ /**
137
+ * Load a signing key from a stored private JWK.
138
+ * @param {object} privateJwk
139
+ * @returns {CryptoKey}
140
+ */
141
+ async function importPrivateKey(privateJwk) {
142
+ return crypto.subtle.importKey(
143
+ 'jwk',
144
+ privateJwk,
145
+ { name: 'ECDSA', namedCurve: 'P-256' },
146
+ false,
147
+ ['sign']
148
+ );
149
+ }
150
+
151
+ // ─────────────────────────────────────────────
152
+ // RECEIPT CREATION
153
+ // ─────────────────────────────────────────────
154
+
155
+ /**
156
+ * Create a signed delegation receipt.
157
+ *
158
+ * @param {object} options
159
+ * @param {string} options.scope - What the AI is authorized to do
160
+ * @param {string} options.boundaries - What the AI must never do
161
+ * @param {string} options.instructions - Operator instructions locked into the receipt
162
+ * @param {number} [options.ttlHours=1] - How long the receipt is valid (hours)
163
+ * @param {CryptoKey} options.privateKey - ECDSA P-256 private signing key
164
+ * @param {object} options.publicJwk - Corresponding public key JWK
165
+ * @param {string} [options.agentId] - Optional identifier for the agent being authorized
166
+ * @param {object} [options.metadata] - Optional arbitrary metadata attached to receipt
167
+ *
168
+ * @returns {{ receipt: object, receiptId: string, systemPrompt: string }}
169
+ */
170
+ async function create(options) {
171
+ const {
172
+ scope,
173
+ boundaries,
174
+ instructions,
175
+ ttlHours = 1,
176
+ privateKey,
177
+ publicJwk,
178
+ agentId,
179
+ metadata,
180
+ } = options;
181
+
182
+ if (!scope) throw new Error('AuthProof: scope is required');
183
+ if (!boundaries) throw new Error('AuthProof: boundaries is required');
184
+ if (!instructions) throw new Error('AuthProof: instructions is required');
185
+ if (!privateKey) throw new Error('AuthProof: privateKey is required');
186
+ if (!publicJwk) throw new Error('AuthProof: publicJwk is required');
187
+
188
+ const now = new Date();
189
+ const end = new Date(now.getTime() + ttlHours * 3_600_000);
190
+ const id = `auth-${now.getTime()}-${Math.random().toString(36).slice(2, 7)}`;
191
+ const instructionsHash = await _sha256(instructions);
192
+
193
+ const { kty, crv, x, y } = publicJwk;
194
+
195
+ const body = {
196
+ delegationId: id,
197
+ issuedAt: now.toISOString(),
198
+ scope,
199
+ boundaries,
200
+ timeWindow: { start: now.toISOString(), end: end.toISOString() },
201
+ operatorInstructions: instructions,
202
+ instructionsHash,
203
+ signerPublicKey: { kty, crv, x, y },
204
+ ...(agentId ? { agentId } : {}),
205
+ ...(metadata ? { metadata } : {}),
206
+ };
207
+
208
+ const signature = await _sign(privateKey, JSON.stringify(body));
209
+ const receipt = { ...body, signature };
210
+ const receiptId = await _sha256(JSON.stringify(receipt));
211
+
212
+ return {
213
+ receipt,
214
+ receiptId,
215
+ systemPrompt: buildSystemPrompt(receipt, receiptId),
216
+ };
217
+ }
218
+
219
+ // ─────────────────────────────────────────────
220
+ // RECEIPT VERIFICATION
221
+ // ─────────────────────────────────────────────
222
+
223
+ /**
224
+ * Verify a receipt locally (no network call).
225
+ * Checks: found, not revoked, within time window, signature valid, hash matches,
226
+ * and optionally whether a proposed action falls within scope.
227
+ *
228
+ * @param {object} receipt - The receipt object
229
+ * @param {string} receiptId - The SHA-256 receipt ID to verify against
230
+ * @param {object} [options]
231
+ * @param {boolean} [options.revoked] - Whether the receipt has been revoked (from your store)
232
+ * @param {string} [options.action] - Proposed action to scope-check
233
+ *
234
+ * @returns {{ authorized: boolean, checks: Array, receiptContext: object }}
235
+ */
236
+ async function verify(receipt, receiptId, options = {}) {
237
+ const { revoked = false, action } = options;
238
+ const checks = [];
239
+
240
+ // 1. Not revoked
241
+ const notRevoked = !revoked && !receipt.revoked;
242
+ checks.push({
243
+ name: 'Not revoked',
244
+ passed: notRevoked,
245
+ detail: notRevoked ? 'Active' : 'This authorization has been revoked',
246
+ });
247
+
248
+ // 2. Within time window
249
+ const now = new Date();
250
+ const start = new Date(receipt.timeWindow.start);
251
+ const end = new Date(receipt.timeWindow.end);
252
+ const inWindow = now >= start && now <= end;
253
+ checks.push({
254
+ name: 'Within time window',
255
+ passed: inWindow,
256
+ detail: inWindow
257
+ ? `Expires ${end.toLocaleString()}`
258
+ : now < start
259
+ ? `Not yet valid — starts ${start.toLocaleString()}`
260
+ : `Expired ${end.toLocaleString()}`,
261
+ });
262
+
263
+ // 3. Signature valid
264
+ const { signature, ...bodyWithoutSig } = receipt;
265
+ const sigValid = await _verify(
266
+ receipt.signerPublicKey,
267
+ signature,
268
+ JSON.stringify(bodyWithoutSig)
269
+ );
270
+ checks.push({
271
+ name: 'Signature valid',
272
+ passed: sigValid,
273
+ detail: sigValid
274
+ ? 'Cryptographic signature verified (ECDSA P-256)'
275
+ : 'Signature verification failed — receipt may be tampered',
276
+ });
277
+
278
+ // 4. Receipt ID matches
279
+ const computedId = await _sha256(JSON.stringify(receipt));
280
+ const idMatches = computedId === receiptId;
281
+ checks.push({
282
+ name: 'Receipt ID matches',
283
+ passed: idMatches,
284
+ detail: idMatches
285
+ ? 'SHA-256 content hash verified'
286
+ : 'ID mismatch — receipt content may have been altered',
287
+ });
288
+
289
+ // 5. Instructions hash matches
290
+ const computedIH = await _sha256(receipt.operatorInstructions);
291
+ const ihMatches = computedIH === receipt.instructionsHash;
292
+ checks.push({
293
+ name: 'Instructions intact',
294
+ passed: ihMatches,
295
+ detail: ihMatches
296
+ ? 'Instructions hash verified'
297
+ : 'Instructions hash mismatch — instructions may have been altered',
298
+ });
299
+
300
+ // 6. Optional scope check
301
+ if (action) {
302
+ const { withinScope, scopeScore, boundaryScore } = checkScope(action, receipt);
303
+ checks.push({
304
+ name: 'Action within scope',
305
+ passed: scopeScore >= 30,
306
+ detail: `${scopeScore}% keyword match with authorized scope`,
307
+ });
308
+ checks.push({
309
+ name: 'Action not blocked',
310
+ passed: boundaryScore < 50,
311
+ detail: `${boundaryScore}% overlap with off-limits boundaries`,
312
+ });
313
+ }
314
+
315
+ const authorized = checks.every(c => c.passed);
316
+
317
+ return {
318
+ authorized,
319
+ result: authorized
320
+ ? 'This action is authorized'
321
+ : 'Authorization denied — see checks for details',
322
+ checks,
323
+ receiptContext: {
324
+ scope: receipt.scope,
325
+ boundaries: receipt.boundaries,
326
+ operatorInstructions: receipt.operatorInstructions,
327
+ timeWindow: receipt.timeWindow,
328
+ issuedAt: receipt.issuedAt,
329
+ agentId: receipt.agentId,
330
+ revoked,
331
+ },
332
+ };
333
+ }
334
+
335
+ // ─────────────────────────────────────────────
336
+ // SYSTEM PROMPT BUILDER
337
+ // ─────────────────────────────────────────────
338
+
339
+ /**
340
+ * Build a ready-to-use system prompt block from a receipt.
341
+ * Paste this directly into any AI chat or agent system prompt.
342
+ *
343
+ * @param {object} receipt
344
+ * @param {string} receiptId
345
+ * @param {string} [verifyUrl] - Optional URL to your verify endpoint
346
+ * @returns {string}
347
+ */
348
+ function buildSystemPrompt(receipt, receiptId, verifyUrl) {
349
+ const expiry = new Date(receipt.timeWindow.end).toLocaleString();
350
+ const verifyLine = verifyUrl
351
+ ? `\nVerify this authorization: ${verifyUrl}?receipt=${receiptId}`
352
+ : `\nAuthorization ID: ${receiptId}`;
353
+
354
+ return `You are authorized to act within the following scope:
355
+
356
+ ${receipt.scope}
357
+
358
+ You must not:
359
+ ${receipt.boundaries}
360
+
361
+ Operator instructions:
362
+ ${receipt.operatorInstructions}
363
+
364
+ This authorization is valid until: ${expiry}${verifyLine}
365
+
366
+ Before taking any significant action, confirm it falls within the authorized scope above. If uncertain, ask for clarification rather than proceeding.`;
367
+ }
368
+
369
+ // ─────────────────────────────────────────────
370
+ // UTILITY
371
+ // ─────────────────────────────────────────────
372
+
373
+ /**
374
+ * Compute the receipt ID (SHA-256 of the full receipt JSON).
375
+ * Useful for re-deriving the ID from a stored receipt.
376
+ *
377
+ * @param {object} receipt
378
+ * @returns {string}
379
+ */
380
+ async function receiptId(receipt) {
381
+ return _sha256(JSON.stringify(receipt));
382
+ }
383
+
384
+ /**
385
+ * Check if a receipt is currently active (not expired, not revoked).
386
+ * @param {object} receipt
387
+ * @param {boolean} [revoked=false]
388
+ * @returns {boolean}
389
+ */
390
+ function isActive(receipt, revoked = false) {
391
+ if (revoked || receipt.revoked) return false;
392
+ const now = new Date();
393
+ return now >= new Date(receipt.timeWindow.start) && now <= new Date(receipt.timeWindow.end);
394
+ }
395
+
396
+ /**
397
+ * Get seconds remaining before a receipt expires.
398
+ * Returns 0 if already expired.
399
+ * @param {object} receipt
400
+ * @returns {number}
401
+ */
402
+ function secondsRemaining(receipt) {
403
+ const remaining = new Date(receipt.timeWindow.end) - new Date();
404
+ return Math.max(0, Math.floor(remaining / 1000));
405
+ }
406
+
407
+ // ─────────────────────────────────────────────
408
+ // EXPORTS
409
+ // ─────────────────────────────────────────────
410
+
411
+ const AuthProof = {
412
+ // Key management
413
+ generateKey,
414
+ importPrivateKey,
415
+
416
+ // Core protocol
417
+ create,
418
+ verify,
419
+
420
+ // Utilities
421
+ buildSystemPrompt,
422
+ checkScope,
423
+ receiptId,
424
+ isActive,
425
+ secondsRemaining,
426
+ };
427
+
428
+ // ESM + CJS compatible export
429
+ if (typeof module !== 'undefined' && module.exports) {
430
+ module.exports = AuthProof;
431
+ } else if (typeof globalThis !== 'undefined') {
432
+ globalThis.AuthProof = AuthProof;
433
+ }
434
+
435
+ export default AuthProof;
436
+ export {
437
+ generateKey,
438
+ importPrivateKey,
439
+ create,
440
+ verify,
441
+ buildSystemPrompt,
442
+ checkScope,
443
+ receiptId,
444
+ isActive,
445
+ secondsRemaining,
446
+ };