@agentguard-run/spend 0.15.7 → 0.15.9
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/README.md +61 -1
- package/dist/anchor/index.d.ts +35 -0
- package/dist/anchor/index.d.ts.map +1 -0
- package/dist/anchor/index.js +82 -0
- package/dist/anchor/index.js.map +1 -0
- package/dist/anchor/opentimestamps.d.ts +86 -0
- package/dist/anchor/opentimestamps.d.ts.map +1 -0
- package/dist/anchor/opentimestamps.js +239 -0
- package/dist/anchor/opentimestamps.js.map +1 -0
- package/dist/anchor/store.d.ts +29 -0
- package/dist/anchor/store.d.ts.map +1 -0
- package/dist/anchor/store.js +130 -0
- package/dist/anchor/store.js.map +1 -0
- package/dist/anchor/types.d.ts +84 -0
- package/dist/anchor/types.d.ts.map +1 -0
- package/dist/anchor/types.js +28 -0
- package/dist/anchor/types.js.map +1 -0
- package/dist/cli/anchor.d.ts +10 -0
- package/dist/cli/anchor.d.ts.map +1 -0
- package/dist/cli/anchor.js +177 -0
- package/dist/cli/anchor.js.map +1 -0
- package/dist/cli/colors.d.ts +1 -0
- package/dist/cli/colors.d.ts.map +1 -1
- package/dist/cli/colors.js +4 -0
- package/dist/cli/colors.js.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +10 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/sample.d.ts +43 -0
- package/dist/cli/sample.d.ts.map +1 -0
- package/dist/cli/sample.js +154 -0
- package/dist/cli/sample.js.map +1 -0
- package/dist/cli/serve.d.ts +1 -1
- package/dist/cli/serve.d.ts.map +1 -1
- package/dist/cli/serve.js +10 -4
- package/dist/cli/serve.js.map +1 -1
- package/dist/cli/verify.d.ts.map +1 -1
- package/dist/cli/verify.js +75 -0
- package/dist/cli/verify.js.map +1 -1
- package/dist/dashboard/aggregate.d.ts +2 -0
- package/dist/dashboard/aggregate.d.ts.map +1 -1
- package/dist/dashboard/aggregate.js.map +1 -1
- package/dist/decision-log.d.ts +28 -0
- package/dist/decision-log.d.ts.map +1 -1
- package/dist/decision-log.js +34 -0
- package/dist/decision-log.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -4
- package/dist/index.js.map +1 -1
- package/dist/samples/flows.d.ts +28 -0
- package/dist/samples/flows.d.ts.map +1 -0
- package/dist/samples/flows.js +151 -0
- package/dist/samples/flows.js.map +1 -0
- package/dist/samples/verifier-html.d.ts +29 -0
- package/dist/samples/verifier-html.d.ts.map +1 -0
- package/dist/samples/verifier-html.js +188 -0
- package/dist/samples/verifier-html.js.map +1 -0
- package/package.json +7 -2
- package/src/bindings/bindings-enforce.test.ts +97 -0
- package/src/dashboard/aggregate.ts +8 -1
package/README.md
CHANGED
|
@@ -187,10 +187,70 @@ Each template sets recommended OpenRouter model assignments, capability tier, fa
|
|
|
187
187
|
|
|
188
188
|
## Provider bindings
|
|
189
189
|
|
|
190
|
-
TypeScript includes native OpenAI, Anthropic, and Bedrock bindings. Streaming usage is settled from provider usage events when available, with local token-estimator fallback when usage is missing. Settlement entries are signed into the same hash chain as enforcement decisions.
|
|
190
|
+
TypeScript includes native OpenAI, Anthropic, and Bedrock bindings. Each wraps a direct provider client in one line, with per-provider cost tables and response-shape handling. Enforcement semantics are identical across all three (and the OpenRouter path): the same policy blocks, downgrades, shadows, or allows a call before any bytes leave the process. Streaming usage is settled from provider usage events when available, with local token-estimator fallback when usage is missing. Settlement entries are signed into the same hash chain as enforcement decisions.
|
|
191
|
+
|
|
192
|
+
Wrap a direct **OpenAI** client:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import OpenAI from 'openai';
|
|
196
|
+
import { withSpendGuard } from '@agentguard-run/spend';
|
|
197
|
+
|
|
198
|
+
const openai = withSpendGuard(new OpenAI(), { policy, scope: { tenantId: 'acme' } });
|
|
199
|
+
await openai.chat.completions.create({ model: 'gpt-5', messages });
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Wrap a direct **Anthropic** client:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
206
|
+
import { withSpendGuardAnthropic } from '@agentguard-run/spend';
|
|
207
|
+
|
|
208
|
+
const anthropic = withSpendGuardAnthropic(new Anthropic(), { policy, scope: { tenantId: 'acme' } });
|
|
209
|
+
await anthropic.messages.create({ model: 'claude-sonnet-4-6', max_tokens: 1024, messages });
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Wrap a direct **Bedrock** runtime client:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { BedrockRuntimeClient } from '@aws-sdk/client-bedrock-runtime';
|
|
216
|
+
import { withSpendGuardBedrock } from '@agentguard-run/spend';
|
|
217
|
+
|
|
218
|
+
const bedrock = withSpendGuardBedrock(new BedrockRuntimeClient({ region: 'us-east-1' }), { policy, scope: { tenantId: 'acme' } });
|
|
219
|
+
await bedrock.send(new InvokeModelCommand({ modelId: 'anthropic.claude-sonnet-4-v1:0', body }));
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Only `InvokeModel` / `InvokeModelWithResponseStream` commands are gated; other Bedrock commands pass through untouched.
|
|
191
223
|
|
|
192
224
|
Python includes OpenAI, Anthropic, Bedrock, LangChain, CrewAI, and LlamaIndex integration helpers.
|
|
193
225
|
|
|
226
|
+
## Sample receipts for outreach
|
|
227
|
+
|
|
228
|
+
Generate a realistic, signed sample receipt for a named flow plus a self-contained, self-verifying HTML page — "exactly what your counterparty would get":
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
agentguard sample --flow x402-payment # or: refund | agent-purchase
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
This writes `receipt.json` and `verify.html`. The HTML re-derives the entry hash from canonical JSON (SHA-256) and verifies the Ed25519 signature in-browser via WebCrypto, with NO network calls; where a browser lacks WebCrypto Ed25519 it still checks the hash chain and links to agentguard.run/verify with the receipt prefilled. The signature is real; only the flow parameters are illustrative.
|
|
235
|
+
|
|
236
|
+
## External timestamp anchoring (opt-in)
|
|
237
|
+
|
|
238
|
+
Signed receipts prove internal order and integrity, but the signatures are produced in your own environment. Anchoring the chain head's SHA-256 to a public timestamp calendar (OpenTimestamps → Bitcoin) adds an EXTERNAL clock, so "this chain existed no later than <time>" is provable to a third party.
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
AGENTGUARD_ANCHOR=opentimestamps agentguard anchor --tenant my-tenant
|
|
242
|
+
agentguard verify --trace ~/.agentguard/my-tenant/decisions.ndjson
|
|
243
|
+
# → chain head submitted to external timestamp calendar at <time>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Anchoring is OFF by default; nothing changes for existing users. **Zero data plane is preserved: only the 32-byte SHA-256 of the chain head leaves the machine.** A hash reveals nothing about prompts, completions, keys, amounts, or model identity. The proof is stored alongside the ledger at `~/.agentguard/<tenant>/anchor.json` (+ `anchor.ots`).
|
|
247
|
+
|
|
248
|
+
Note: full Bitcoin-attestation verification requires the `opentimestamps` npm package, which is not yet bundled. Without it, submitted proofs are reported as `pending` (calendar commitment received; external attestation not yet confirmed) — never as a fabricated attested time.
|
|
249
|
+
|
|
250
|
+
## Completeness: sequence-gap detection
|
|
251
|
+
|
|
252
|
+
Each signed entry carries a monotonic per-tenant sequence number. `agentguard verify` and the `agentguard serve` integrity badge flag missing sequence numbers as `possible omitted actions: sequence gap at N` — evidence that an action may have been taken around the SDK and not logged, or that a logged entry was deleted. This is detection and reporting only; it never changes spend-cap enforcement.
|
|
253
|
+
|
|
194
254
|
## No proxy
|
|
195
255
|
|
|
196
256
|
AgentGuard Spend is a library, not a gateway. It does not proxy traffic, store prompts, hold provider keys, or host policy state. The signed log lives in your storage.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External timestamp anchoring: public surface + factory.
|
|
3
|
+
*
|
|
4
|
+
* Opt-in only. `createAnchorFromEnv()` returns null unless AGENTGUARD_ANCHOR is
|
|
5
|
+
* set (today: "opentimestamps"). Nothing in the default SDK path anchors, so
|
|
6
|
+
* existing users see zero behavior change.
|
|
7
|
+
*
|
|
8
|
+
* Patent notice: Protected by U.S. patent-pending technology
|
|
9
|
+
* (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626;
|
|
10
|
+
* 64/071,781; 64/071,789).
|
|
11
|
+
*/
|
|
12
|
+
import type { SignedDecisionLogEntry } from '../types';
|
|
13
|
+
import type { TimestampAnchor } from './types';
|
|
14
|
+
import { type OpenTimestampsAnchorOptions } from './opentimestamps';
|
|
15
|
+
export type { AnchorProof, AnchorStatus, AnchorVerification, TimestampAnchor } from './types';
|
|
16
|
+
export { OpenTimestampsAnchor, DEFAULT_OTS_CALENDARS, defaultOtsLib, type OtsSubmit, type OtsLib, type OtsBitcoinAttestation, type OpenTimestampsAnchorOptions, } from './opentimestamps';
|
|
17
|
+
export { writeAnchorProof, replaceAnchorIndex, readAnchorIndex, readAnchorIndexAt, findAnchorForHead, anchorIndexPath, anchorProofPath, anchorDir, } from './store';
|
|
18
|
+
/** Build a named anchor. Extend the switch as adapters are added. */
|
|
19
|
+
export declare function createAnchor(type: string, options?: OpenTimestampsAnchorOptions): TimestampAnchor | null;
|
|
20
|
+
/** Resolve the configured anchor from AGENTGUARD_ANCHOR, or null when unset. */
|
|
21
|
+
export declare function createAnchorFromEnv(options?: OpenTimestampsAnchorOptions): TimestampAnchor | null;
|
|
22
|
+
/**
|
|
23
|
+
* The digest an anchor should submit for a ledger: the SHA-256 of the current
|
|
24
|
+
* chain HEAD entry hash. Anchoring the head transitively commits to every prior
|
|
25
|
+
* entry (each entry binds its predecessor's hash), so a single 32-byte digest
|
|
26
|
+
* externally timestamps the whole chain. Returns null for an empty chain.
|
|
27
|
+
*/
|
|
28
|
+
export declare function chainHeadDigestHex(entries: ReadonlyArray<Pick<SignedDecisionLogEntry, 'sequence' | 'entryHash'>>): string | null;
|
|
29
|
+
/**
|
|
30
|
+
* A Merkle root over the ndjson chain's entry hashes — an alternative anchor
|
|
31
|
+
* digest when you want the anchored value to be independent of chain order
|
|
32
|
+
* bugs. Uses SHA-256 with duplicate-last-node padding.
|
|
33
|
+
*/
|
|
34
|
+
export declare function merkleRootHex(entries: ReadonlyArray<Pick<SignedDecisionLogEntry, 'entryHash'>>): string | null;
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/anchor/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAwB,KAAK,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAE1F,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC9F,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,aAAa,EACb,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,qBAAqB,EAC1B,KAAK,2BAA2B,GACjC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,SAAS,GACV,MAAM,SAAS,CAAC;AAEjB,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,2BAAgC,GAAG,eAAe,GAAG,IAAI,CAG5G;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,2BAAgC,GAAG,eAAe,GAAG,IAAI,CAIrG;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,sBAAsB,EAAE,UAAU,GAAG,WAAW,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAKhI;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,sBAAsB,EAAE,WAAW,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAa9G"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* External timestamp anchoring: public surface + factory.
|
|
4
|
+
*
|
|
5
|
+
* Opt-in only. `createAnchorFromEnv()` returns null unless AGENTGUARD_ANCHOR is
|
|
6
|
+
* set (today: "opentimestamps"). Nothing in the default SDK path anchors, so
|
|
7
|
+
* existing users see zero behavior change.
|
|
8
|
+
*
|
|
9
|
+
* Patent notice: Protected by U.S. patent-pending technology
|
|
10
|
+
* (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626;
|
|
11
|
+
* 64/071,781; 64/071,789).
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.anchorDir = exports.anchorProofPath = exports.anchorIndexPath = exports.findAnchorForHead = exports.readAnchorIndexAt = exports.readAnchorIndex = exports.replaceAnchorIndex = exports.writeAnchorProof = exports.defaultOtsLib = exports.DEFAULT_OTS_CALENDARS = exports.OpenTimestampsAnchor = void 0;
|
|
15
|
+
exports.createAnchor = createAnchor;
|
|
16
|
+
exports.createAnchorFromEnv = createAnchorFromEnv;
|
|
17
|
+
exports.chainHeadDigestHex = chainHeadDigestHex;
|
|
18
|
+
exports.merkleRootHex = merkleRootHex;
|
|
19
|
+
const crypto_1 = require("crypto");
|
|
20
|
+
const opentimestamps_1 = require("./opentimestamps");
|
|
21
|
+
var opentimestamps_2 = require("./opentimestamps");
|
|
22
|
+
Object.defineProperty(exports, "OpenTimestampsAnchor", { enumerable: true, get: function () { return opentimestamps_2.OpenTimestampsAnchor; } });
|
|
23
|
+
Object.defineProperty(exports, "DEFAULT_OTS_CALENDARS", { enumerable: true, get: function () { return opentimestamps_2.DEFAULT_OTS_CALENDARS; } });
|
|
24
|
+
Object.defineProperty(exports, "defaultOtsLib", { enumerable: true, get: function () { return opentimestamps_2.defaultOtsLib; } });
|
|
25
|
+
var store_1 = require("./store");
|
|
26
|
+
Object.defineProperty(exports, "writeAnchorProof", { enumerable: true, get: function () { return store_1.writeAnchorProof; } });
|
|
27
|
+
Object.defineProperty(exports, "replaceAnchorIndex", { enumerable: true, get: function () { return store_1.replaceAnchorIndex; } });
|
|
28
|
+
Object.defineProperty(exports, "readAnchorIndex", { enumerable: true, get: function () { return store_1.readAnchorIndex; } });
|
|
29
|
+
Object.defineProperty(exports, "readAnchorIndexAt", { enumerable: true, get: function () { return store_1.readAnchorIndexAt; } });
|
|
30
|
+
Object.defineProperty(exports, "findAnchorForHead", { enumerable: true, get: function () { return store_1.findAnchorForHead; } });
|
|
31
|
+
Object.defineProperty(exports, "anchorIndexPath", { enumerable: true, get: function () { return store_1.anchorIndexPath; } });
|
|
32
|
+
Object.defineProperty(exports, "anchorProofPath", { enumerable: true, get: function () { return store_1.anchorProofPath; } });
|
|
33
|
+
Object.defineProperty(exports, "anchorDir", { enumerable: true, get: function () { return store_1.anchorDir; } });
|
|
34
|
+
/** Build a named anchor. Extend the switch as adapters are added. */
|
|
35
|
+
function createAnchor(type, options = {}) {
|
|
36
|
+
if (type === 'opentimestamps')
|
|
37
|
+
return new opentimestamps_1.OpenTimestampsAnchor(options);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
/** Resolve the configured anchor from AGENTGUARD_ANCHOR, or null when unset. */
|
|
41
|
+
function createAnchorFromEnv(options = {}) {
|
|
42
|
+
const type = (process.env.AGENTGUARD_ANCHOR || '').trim().toLowerCase();
|
|
43
|
+
if (!type || type === 'off' || type === 'none' || type === '0')
|
|
44
|
+
return null;
|
|
45
|
+
return createAnchor(type, options);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The digest an anchor should submit for a ledger: the SHA-256 of the current
|
|
49
|
+
* chain HEAD entry hash. Anchoring the head transitively commits to every prior
|
|
50
|
+
* entry (each entry binds its predecessor's hash), so a single 32-byte digest
|
|
51
|
+
* externally timestamps the whole chain. Returns null for an empty chain.
|
|
52
|
+
*/
|
|
53
|
+
function chainHeadDigestHex(entries) {
|
|
54
|
+
if (entries.length === 0)
|
|
55
|
+
return null;
|
|
56
|
+
let head = entries[0];
|
|
57
|
+
for (const e of entries)
|
|
58
|
+
if (e.sequence >= head.sequence)
|
|
59
|
+
head = e;
|
|
60
|
+
return (0, crypto_1.createHash)('sha256').update(head.entryHash, 'utf8').digest('hex');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A Merkle root over the ndjson chain's entry hashes — an alternative anchor
|
|
64
|
+
* digest when you want the anchored value to be independent of chain order
|
|
65
|
+
* bugs. Uses SHA-256 with duplicate-last-node padding.
|
|
66
|
+
*/
|
|
67
|
+
function merkleRootHex(entries) {
|
|
68
|
+
if (entries.length === 0)
|
|
69
|
+
return null;
|
|
70
|
+
let level = entries.map((e) => new Uint8Array((0, crypto_1.createHash)('sha256').update(e.entryHash, 'utf8').digest()));
|
|
71
|
+
while (level.length > 1) {
|
|
72
|
+
const next = [];
|
|
73
|
+
for (let i = 0; i < level.length; i += 2) {
|
|
74
|
+
const left = level[i];
|
|
75
|
+
const right = level[i + 1] ?? left;
|
|
76
|
+
next.push(new Uint8Array((0, crypto_1.createHash)('sha256').update(Buffer.concat([left, right])).digest()));
|
|
77
|
+
}
|
|
78
|
+
level = next;
|
|
79
|
+
}
|
|
80
|
+
return Buffer.from(level[0]).toString('hex');
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/anchor/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;;AA6BH,oCAGC;AAGD,kDAIC;AAQD,gDAKC;AAOD,sCAaC;AAtED,mCAAoC;AAGpC,qDAA0F;AAG1F,mDAQ0B;AAPxB,sHAAA,oBAAoB,OAAA;AACpB,uHAAA,qBAAqB,OAAA;AACrB,+GAAA,aAAa,OAAA;AAMf,iCASiB;AARf,yGAAA,gBAAgB,OAAA;AAChB,2GAAA,kBAAkB,OAAA;AAClB,wGAAA,eAAe,OAAA;AACf,0GAAA,iBAAiB,OAAA;AACjB,0GAAA,iBAAiB,OAAA;AACjB,wGAAA,eAAe,OAAA;AACf,wGAAA,eAAe,OAAA;AACf,kGAAA,SAAS,OAAA;AAGX,qEAAqE;AACrE,SAAgB,YAAY,CAAC,IAAY,EAAE,UAAuC,EAAE;IAClF,IAAI,IAAI,KAAK,gBAAgB;QAAE,OAAO,IAAI,qCAAoB,CAAC,OAAO,CAAC,CAAC;IACxE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gFAAgF;AAChF,SAAgB,mBAAmB,CAAC,UAAuC,EAAE;IAC3E,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACxE,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAC5E,OAAO,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;GAKG;AACH,SAAgB,kBAAkB,CAAC,OAA8E;IAC/G,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,IAAI,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;IACvB,KAAK,MAAM,CAAC,IAAI,OAAO;QAAE,IAAI,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ;YAAE,IAAI,GAAG,CAAC,CAAC;IACnE,OAAO,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC;AAED;;;;GAIG;AACH,SAAgB,aAAa,CAAC,OAAiE;IAC7F,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,KAAK,GAAiB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACxH,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,GAAiB,EAAE,CAAC;QAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;YACvB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;YACnC,IAAI,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAChG,CAAC;QACD,KAAK,GAAG,IAAI,CAAC;IACf,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTimestamps anchor adapter.
|
|
3
|
+
*
|
|
4
|
+
* Submits ONLY the 32-byte SHA-256 of the chain head to one or more public
|
|
5
|
+
* OpenTimestamps calendar servers, stores the returned `.ots` proof, and can
|
|
6
|
+
* later upgrade that proof to a Bitcoin block attestation. Nothing else leaves
|
|
7
|
+
* the process (zero data plane): a hash is submitted, an `.ots` proof comes
|
|
8
|
+
* back. No decision content, policy, scope, amount, model identity, or key
|
|
9
|
+
* material is ever transmitted.
|
|
10
|
+
*
|
|
11
|
+
* This adapter uses the real `opentimestamps` npm library through an injectable
|
|
12
|
+
* boundary (`OtsLib`), so:
|
|
13
|
+
* - `anchor()` builds a `DetachedTimestampFile` from the raw digest, stamps it
|
|
14
|
+
* against public calendars, and stores the serialized `.ots` bytes,
|
|
15
|
+
* - `upgrade()` fetches the Bitcoin attestation when the calendars have it and
|
|
16
|
+
* persists the upgraded `.ots` (idempotent),
|
|
17
|
+
* - `verify()` reports `attested: true` with a real `when` ONLY when the `.ots`
|
|
18
|
+
* proof actually carries a Bitcoin block-header attestation. A pending proof
|
|
19
|
+
* stays pending. An attested time is NEVER fabricated.
|
|
20
|
+
*
|
|
21
|
+
* Patent notice: Protected by U.S. patent-pending technology
|
|
22
|
+
* (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626;
|
|
23
|
+
* 64/071,781; 64/071,789).
|
|
24
|
+
*/
|
|
25
|
+
import type { AnchorProof, AnchorVerification, TimestampAnchor } from './types';
|
|
26
|
+
/** Public OpenTimestamps calendar servers (aggregators). */
|
|
27
|
+
export declare const DEFAULT_OTS_CALENDARS: string[];
|
|
28
|
+
/**
|
|
29
|
+
* Result of inspecting an `.ots` proof for a Bitcoin attestation.
|
|
30
|
+
* `bitcoinAttested` is true iff the timestamp carries a Bitcoin block-header
|
|
31
|
+
* attestation; `attestedTimeSec` is the confirmed block time (unix seconds),
|
|
32
|
+
* only present when the attestation resolves to a real time.
|
|
33
|
+
*/
|
|
34
|
+
export interface OtsBitcoinAttestation {
|
|
35
|
+
bitcoinAttested: boolean;
|
|
36
|
+
attestedTimeSec?: number;
|
|
37
|
+
height?: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The `opentimestamps` library boundary. Injectable so tests are deterministic
|
|
41
|
+
* and never touch the network. Every method receives/returns raw bytes and the
|
|
42
|
+
* 32-byte digest only — zero data plane.
|
|
43
|
+
*/
|
|
44
|
+
export interface OtsLib {
|
|
45
|
+
/** Stamp a 32-byte SHA-256 digest against calendars; return the `.ots` bytes. */
|
|
46
|
+
stamp(sha256Digest: Uint8Array, calendars: string[]): Promise<Uint8Array>;
|
|
47
|
+
/** Upgrade stored `.ots` bytes (fetch Bitcoin attestation if available). Idempotent; returns the possibly-upgraded `.ots` bytes. */
|
|
48
|
+
upgrade(otsBytes: Uint8Array): Promise<Uint8Array>;
|
|
49
|
+
/** Inspect `.ots` bytes for the given digest. MUST throw if the proof's embedded file hash does not equal `sha256Digest`. */
|
|
50
|
+
verify(sha256Digest: Uint8Array, otsBytes: Uint8Array): Promise<OtsBitcoinAttestation>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* @deprecated Legacy calendar-POST transport hook. Superseded by the `OtsLib`
|
|
54
|
+
* boundary now that the real `opentimestamps` library is a dependency. Retained
|
|
55
|
+
* only so existing type imports keep compiling.
|
|
56
|
+
*/
|
|
57
|
+
export type OtsSubmit = (calendarUrl: string, digest: Uint8Array) => Promise<Uint8Array>;
|
|
58
|
+
export interface OpenTimestampsAnchorOptions {
|
|
59
|
+
calendars?: string[];
|
|
60
|
+
/** Override the OpenTimestamps library boundary (defaults to the real npm lib). */
|
|
61
|
+
lib?: OtsLib;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Default `OtsLib` backed by the real `opentimestamps` npm package. Lazily
|
|
65
|
+
* required so the SDK never pulls the library (or node:https) at import time in
|
|
66
|
+
* bundlers — only when an anchor is actually exercised.
|
|
67
|
+
*/
|
|
68
|
+
export declare function defaultOtsLib(): OtsLib;
|
|
69
|
+
export declare class OpenTimestampsAnchor implements TimestampAnchor {
|
|
70
|
+
readonly type = "opentimestamps";
|
|
71
|
+
private readonly calendars;
|
|
72
|
+
private lazyLib;
|
|
73
|
+
constructor(options?: OpenTimestampsAnchorOptions);
|
|
74
|
+
private lib;
|
|
75
|
+
private digestOf;
|
|
76
|
+
anchor(headHashHex: string): Promise<AnchorProof>;
|
|
77
|
+
/**
|
|
78
|
+
* Fetch the Bitcoin attestation for a stored proof, if the calendars now have
|
|
79
|
+
* it, and return an updated proof. Idempotent: safe to call repeatedly. If the
|
|
80
|
+
* attestation is confirmable, `attestedAt` is set to the real Bitcoin block
|
|
81
|
+
* time and status becomes `complete`; otherwise the proof stays `pending`.
|
|
82
|
+
*/
|
|
83
|
+
upgrade(proof: AnchorProof): Promise<AnchorProof>;
|
|
84
|
+
verify(headHashHex: string, proof: AnchorProof): Promise<AnchorVerification>;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=opentimestamps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opentimestamps.d.ts","sourceRoot":"","sources":["../../src/anchor/opentimestamps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEhF,4DAA4D;AAC5D,eAAO,MAAM,qBAAqB,UAGjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,MAAM;IACrB,iFAAiF;IACjF,KAAK,CAAC,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1E,oIAAoI;IACpI,OAAO,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACnD,6HAA6H;IAC7H,MAAM,CAAC,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACxF;AAED;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AAEzF,MAAM,WAAW,2BAA2B;IAC1C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,mFAAmF;IACnF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAkBD;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAkEtC;AAED,qBAAa,oBAAqB,YAAW,eAAe;IAC1D,QAAQ,CAAC,IAAI,oBAAoB;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IACrC,OAAO,CAAC,OAAO,CAAqB;gBAExB,OAAO,GAAE,2BAAgC;IAKrD,OAAO,CAAC,GAAG;IAKX,OAAO,CAAC,QAAQ;IAQV,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAmBvD;;;;;OAKG;IACG,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAoBjD,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,kBAAkB,CAAC;CA2DnF"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenTimestamps anchor adapter.
|
|
4
|
+
*
|
|
5
|
+
* Submits ONLY the 32-byte SHA-256 of the chain head to one or more public
|
|
6
|
+
* OpenTimestamps calendar servers, stores the returned `.ots` proof, and can
|
|
7
|
+
* later upgrade that proof to a Bitcoin block attestation. Nothing else leaves
|
|
8
|
+
* the process (zero data plane): a hash is submitted, an `.ots` proof comes
|
|
9
|
+
* back. No decision content, policy, scope, amount, model identity, or key
|
|
10
|
+
* material is ever transmitted.
|
|
11
|
+
*
|
|
12
|
+
* This adapter uses the real `opentimestamps` npm library through an injectable
|
|
13
|
+
* boundary (`OtsLib`), so:
|
|
14
|
+
* - `anchor()` builds a `DetachedTimestampFile` from the raw digest, stamps it
|
|
15
|
+
* against public calendars, and stores the serialized `.ots` bytes,
|
|
16
|
+
* - `upgrade()` fetches the Bitcoin attestation when the calendars have it and
|
|
17
|
+
* persists the upgraded `.ots` (idempotent),
|
|
18
|
+
* - `verify()` reports `attested: true` with a real `when` ONLY when the `.ots`
|
|
19
|
+
* proof actually carries a Bitcoin block-header attestation. A pending proof
|
|
20
|
+
* stays pending. An attested time is NEVER fabricated.
|
|
21
|
+
*
|
|
22
|
+
* Patent notice: Protected by U.S. patent-pending technology
|
|
23
|
+
* (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626;
|
|
24
|
+
* 64/071,781; 64/071,789).
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.OpenTimestampsAnchor = exports.DEFAULT_OTS_CALENDARS = void 0;
|
|
28
|
+
exports.defaultOtsLib = defaultOtsLib;
|
|
29
|
+
/** Public OpenTimestamps calendar servers (aggregators). */
|
|
30
|
+
exports.DEFAULT_OTS_CALENDARS = [
|
|
31
|
+
'https://alice.btc.calendar.opentimestamps.org',
|
|
32
|
+
'https://bob.btc.calendar.opentimestamps.org',
|
|
33
|
+
];
|
|
34
|
+
function hexToBytes(hex) {
|
|
35
|
+
const clean = hex.trim().toLowerCase();
|
|
36
|
+
if (!/^[0-9a-f]*$/.test(clean) || clean.length % 2 !== 0) {
|
|
37
|
+
throw new Error('OpenTimestampsAnchor: headHashHex must be lowercase hex of even length');
|
|
38
|
+
}
|
|
39
|
+
const out = new Uint8Array(clean.length / 2);
|
|
40
|
+
for (let i = 0; i < out.length; i++)
|
|
41
|
+
out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
function bytesEqual(a, b) {
|
|
45
|
+
if (a.length !== b.length)
|
|
46
|
+
return false;
|
|
47
|
+
for (let i = 0; i < a.length; i++)
|
|
48
|
+
if (a[i] !== b[i])
|
|
49
|
+
return false;
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Default `OtsLib` backed by the real `opentimestamps` npm package. Lazily
|
|
54
|
+
* required so the SDK never pulls the library (or node:https) at import time in
|
|
55
|
+
* bundlers — only when an anchor is actually exercised.
|
|
56
|
+
*/
|
|
57
|
+
function defaultOtsLib() {
|
|
58
|
+
let OTS;
|
|
59
|
+
try {
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
61
|
+
OTS = require('opentimestamps');
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw new Error("Bitcoin timestamp anchoring requires the optional 'opentimestamps' package. " +
|
|
65
|
+
'Install it to enable this feature: npm i opentimestamps');
|
|
66
|
+
}
|
|
67
|
+
const { DetachedTimestampFile, Ops, Notary } = OTS;
|
|
68
|
+
const detachedFromHash = (digest) => DetachedTimestampFile.fromHash(new Ops.OpSHA256(), Array.from(digest));
|
|
69
|
+
const detachedFromOts = (otsBytes) => DetachedTimestampFile.deserialize(Array.from(otsBytes));
|
|
70
|
+
const toBytes = (detached) => Uint8Array.from(detached.serializeToBytes());
|
|
71
|
+
const hasBitcoinAttestation = (detached) => {
|
|
72
|
+
let found = false;
|
|
73
|
+
detached.timestamp.allAttestations().forEach((attestation) => {
|
|
74
|
+
if (attestation instanceof Notary.BitcoinBlockHeaderAttestation)
|
|
75
|
+
found = true;
|
|
76
|
+
});
|
|
77
|
+
return found;
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
async stamp(sha256Digest, calendars) {
|
|
81
|
+
const detached = detachedFromHash(sha256Digest);
|
|
82
|
+
await OTS.stamp(detached, { calendars, m: calendars.length >= 2 ? 2 : 1 });
|
|
83
|
+
return toBytes(detached);
|
|
84
|
+
},
|
|
85
|
+
async upgrade(otsBytes) {
|
|
86
|
+
const detached = detachedFromOts(otsBytes);
|
|
87
|
+
await OTS.upgrade(detached);
|
|
88
|
+
return toBytes(detached);
|
|
89
|
+
},
|
|
90
|
+
async verify(sha256Digest, otsBytes) {
|
|
91
|
+
const detached = detachedFromOts(otsBytes);
|
|
92
|
+
// Bind the proof to the supplied digest: the .ots embeds the file hash it
|
|
93
|
+
// was built from. A mismatch means this proof is not for this head.
|
|
94
|
+
if (!bytesEqual(Uint8Array.from(detached.fileDigest()), sha256Digest)) {
|
|
95
|
+
throw new Error('OTS proof file hash does not match the supplied digest');
|
|
96
|
+
}
|
|
97
|
+
if (!hasBitcoinAttestation(detached)) {
|
|
98
|
+
return { bitcoinAttested: false };
|
|
99
|
+
}
|
|
100
|
+
// A Bitcoin attestation is present; resolve its block time. verifyTimestamp
|
|
101
|
+
// checks completed attestations only (no re-stamp) and returns
|
|
102
|
+
// { bitcoin: { timestamp, height } } when confirmable.
|
|
103
|
+
const result = await OTS.verifyTimestamp(detached.timestamp, {});
|
|
104
|
+
const btc = result && (result.bitcoin || result.Bitcoin);
|
|
105
|
+
if (btc && typeof btc.timestamp === 'number') {
|
|
106
|
+
return { bitcoinAttested: true, attestedTimeSec: btc.timestamp, height: btc.height };
|
|
107
|
+
}
|
|
108
|
+
// Attestation present but time not resolvable right now (e.g. explorer
|
|
109
|
+
// unreachable). Report attested-but-timeless; callers keep it pending
|
|
110
|
+
// rather than inventing a time.
|
|
111
|
+
return { bitcoinAttested: true };
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
class OpenTimestampsAnchor {
|
|
116
|
+
type = 'opentimestamps';
|
|
117
|
+
calendars;
|
|
118
|
+
lazyLib;
|
|
119
|
+
constructor(options = {}) {
|
|
120
|
+
this.calendars = options.calendars ?? exports.DEFAULT_OTS_CALENDARS;
|
|
121
|
+
this.lazyLib = options.lib;
|
|
122
|
+
}
|
|
123
|
+
lib() {
|
|
124
|
+
if (!this.lazyLib)
|
|
125
|
+
this.lazyLib = defaultOtsLib();
|
|
126
|
+
return this.lazyLib;
|
|
127
|
+
}
|
|
128
|
+
digestOf(headHashHex) {
|
|
129
|
+
const digest = hexToBytes(headHashHex);
|
|
130
|
+
if (digest.length !== 32) {
|
|
131
|
+
throw new Error('OpenTimestampsAnchor: chain head digest must be SHA-256 (32 bytes)');
|
|
132
|
+
}
|
|
133
|
+
return digest;
|
|
134
|
+
}
|
|
135
|
+
async anchor(headHashHex) {
|
|
136
|
+
const digest = this.digestOf(headHashHex);
|
|
137
|
+
const otsBytes = await this.lib().stamp(digest, this.calendars);
|
|
138
|
+
return {
|
|
139
|
+
version: 1,
|
|
140
|
+
anchorType: this.type,
|
|
141
|
+
headHashHex: headHashHex.trim().toLowerCase(),
|
|
142
|
+
submittedAt: new Date().toISOString(),
|
|
143
|
+
proof: Buffer.from(otsBytes).toString('base64'),
|
|
144
|
+
status: 'pending',
|
|
145
|
+
calendars: this.calendars.slice(),
|
|
146
|
+
attestedAt: null,
|
|
147
|
+
note: 'OpenTimestamps .ots proof stored. Bitcoin attestation not yet confirmed; ' +
|
|
148
|
+
"run 'agentguard anchor --upgrade' once the calendars have committed the " +
|
|
149
|
+
'digest to a Bitcoin block. Only a 32-byte SHA-256 hash was transmitted.',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Fetch the Bitcoin attestation for a stored proof, if the calendars now have
|
|
154
|
+
* it, and return an updated proof. Idempotent: safe to call repeatedly. If the
|
|
155
|
+
* attestation is confirmable, `attestedAt` is set to the real Bitcoin block
|
|
156
|
+
* time and status becomes `complete`; otherwise the proof stays `pending`.
|
|
157
|
+
*/
|
|
158
|
+
async upgrade(proof) {
|
|
159
|
+
if (proof.anchorType !== this.type)
|
|
160
|
+
return proof;
|
|
161
|
+
const digest = this.digestOf(proof.headHashHex);
|
|
162
|
+
const current = Uint8Array.from(Buffer.from(proof.proof, 'base64'));
|
|
163
|
+
const upgraded = await this.lib().upgrade(current);
|
|
164
|
+
const next = { ...proof, proof: Buffer.from(upgraded).toString('base64') };
|
|
165
|
+
const info = await this.lib().verify(digest, upgraded);
|
|
166
|
+
if (info.bitcoinAttested && typeof info.attestedTimeSec === 'number') {
|
|
167
|
+
next.attestedAt = new Date(info.attestedTimeSec * 1000).toISOString();
|
|
168
|
+
next.status = 'complete';
|
|
169
|
+
next.note =
|
|
170
|
+
'OpenTimestamps .ots proof upgraded: chain head committed to Bitcoin' +
|
|
171
|
+
(typeof info.height === 'number' ? ` block ${info.height}` : '') + '.';
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
next.attestedAt = proof.attestedAt ?? null;
|
|
175
|
+
next.status = 'pending';
|
|
176
|
+
}
|
|
177
|
+
return next;
|
|
178
|
+
}
|
|
179
|
+
async verify(headHashHex, proof) {
|
|
180
|
+
const head = headHashHex.trim().toLowerCase();
|
|
181
|
+
if (proof.anchorType !== this.type) {
|
|
182
|
+
return { ok: false, status: 'invalid', when: null, attested: false, detail: `not an ${this.type} proof` };
|
|
183
|
+
}
|
|
184
|
+
if (proof.headHashHex.trim().toLowerCase() !== head) {
|
|
185
|
+
return {
|
|
186
|
+
ok: false,
|
|
187
|
+
status: 'invalid',
|
|
188
|
+
when: null,
|
|
189
|
+
attested: false,
|
|
190
|
+
detail: 'proof does not bind the supplied chain head hash',
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
let digest;
|
|
194
|
+
try {
|
|
195
|
+
digest = this.digestOf(head);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
return { ok: false, status: 'invalid', when: null, attested: false, detail: err instanceof Error ? err.message : String(err) };
|
|
199
|
+
}
|
|
200
|
+
const hasProofBytes = typeof proof.proof === 'string' && proof.proof.length > 0;
|
|
201
|
+
if (!hasProofBytes) {
|
|
202
|
+
return { ok: false, status: 'invalid', when: null, attested: false, detail: 'proof carries no .ots commitment' };
|
|
203
|
+
}
|
|
204
|
+
let info;
|
|
205
|
+
try {
|
|
206
|
+
const otsBytes = Uint8Array.from(Buffer.from(proof.proof, 'base64'));
|
|
207
|
+
info = await this.lib().verify(digest, otsBytes);
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
status: 'invalid',
|
|
213
|
+
when: null,
|
|
214
|
+
attested: false,
|
|
215
|
+
detail: `OTS proof did not verify against this head: ${err instanceof Error ? err.message : String(err)}`,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
// attested:true is gated SOLELY on the .ots carrying a Bitcoin block
|
|
219
|
+
// attestation that resolves to a real block time. Never fabricated.
|
|
220
|
+
if (info.bitcoinAttested && typeof info.attestedTimeSec === 'number') {
|
|
221
|
+
return { ok: true, status: 'complete', when: new Date(info.attestedTimeSec * 1000).toISOString(), attested: true };
|
|
222
|
+
}
|
|
223
|
+
// A previously-recorded, real upgrade may have persisted attestedAt; trust it
|
|
224
|
+
// (we only ever set it from a genuine Bitcoin block time).
|
|
225
|
+
if (proof.attestedAt) {
|
|
226
|
+
return { ok: true, status: 'complete', when: proof.attestedAt, attested: true };
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
ok: true,
|
|
230
|
+
status: 'pending',
|
|
231
|
+
when: proof.submittedAt ?? null,
|
|
232
|
+
attested: false,
|
|
233
|
+
detail: 'OpenTimestamps .ots commitment present for this head hash; Bitcoin attestation ' +
|
|
234
|
+
'not yet confirmed (run `anchor --upgrade`). `when` is the submission time, not an attested time.',
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
exports.OpenTimestampsAnchor = OpenTimestampsAnchor;
|
|
239
|
+
//# sourceMappingURL=opentimestamps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opentimestamps.js","sourceRoot":"","sources":["../../src/anchor/opentimestamps.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anchor proof persistence, stored ALONGSIDE the ndjson ledger so a verifier
|
|
3
|
+
* can find both together:
|
|
4
|
+
* ~/.agentguard/<tenant>/anchor.ots latest raw calendar proof bytes (binary)
|
|
5
|
+
* ~/.agentguard/<tenant>/anchor.json small append-only index of AnchorProof
|
|
6
|
+
*
|
|
7
|
+
* The index is what `agentguard verify` reads to report external timestamping.
|
|
8
|
+
*
|
|
9
|
+
* Patent notice: Protected by U.S. patent-pending technology
|
|
10
|
+
* (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626;
|
|
11
|
+
* 64/071,781; 64/071,789).
|
|
12
|
+
*/
|
|
13
|
+
import type { AnchorProof } from './types';
|
|
14
|
+
export declare function defaultHome(): string;
|
|
15
|
+
export declare function anchorDir(tenant: string, home?: string): string;
|
|
16
|
+
export declare function anchorIndexPath(tenant: string, home?: string): string;
|
|
17
|
+
export declare function anchorProofPath(tenant: string, home?: string): string;
|
|
18
|
+
/** Append a proof to the tenant index and write its raw bytes to anchor.ots. */
|
|
19
|
+
export declare function writeAnchorProof(tenant: string, proof: AnchorProof, home?: string): void;
|
|
20
|
+
/** Overwrite the tenant index with `proofs` and refresh the raw .ots sidecar
|
|
21
|
+
* from the most recent proof. Used by `anchor --upgrade` to update proofs in
|
|
22
|
+
* place (idempotent) rather than appending duplicates. */
|
|
23
|
+
export declare function replaceAnchorIndex(tenant: string, proofs: AnchorProof[], home?: string): void;
|
|
24
|
+
export declare function readAnchorIndex(tenant: string, home?: string): AnchorProof[];
|
|
25
|
+
/** Read an anchor index directly from a path (used by verify beside a trace). */
|
|
26
|
+
export declare function readAnchorIndexAt(indexPath: string): AnchorProof[];
|
|
27
|
+
/** The most recent proof recorded for a given head hash, if any. */
|
|
28
|
+
export declare function findAnchorForHead(proofs: AnchorProof[], headHashHex: string): AnchorProof | null;
|
|
29
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/anchor/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,MAAsB,GAAG,MAAM,CAG9E;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAWxF;AAED;;2DAE2D;AAC3D,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAY7F;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,EAAE,CAE5E;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE,CASlE;AAED,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAMhG"}
|