@aleutian/verification 0.0.0 → 0.7.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.
@@ -0,0 +1,3841 @@
1
+ #!/usr/bin/env node
2
+ import { createHash } from 'crypto';
3
+ import { constants, createReadStream } from 'fs';
4
+ import { stat, readFile, open } from 'fs/promises';
5
+ import { sha512 } from '@noble/hashes/sha2.js';
6
+ import { utf8ToBytes, bytesToHex } from '@noble/hashes/utils.js';
7
+ import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
8
+ import '@noble/post-quantum/ml-kem.js';
9
+ import '@noble/hashes/sha3.js';
10
+ import 'tweetnacl';
11
+
12
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
13
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
14
+ }) : x)(function(x) {
15
+ if (typeof require !== "undefined") return require.apply(this, arguments);
16
+ throw Error('Dynamic require of "' + x + '" is not supported');
17
+ });
18
+
19
+ // src/errors.ts
20
+ var AnchorVerificationError = class _AnchorVerificationError extends Error {
21
+ name = "AnchorVerificationError";
22
+ constructor(message) {
23
+ super(message);
24
+ if (Error.captureStackTrace) {
25
+ Error.captureStackTrace(this, _AnchorVerificationError);
26
+ }
27
+ }
28
+ };
29
+ var ChainVerificationError = class _ChainVerificationError extends Error {
30
+ name = "ChainVerificationError";
31
+ constructor(message) {
32
+ super(message);
33
+ if (Error.captureStackTrace) {
34
+ Error.captureStackTrace(this, _ChainVerificationError);
35
+ }
36
+ }
37
+ };
38
+ var EntriesFormatError = class _EntriesFormatError extends Error {
39
+ name = "EntriesFormatError";
40
+ constructor(message) {
41
+ super(message);
42
+ Object.setPrototypeOf(this, _EntriesFormatError.prototype);
43
+ if (Error.captureStackTrace) {
44
+ Error.captureStackTrace(this, _EntriesFormatError);
45
+ }
46
+ }
47
+ };
48
+
49
+ // src/hash.ts
50
+ function base64ToUint8Array(base64) {
51
+ if (typeof Buffer !== "undefined") {
52
+ return new Uint8Array(Buffer.from(base64, "base64"));
53
+ } else {
54
+ const binary = atob(base64);
55
+ const bytes = new Uint8Array(binary.length);
56
+ for (let i = 0; i < binary.length; i++) {
57
+ bytes[i] = binary.charCodeAt(i);
58
+ }
59
+ return bytes;
60
+ }
61
+ }
62
+ async function sha512Hex(data) {
63
+ const encoder = new TextEncoder();
64
+ const bytes = encoder.encode(data);
65
+ const hashBuffer = await crypto.subtle.digest("SHA-512", bytes);
66
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
67
+ }
68
+ async function sha512HexRaw(data) {
69
+ const hashBuffer = await crypto.subtle.digest("SHA-512", data);
70
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
71
+ }
72
+ function hexToUint8Array(hex) {
73
+ const bytes = new Uint8Array(hex.length / 2);
74
+ for (let i = 0; i < bytes.length; i++) {
75
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
76
+ }
77
+ return bytes;
78
+ }
79
+
80
+ // src/tombstone.ts
81
+ var TOMBSTONE_CONTENT_HASH_PREFIX = "TOMBSTONE:";
82
+ var TOMBSTONE_ENTRY_ID_PREFIX = "tomb_";
83
+ var TOMBSTONE_ENTRY_TYPE = "tombstone";
84
+ var HEX_PATTERN = /^[0-9a-f]{64}$/;
85
+ function isTombstone(entryType, entryId) {
86
+ return entryType === TOMBSTONE_ENTRY_TYPE && entryId.startsWith(TOMBSTONE_ENTRY_ID_PREFIX);
87
+ }
88
+ function validateTombstoneContentHash(contentHash) {
89
+ if (contentHash.length !== 74) {
90
+ return false;
91
+ }
92
+ if (!contentHash.startsWith(TOMBSTONE_CONTENT_HASH_PREFIX)) {
93
+ return false;
94
+ }
95
+ const hexPart = contentHash.slice(10);
96
+ return HEX_PATTERN.test(hexPart);
97
+ }
98
+
99
+ // src/chain.ts
100
+ var CHAIN_HASH_PREFIX = "aleutian.chain.v2:";
101
+ var CHAIN_HASH_PREFIX_V3 = "aleutian.chain.v3:";
102
+ var FORMAT_V2 = 2;
103
+ var FORMAT_V3 = 3;
104
+ var HASH_HEX = /^[0-9a-f]{128}$/;
105
+ var TIMESTAMP_FORM = /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})\.[0-9]{6}Z$/;
106
+ var MAX_GLOBAL_SEQ = 2n ** 63n - 1n;
107
+ function isChainDigest(value) {
108
+ return typeof value === "string" && HASH_HEX.test(value);
109
+ }
110
+ function isChainTimestamp(value) {
111
+ if (typeof value !== "string") return false;
112
+ const m = TIMESTAMP_FORM.exec(value);
113
+ if (!m) return false;
114
+ const year = Number(m[1]);
115
+ const month = Number(m[2]);
116
+ const day = Number(m[3]);
117
+ if (month < 1 || month > 12 || day < 1) return false;
118
+ const leap = year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
119
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
120
+ if (day > days[month - 1]) return false;
121
+ return Number(m[4]) <= 23 && Number(m[5]) <= 59 && Number(m[6]) <= 59;
122
+ }
123
+ function normalizeFormatVersion(version) {
124
+ return !version ? FORMAT_V2 : version;
125
+ }
126
+ async function computeChainHashV3(previousHash, globalSeq, timestamp, contentHash) {
127
+ if (typeof globalSeq === "number") {
128
+ if (!Number.isSafeInteger(globalSeq)) {
129
+ throw new Error(
130
+ `globalSeq ${globalSeq} is not a safe integer; pass a bigint for values beyond Number.MAX_SAFE_INTEGER \u2014 a rounded value would hash to a digest that is not this entry's`
131
+ );
132
+ }
133
+ if (globalSeq < 0) {
134
+ throw new Error(`globalSeq must be non-negative, got ${globalSeq}`);
135
+ }
136
+ } else if (globalSeq < 0n) {
137
+ throw new Error(`globalSeq must be non-negative, got ${globalSeq}`);
138
+ } else if (globalSeq > MAX_GLOBAL_SEQ) {
139
+ throw new Error(`globalSeq must be at most 2^63 - 1, got ${globalSeq}`);
140
+ }
141
+ if (previousHash !== "" && !HASH_HEX.test(previousHash)) {
142
+ throw new Error("previousHash must be empty or exactly 128 lowercase hex characters");
143
+ }
144
+ if (!isChainTimestamp(timestamp)) {
145
+ throw new Error("timestamp must be YYYY-MM-DDTHH:MM:SS.ffffffZ (UTC, six digits)");
146
+ }
147
+ if (!HASH_HEX.test(contentHash) && !validateTombstoneContentHash(contentHash)) {
148
+ throw new Error(
149
+ 'contentHash must be 128 lowercase hex characters, or a tombstone value ("TOMBSTONE:" + 64 lowercase hex)'
150
+ );
151
+ }
152
+ const preimage = CHAIN_HASH_PREFIX_V3 + previousHash + "|" + globalSeq.toString() + "|" + timestamp + "|" + contentHash;
153
+ return sha512Hex(preimage);
154
+ }
155
+ async function computeChainHash(previousHash, runId, sequenceNum, timestamp, contentHash) {
156
+ if (!Number.isSafeInteger(sequenceNum)) {
157
+ throw new Error(`sequenceNum ${sequenceNum} exceeds Number.MAX_SAFE_INTEGER`);
158
+ }
159
+ const preimage = CHAIN_HASH_PREFIX + previousHash + "|" + runId + "|" + sequenceNum.toString() + "|" + timestamp + "|" + contentHash;
160
+ return sha512Hex(preimage);
161
+ }
162
+ var ANCHOR_CHAIN_DOMAIN_V2 = "aleutian.anchor.v2:";
163
+ var SEED_ANCHOR_HASH = "03443d96d3b369839f63f98b712463efba04c00551822743108d60c5f291627859ff0b7dc29f99e3c847b07b0b18bb385426c744847c1b070563f8dd07271f27";
164
+ var SEED_ANCHOR_ID = "anchor_00000000-0000-0000-0000-000000000000";
165
+ async function computeAnchorChainHash(previousAnchorHash, companyId, startEntryId, endEntryId, tipChainHash) {
166
+ const preimage = ANCHOR_CHAIN_DOMAIN_V2 + previousAnchorHash + "|" + companyId + "|" + startEntryId + "|" + endEntryId + "|" + tipChainHash;
167
+ return sha512Hex(preimage);
168
+ }
169
+ var ANCHOR_CHAIN_DOMAIN_V7 = "aleutian.anchor.v7:";
170
+ function anchorChainHashV7Sync(previousAnchorHash, subject, startSeq, endSeq, tipChainHash) {
171
+ const subjectBytes = utf8ToBytes(subject);
172
+ const head = utf8ToBytes(ANCHOR_CHAIN_DOMAIN_V7 + previousAnchorHash + "|");
173
+ const tail = utf8ToBytes("|" + startSeq + "|" + endSeq + "|" + tipChainHash);
174
+ const n = subjectBytes.length;
175
+ const out = new Uint8Array(head.length + 4 + n + tail.length);
176
+ out.set(head, 0);
177
+ out.set([n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255], head.length);
178
+ out.set(subjectBytes, head.length + 4);
179
+ out.set(tail, head.length + 4 + n);
180
+ return bytesToHex(sha512(out));
181
+ }
182
+
183
+ // src/fixtures/embedded-keys-dev.json
184
+ var embedded_keys_dev_default = {
185
+ "aleutian-ml-dsa-65-2026-v1": "MIIHsjALBglghkgBZQMEAxIDggehAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
186
+ "aleutian-ml-dsa-65-2025-v1": "MIIHsjALBglghkgBZQMEAxIDggehAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
187
+ };
188
+
189
+ // src/fixtures/embedded-keys-staging.json
190
+ var embedded_keys_staging_default = {
191
+ "aleutian-ml-dsa-65-2026-v1": "MIIHsjALBglghkgBZQMEAxIDggehAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
192
+ "aleutian-ml-dsa-65-2025-v1": "MIIHsjALBglghkgBZQMEAxIDggehAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
193
+ };
194
+
195
+ // src/fixtures/embedded-keys-prod.json
196
+ var embedded_keys_prod_default = {
197
+ "aleutian-ml-dsa-65-2026-v1": "MIIHsjALBglghkgBZQMEAxIDggehAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
198
+ "aleutian-ml-dsa-65-2025-v1": "MIIHsjALBglghkgBZQMEAxIDggehAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
199
+ };
200
+
201
+ // src/keys.ts
202
+ var MLDSA_PUB_KEY_SIZE = 1952;
203
+ function readDerLength(data, offset) {
204
+ if (offset >= data.length) {
205
+ throw new Error("DER: unexpected end of data");
206
+ }
207
+ const b = data[offset];
208
+ offset += 1;
209
+ if (b < 128) {
210
+ return [b, offset];
211
+ }
212
+ const numBytes = b & 127;
213
+ if (offset + numBytes > data.length) {
214
+ throw new Error("DER: length encoding truncated");
215
+ }
216
+ let length = 0;
217
+ for (let i = 0; i < numBytes; i++) {
218
+ length = length << 8 | data[offset];
219
+ offset += 1;
220
+ }
221
+ return [length, offset];
222
+ }
223
+ function decodeSpkiDer(keyB64) {
224
+ const der = base64ToUint8Array(keyB64);
225
+ if (der.length < MLDSA_PUB_KEY_SIZE + 20) {
226
+ throw new Error(`Invalid SPKI DER: too short (${der.length} bytes)`);
227
+ }
228
+ let pos = 0;
229
+ if (der[pos] !== 48) {
230
+ throw new Error("Invalid SPKI: expected SEQUENCE tag");
231
+ }
232
+ pos += 1;
233
+ const [outerLen, contentStart] = readDerLength(der, pos);
234
+ if (contentStart + outerLen > der.length) {
235
+ throw new Error("SPKI DER: outer SEQUENCE truncated");
236
+ }
237
+ pos = contentStart;
238
+ if (der[pos] !== 48) {
239
+ throw new Error("Invalid SPKI: expected AlgorithmIdentifier SEQUENCE");
240
+ }
241
+ pos += 1;
242
+ const [algLen, algContentStart] = readDerLength(der, pos);
243
+ pos = algContentStart + algLen;
244
+ if (der[pos] !== 3) {
245
+ throw new Error("Invalid SPKI: expected BIT STRING tag");
246
+ }
247
+ pos += 1;
248
+ const [bitStringLen, bitStringStart] = readDerLength(der, pos);
249
+ pos = bitStringStart;
250
+ if (der[pos] !== 0) {
251
+ throw new Error("Invalid SPKI BIT STRING: non-zero unused bits");
252
+ }
253
+ pos += 1;
254
+ const raw = der.slice(pos, pos + bitStringLen - 1);
255
+ if (raw.length !== MLDSA_PUB_KEY_SIZE) {
256
+ throw new Error(
257
+ `Invalid ML-DSA-65 key size: got ${raw.length} bytes, want ${MLDSA_PUB_KEY_SIZE}`
258
+ );
259
+ }
260
+ return raw;
261
+ }
262
+ function loadFixture() {
263
+ const env = typeof process !== "undefined" && process.env && process.env.ALEUTIAN_ENV ? process.env.ALEUTIAN_ENV : "dev";
264
+ switch (env) {
265
+ case "prod":
266
+ return embedded_keys_prod_default;
267
+ case "staging":
268
+ return embedded_keys_staging_default;
269
+ default:
270
+ return embedded_keys_dev_default;
271
+ }
272
+ }
273
+ var _EMBEDDED_PUBLIC_KEYS_B64 = new Map(
274
+ Object.entries(loadFixture())
275
+ );
276
+ new Map(
277
+ Array.from(_EMBEDDED_PUBLIC_KEYS_B64.entries()).map(([id, b64]) => [
278
+ id,
279
+ decodeSpkiDer(b64)
280
+ ])
281
+ );
282
+
283
+ // src/canonicalBase64.ts
284
+ var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
285
+ var DECODE = (() => {
286
+ const table = new Int16Array(128).fill(-1);
287
+ for (let i = 0; i < ALPHABET.length; i++) table[ALPHABET.charCodeAt(i)] = i;
288
+ return table;
289
+ })();
290
+ function sextet(code) {
291
+ return code < 128 ? DECODE[code] : -1;
292
+ }
293
+ function decodeCanonicalBase64(text) {
294
+ const len = text.length;
295
+ if (len % 4 !== 0) return null;
296
+ if (len === 0) return new Uint8Array(0);
297
+ let pad = 0;
298
+ if (text.charCodeAt(len - 1) === 61) pad = text.charCodeAt(len - 2) === 61 ? 2 : 1;
299
+ const out = new Uint8Array(len / 4 * 3 - pad);
300
+ let o = 0;
301
+ for (let i = 0; i < len; i += 4) {
302
+ const last = i + 4 === len;
303
+ const a = sextet(text.charCodeAt(i));
304
+ const b = sextet(text.charCodeAt(i + 1));
305
+ if (a < 0 || b < 0) return null;
306
+ if (last && pad === 2) {
307
+ if ((b & 15) !== 0) return null;
308
+ out[o++] = a << 2 | b >> 4;
309
+ break;
310
+ }
311
+ const c = sextet(text.charCodeAt(i + 2));
312
+ if (c < 0) return null;
313
+ if (last && pad === 1) {
314
+ if ((c & 3) !== 0) return null;
315
+ out[o++] = a << 2 | b >> 4;
316
+ out[o++] = (b & 15) << 4 | c >> 2;
317
+ break;
318
+ }
319
+ const d = sextet(text.charCodeAt(i + 3));
320
+ if (d < 0) return null;
321
+ out[o++] = a << 2 | b >> 4;
322
+ out[o++] = (b & 15) << 4 | c >> 2;
323
+ out[o++] = (c & 3) << 6 | d;
324
+ }
325
+ return out;
326
+ }
327
+
328
+ // src/anchorCanonical.ts
329
+ var ANCHOR_SIGNATURE_CONTEXT_V7 = "aleutian.proof.anchor.v7";
330
+ var HEX_128 = /^[0-9a-f]{128}$/;
331
+ var LONE_SURROGATE = /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g;
332
+ var HAS_LONE_SURROGATE = new RegExp(LONE_SURROGATE.source);
333
+ function encodeInt64Token(v) {
334
+ if (typeof v === "bigint") {
335
+ return v.toString();
336
+ }
337
+ if (!Number.isFinite(v) || !Number.isInteger(v)) {
338
+ throw new AnchorVerificationError(
339
+ "anchor int64 field: number must be a finite integer"
340
+ );
341
+ }
342
+ if (!Number.isSafeInteger(v)) {
343
+ throw new AnchorVerificationError(
344
+ "anchor int64 field: number exceeds Number.MAX_SAFE_INTEGER; pass as bigint to avoid precision loss"
345
+ );
346
+ }
347
+ return v.toString();
348
+ }
349
+ function anchorInteger(v) {
350
+ if (typeof v === "bigint") return v;
351
+ if (typeof v === "number" && Number.isSafeInteger(v)) return BigInt(v);
352
+ throw new AnchorVerificationError(
353
+ "anchor int64 field: must be an integer (a bigint beyond Number.MAX_SAFE_INTEGER)"
354
+ );
355
+ }
356
+ function checkAnchorWellFormed(anchor) {
357
+ const version = anchor.version;
358
+ if (typeof version !== "number" || !Number.isInteger(version)) {
359
+ throw new AnchorVerificationError("anchor version must be an integer");
360
+ }
361
+ if (version === 5) {
362
+ throw new AnchorVerificationError(
363
+ "unsupported anchor version: 5 is withdrawn and has no canonical form"
364
+ );
365
+ }
366
+ if (version < 1 || version > 7) {
367
+ throw new AnchorVerificationError("unsupported anchor version");
368
+ }
369
+ if (version === 7) {
370
+ checkAnchorV7WellFormed(anchor);
371
+ return;
372
+ }
373
+ if (anchor.range?.startSeq !== void 0 || anchor.range?.endSeq !== void 0 || anchor.previousAnchorHash !== void 0) {
374
+ throw new AnchorVerificationError(
375
+ `Version=${version} anchor must not carry previous_anchor_hash, start_seq or end_seq`
376
+ );
377
+ }
378
+ const verifiedThrough = anchorInteger(anchor.verifiedThrough ?? 0);
379
+ const entryCount = anchorInteger(anchor.entryCount);
380
+ const createdAtMs = anchorInteger(anchor.createdAtMs);
381
+ if (version <= 3) {
382
+ if (verifiedThrough !== 0n) {
383
+ throw new AnchorVerificationError(
384
+ "Version<=3 anchor must have verified_through=0"
385
+ );
386
+ }
387
+ } else {
388
+ if (verifiedThrough <= 0n) {
389
+ throw new AnchorVerificationError(
390
+ `Version=${version} anchor must have positive verified_through`
391
+ );
392
+ }
393
+ if (version === 6 && !anchor.subject) {
394
+ throw new AnchorVerificationError(
395
+ "Version=6 anchor must have a non-empty subject"
396
+ );
397
+ }
398
+ if (entryCount < 1n) {
399
+ throw new AnchorVerificationError("anchor entry_count must be at least 1");
400
+ }
401
+ if (verifiedThrough > entryCount) {
402
+ throw new AnchorVerificationError(
403
+ "anchor verified_through must not exceed entry_count"
404
+ );
405
+ }
406
+ if (createdAtMs < 0n) {
407
+ throw new AnchorVerificationError("anchor created_at_ms must not be negative");
408
+ }
409
+ }
410
+ if (typeof anchor.signingKeyId !== "string" || anchor.signingKeyId === "") {
411
+ throw new AnchorVerificationError("anchor signing_key_id must not be empty");
412
+ }
413
+ }
414
+ var MAX_INT642 = 2n ** 63n - 1n;
415
+ function checkAnchorV7WellFormed(anchor) {
416
+ if (typeof anchor.subject !== "string" || anchor.subject === "") {
417
+ throw new AnchorVerificationError("Version=7 anchor must have a non-empty subject");
418
+ }
419
+ if (typeof anchor.anchorId !== "string" || anchor.anchorId === "") {
420
+ throw new AnchorVerificationError("Version=7 anchor must have a non-empty anchor_id");
421
+ }
422
+ if (typeof anchor.previousAnchorId !== "string" || anchor.previousAnchorId === "") {
423
+ throw new AnchorVerificationError(
424
+ "Version=7 anchor must have a non-empty previous_anchor_id"
425
+ );
426
+ }
427
+ for (const [name, value] of [
428
+ ["subject", anchor.subject],
429
+ ["anchor_id", anchor.anchorId],
430
+ ["previous_anchor_id", anchor.previousAnchorId],
431
+ ["signing_key_id", anchor.signingKeyId]
432
+ ]) {
433
+ if (typeof value === "string" && HAS_LONE_SURROGATE.test(value)) {
434
+ throw new AnchorVerificationError(`Version=7 anchor ${name} must be valid UTF-8`);
435
+ }
436
+ }
437
+ if (anchor.range === void 0 || anchor.range.startSeq === void 0 || anchor.range.endSeq === void 0) {
438
+ throw new AnchorVerificationError("Version=7 anchor must name its range by start_seq and end_seq");
439
+ }
440
+ if (anchor.range.startEntryId || anchor.range.endEntryId) {
441
+ throw new AnchorVerificationError("Version=7 anchor must not name its range by entry id");
442
+ }
443
+ if (anchor.verifiedThrough !== void 0 && anchorInteger(anchor.verifiedThrough) !== 0n) {
444
+ throw new AnchorVerificationError("Version=7 anchor has no verified_through");
445
+ }
446
+ const start = anchorInteger(anchor.range.startSeq);
447
+ const end = anchorInteger(anchor.range.endSeq);
448
+ const count = anchorInteger(anchor.entryCount);
449
+ for (const n of [start, end, count]) {
450
+ if (n < 0n || n > MAX_INT642) {
451
+ throw new AnchorVerificationError(
452
+ "Version=7 anchor sequence numbers and entry_count must be from 0 to 2^63 - 1"
453
+ );
454
+ }
455
+ }
456
+ if (start > end) {
457
+ throw new AnchorVerificationError("Version=7 anchor start_seq must not exceed end_seq");
458
+ }
459
+ if (count !== end - start + 1n) {
460
+ throw new AnchorVerificationError(
461
+ "Version=7 anchor entry_count must equal end_seq - start_seq + 1"
462
+ );
463
+ }
464
+ const prevHash = anchor.previousAnchorHash;
465
+ if (typeof prevHash !== "string" || !HEX_128.test(prevHash)) {
466
+ throw new AnchorVerificationError(
467
+ "Version=7 anchor previous_anchor_hash must be 128 lowercase hex characters"
468
+ );
469
+ }
470
+ if (anchor.previousAnchorId === SEED_ANCHOR_ID !== (prevHash === SEED_ANCHOR_HASH)) {
471
+ throw new AnchorVerificationError(
472
+ "Version=7 anchor names the seed id exactly when it names the seed hash"
473
+ );
474
+ }
475
+ if (typeof anchor.chainHash !== "string" || !HEX_128.test(anchor.chainHash)) {
476
+ throw new AnchorVerificationError("Version=7 anchor chain_hash must be 128 lowercase hex characters");
477
+ }
478
+ if (anchorInteger(anchor.createdAtMs) < 0n) {
479
+ throw new AnchorVerificationError("anchor created_at_ms must not be negative");
480
+ }
481
+ if (typeof anchor.signingKeyId !== "string" || anchor.signingKeyId === "") {
482
+ throw new AnchorVerificationError("anchor signing_key_id must not be empty");
483
+ }
484
+ }
485
+ function encodeJsonString(s) {
486
+ const wellFormed = s.replace(LONE_SURROGATE, "\uFFFD");
487
+ return JSON.stringify(wellFormed).replace(
488
+ /[<>&\u2028\u2029]/g,
489
+ (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0")
490
+ );
491
+ }
492
+ function canonicalizeAnchorV6(anchor) {
493
+ const verifiedThrough = anchor.verifiedThrough ?? 0;
494
+ const parts = [
495
+ `"anchor_id":${encodeJsonString(anchor.anchorId)}`,
496
+ `"chain_hash":${encodeJsonString(anchor.chainHash)}`,
497
+ `"created_at_ms":${encodeInt64Token(anchor.createdAtMs)}`,
498
+ `"entry_count":${encodeInt64Token(anchor.entryCount)}`,
499
+ `"previous_anchor_id":${encodeJsonString(anchor.previousAnchorId)}`,
500
+ `"range":{"end_entry_id":${encodeJsonString(anchor.range.endEntryId)},"start_entry_id":${encodeJsonString(anchor.range.startEntryId)}}`,
501
+ `"signing_key_id":${encodeJsonString(anchor.signingKeyId)}`,
502
+ `"subject":${encodeJsonString(anchor.subject)}`,
503
+ `"verified_through":${encodeInt64Token(verifiedThrough)}`,
504
+ `"version":${anchor.version}`
505
+ ];
506
+ return `{${parts.join(",")}}`;
507
+ }
508
+ function canonicalizeAnchorV7(anchor) {
509
+ const range = anchor.range;
510
+ if (range.startSeq === void 0 || range.endSeq === void 0) {
511
+ throw new AnchorVerificationError("Version=7 anchor must name its range by start_seq and end_seq");
512
+ }
513
+ const decimal = (v) => JSON.stringify(encodeInt64Token(v));
514
+ const parts = [
515
+ `"anchor_id":${encodeJsonString(anchor.anchorId)}`,
516
+ `"chain_hash":${encodeJsonString(anchor.chainHash)}`,
517
+ `"created_at_ms":${encodeInt64Token(anchor.createdAtMs)}`,
518
+ `"entry_count":${decimal(anchor.entryCount)}`,
519
+ `"previous_anchor_hash":${encodeJsonString(anchor.previousAnchorHash ?? "")}`,
520
+ `"previous_anchor_id":${encodeJsonString(anchor.previousAnchorId)}`,
521
+ `"range":{"end_seq":${decimal(range.endSeq)},"start_seq":${decimal(range.startSeq)}}`,
522
+ `"signing_key_id":${encodeJsonString(anchor.signingKeyId)}`,
523
+ `"subject":${encodeJsonString(anchor.subject)}`,
524
+ `"version":${anchor.version}`
525
+ ];
526
+ return `{${parts.join(",")}}`;
527
+ }
528
+ new TextEncoder().encode(ANCHOR_SIGNATURE_CONTEXT_V7);
529
+
530
+ // src/strictJson.ts
531
+ var JsonNumber = class {
532
+ constructor(text) {
533
+ this.text = text;
534
+ }
535
+ };
536
+ var JsonObject = class {
537
+ members = /* @__PURE__ */ new Map();
538
+ };
539
+ var GrammarViolation = class {
540
+ };
541
+ var VIOLATION = new GrammarViolation();
542
+ var QUOTE = 34;
543
+ var BACKSLASH = 92;
544
+ function isDigit(c) {
545
+ return c !== void 0 && c >= 48 && c <= 57;
546
+ }
547
+ var StrictJsonScanner = class {
548
+ constructor(b, maxDepth) {
549
+ this.b = b;
550
+ this.maxDepth = maxDepth;
551
+ }
552
+ pos = 0;
553
+ decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
554
+ /** Reused output buffer for strings that hold escapes (grows by doubling). */
555
+ scratch = new Uint8Array(0);
556
+ scratchLength = 0;
557
+ /** Throw the opaque grammar violation. */
558
+ fail() {
559
+ throw VIOLATION;
560
+ }
561
+ peek() {
562
+ return this.b[this.pos];
563
+ }
564
+ skipWhitespace() {
565
+ const b = this.b;
566
+ while (this.pos < b.length) {
567
+ const c = b[this.pos];
568
+ if (c !== 32 && c !== 9 && c !== 10 && c !== 13) return;
569
+ this.pos++;
570
+ }
571
+ }
572
+ expect(c) {
573
+ if (this.b[this.pos] !== c) this.fail();
574
+ this.pos++;
575
+ }
576
+ /** The end of the document: only whitespace may follow the top-level value. */
577
+ expectEnd() {
578
+ this.skipWhitespace();
579
+ if (this.pos !== this.b.length) this.fail();
580
+ }
581
+ literal(word) {
582
+ for (let i = 0; i < word.length; i++) {
583
+ if (this.b[this.pos + i] !== word.charCodeAt(i)) this.fail();
584
+ }
585
+ this.pos += word.length;
586
+ }
587
+ /** Skip a number, holding it to RFC 8259's grammar exactly. */
588
+ skipNumber() {
589
+ const b = this.b;
590
+ let p = this.pos;
591
+ if (b[p] === 45) p++;
592
+ if (b[p] === 48) p++;
593
+ else if (isDigit(b[p]) && b[p] !== 48) while (isDigit(b[p])) p++;
594
+ else this.fail();
595
+ if (b[p] === 46) {
596
+ p++;
597
+ if (!isDigit(b[p])) this.fail();
598
+ while (isDigit(b[p])) p++;
599
+ }
600
+ if (b[p] === 101 || b[p] === 69) {
601
+ p++;
602
+ if (b[p] === 43 || b[p] === 45) p++;
603
+ if (!isDigit(b[p])) this.fail();
604
+ while (isDigit(b[p])) p++;
605
+ }
606
+ this.pos = p;
607
+ }
608
+ /** Read a number as its source text (ASCII, so UTF-8 decoding is exact). */
609
+ readNumber() {
610
+ const start = this.pos;
611
+ this.skipNumber();
612
+ return this.decoder.decode(this.b.subarray(start, this.pos));
613
+ }
614
+ /** Read a string (the cursor is on its opening quote) and decode it. */
615
+ readString() {
616
+ return this.scanString(true);
617
+ }
618
+ /** Check a string (the cursor is on its opening quote) without decoding it. */
619
+ skipString() {
620
+ this.scanString(false);
621
+ }
622
+ /**
623
+ * Scan one string. Raw runs are validated in place; escapes are written,
624
+ * with the runs around them, into one reused byte buffer, so a string full of
625
+ * escapes costs one decode, not one allocation per escape.
626
+ */
627
+ scanString(decode) {
628
+ const b = this.b;
629
+ this.pos++;
630
+ const start = this.pos;
631
+ let runStart = start;
632
+ let escaped = false;
633
+ this.scratchLength = 0;
634
+ for (; ; ) {
635
+ const c = b[this.pos];
636
+ if (c === void 0) this.fail();
637
+ if (c === QUOTE) {
638
+ const end = this.pos;
639
+ this.pos++;
640
+ if (!decode) return "";
641
+ if (!escaped)
642
+ return end === start ? "" : this.decoder.decode(b.subarray(start, end));
643
+ this.put(b.subarray(runStart, end));
644
+ return this.decoder.decode(this.scratch.subarray(0, this.scratchLength));
645
+ }
646
+ if (c < 32) this.fail();
647
+ if (c < 128 && c !== BACKSLASH) {
648
+ this.pos++;
649
+ continue;
650
+ }
651
+ if (c >= 128) {
652
+ this.pos += this.utf8Length(this.pos);
653
+ continue;
654
+ }
655
+ if (decode) {
656
+ escaped = true;
657
+ this.put(b.subarray(runStart, this.pos));
658
+ }
659
+ const e = b[this.pos + 1];
660
+ this.pos += 2;
661
+ let cp;
662
+ switch (e) {
663
+ case 34:
664
+ cp = 34;
665
+ break;
666
+ case 92:
667
+ cp = 92;
668
+ break;
669
+ case 47:
670
+ cp = 47;
671
+ break;
672
+ case 98:
673
+ cp = 8;
674
+ break;
675
+ case 102:
676
+ cp = 12;
677
+ break;
678
+ case 110:
679
+ cp = 10;
680
+ break;
681
+ case 114:
682
+ cp = 13;
683
+ break;
684
+ case 116:
685
+ cp = 9;
686
+ break;
687
+ case 117: {
688
+ const unit = this.hex4(this.pos);
689
+ this.pos += 4;
690
+ if (unit >= 56320 && unit <= 57343) this.fail();
691
+ if (unit >= 55296 && unit <= 56319) {
692
+ if (b[this.pos] !== BACKSLASH || b[this.pos + 1] !== 117) this.fail();
693
+ const low = this.hex4(this.pos + 2);
694
+ if (low < 56320 || low > 57343) this.fail();
695
+ this.pos += 6;
696
+ cp = 65536 + (unit - 55296 << 10) + (low - 56320);
697
+ } else {
698
+ cp = unit;
699
+ }
700
+ break;
701
+ }
702
+ default:
703
+ this.fail();
704
+ }
705
+ if (decode) this.putCodePoint(cp);
706
+ runStart = this.pos;
707
+ }
708
+ }
709
+ reserve(n) {
710
+ const need = this.scratchLength + n;
711
+ if (need <= this.scratch.length) return;
712
+ const grown = new Uint8Array(Math.max(need, this.scratch.length * 2, 256));
713
+ grown.set(this.scratch.subarray(0, this.scratchLength));
714
+ this.scratch = grown;
715
+ }
716
+ put(run) {
717
+ if (run.length === 0) return;
718
+ this.reserve(run.length);
719
+ this.scratch.set(run, this.scratchLength);
720
+ this.scratchLength += run.length;
721
+ }
722
+ /** Append one code point as UTF-8 (escapes never yield a lone surrogate). */
723
+ putCodePoint(cp) {
724
+ this.reserve(4);
725
+ const s = this.scratch;
726
+ let n = this.scratchLength;
727
+ if (cp < 128) {
728
+ s[n++] = cp;
729
+ } else if (cp < 2048) {
730
+ s[n++] = 192 | cp >> 6;
731
+ s[n++] = 128 | cp & 63;
732
+ } else if (cp < 65536) {
733
+ s[n++] = 224 | cp >> 12;
734
+ s[n++] = 128 | cp >> 6 & 63;
735
+ s[n++] = 128 | cp & 63;
736
+ } else {
737
+ s[n++] = 240 | cp >> 18;
738
+ s[n++] = 128 | cp >> 12 & 63;
739
+ s[n++] = 128 | cp >> 6 & 63;
740
+ s[n++] = 128 | cp & 63;
741
+ }
742
+ this.scratchLength = n;
743
+ }
744
+ hex4(at) {
745
+ let v = 0;
746
+ for (let i = 0; i < 4; i++) {
747
+ const c = this.b[at + i];
748
+ let d;
749
+ if (c === void 0) this.fail();
750
+ if (c >= 48 && c <= 57) d = c - 48;
751
+ else if (c >= 97 && c <= 102) d = c - 97 + 10;
752
+ else if (c >= 65 && c <= 70) d = c - 65 + 10;
753
+ else this.fail();
754
+ v = v * 16 + d;
755
+ }
756
+ return v;
757
+ }
758
+ /** Validate one UTF-8 sequence starting at `at`; return its length. */
759
+ utf8Length(at) {
760
+ const b = this.b;
761
+ const c0 = b[at];
762
+ const cont = (i, lo = 128, hi = 191) => {
763
+ const c = b[at + i];
764
+ if (c === void 0 || c < lo || c > hi) this.fail();
765
+ };
766
+ if (c0 >= 194 && c0 <= 223) {
767
+ cont(1);
768
+ return 2;
769
+ }
770
+ if (c0 >= 224 && c0 <= 239) {
771
+ if (c0 === 224) cont(1, 160, 191);
772
+ else if (c0 === 237)
773
+ cont(1, 128, 159);
774
+ else cont(1);
775
+ cont(2);
776
+ return 3;
777
+ }
778
+ if (c0 >= 240 && c0 <= 244) {
779
+ if (c0 === 240) cont(1, 144, 191);
780
+ else if (c0 === 244) cont(1, 128, 143);
781
+ else cont(1);
782
+ cont(2);
783
+ cont(3);
784
+ return 4;
785
+ }
786
+ this.fail();
787
+ }
788
+ };
789
+ function runStrict(scan) {
790
+ try {
791
+ return { ok: true, value: scan() };
792
+ } catch (err) {
793
+ if (err === VIOLATION) return { ok: false };
794
+ if (err instanceof TypeError) return { ok: false };
795
+ throw new Error("strict JSON parser: internal error");
796
+ }
797
+ }
798
+ var TreeParser = class extends StrictJsonScanner {
799
+ duplicateKey = false;
800
+ parseDocument() {
801
+ this.skipWhitespace();
802
+ const v = this.parseValue(1);
803
+ this.expectEnd();
804
+ return v;
805
+ }
806
+ parseValue(depth) {
807
+ const c = this.peek();
808
+ switch (c) {
809
+ case 123:
810
+ return this.parseObject(depth);
811
+ case 91:
812
+ return this.parseArray(depth);
813
+ case QUOTE:
814
+ return this.readString();
815
+ case 116:
816
+ this.literal("true");
817
+ return true;
818
+ case 102:
819
+ this.literal("false");
820
+ return false;
821
+ case 110:
822
+ this.literal("null");
823
+ return null;
824
+ default:
825
+ return new JsonNumber(this.readNumber());
826
+ }
827
+ }
828
+ parseArray(depth) {
829
+ if (depth > this.maxDepth) this.fail();
830
+ this.pos++;
831
+ const items = [];
832
+ this.skipWhitespace();
833
+ if (this.peek() === 93) {
834
+ this.pos++;
835
+ return items;
836
+ }
837
+ for (; ; ) {
838
+ this.skipWhitespace();
839
+ items.push(this.parseValue(depth + 1));
840
+ this.skipWhitespace();
841
+ const c = this.peek();
842
+ if (c === 44) {
843
+ this.pos++;
844
+ continue;
845
+ }
846
+ if (c === 93) {
847
+ this.pos++;
848
+ return items;
849
+ }
850
+ this.fail();
851
+ }
852
+ }
853
+ parseObject(depth) {
854
+ if (depth > this.maxDepth) this.fail();
855
+ this.pos++;
856
+ const obj = new JsonObject();
857
+ this.skipWhitespace();
858
+ if (this.peek() === 125) {
859
+ this.pos++;
860
+ return obj;
861
+ }
862
+ for (; ; ) {
863
+ this.skipWhitespace();
864
+ if (this.peek() !== QUOTE) this.fail();
865
+ const name = this.readString();
866
+ this.skipWhitespace();
867
+ this.expect(58);
868
+ this.skipWhitespace();
869
+ const value = this.parseValue(depth + 1);
870
+ if (obj.members.has(name)) this.duplicateKey = true;
871
+ else obj.members.set(name, value);
872
+ this.skipWhitespace();
873
+ const c = this.peek();
874
+ if (c === 44) {
875
+ this.pos++;
876
+ continue;
877
+ }
878
+ if (c === 125) {
879
+ this.pos++;
880
+ return obj;
881
+ }
882
+ this.fail();
883
+ }
884
+ }
885
+ };
886
+ function parseStrictJson(bytes, maxDepth) {
887
+ const parser = new TreeParser(bytes, maxDepth);
888
+ const r = runStrict(() => parser.parseDocument());
889
+ return r.ok ? { ok: true, value: r.value, duplicateKey: parser.duplicateKey } : r;
890
+ }
891
+
892
+ // src/strictText.ts
893
+ var MAX_ENTRIES_DEPTH = 32;
894
+ var MAX_ANCHOR_DEPTH = 2;
895
+ var INTEGER_TEXT = /^(0|-?[1-9][0-9]*)$/;
896
+ var MIN_INT64 = -(2n ** 63n);
897
+ var MAX_INT643 = 2n ** 63n - 1n;
898
+ var ENCODER = new TextEncoder();
899
+ function toBytes(text) {
900
+ return typeof text === "string" ? ENCODER.encode(text) : text;
901
+ }
902
+ function int64Of(value) {
903
+ if (!(value instanceof JsonNumber) || !INTEGER_TEXT.test(value.text)) return null;
904
+ if (value.text.length > 20) return null;
905
+ const n = BigInt(value.text);
906
+ return n < MIN_INT64 || n > MAX_INT643 ? null : n;
907
+ }
908
+ function narrow(n) {
909
+ return n >= BigInt(Number.MIN_SAFE_INTEGER) && n <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(n) : n;
910
+ }
911
+ function foldName(name) {
912
+ return name.replace(/\u017f/g, "s").replace(/\u212a/g, "k").toLowerCase();
913
+ }
914
+ function hasMiscasedMember(obj, snake, aliases) {
915
+ for (const name of obj.members.keys()) {
916
+ if (!snake.has(name) && !aliases.has(name) && snake.has(foldName(name))) return true;
917
+ }
918
+ return false;
919
+ }
920
+ var BothSpellings = class {
921
+ constructor(snake) {
922
+ this.snake = snake;
923
+ }
924
+ };
925
+ function pick(obj, snake, ...aliases) {
926
+ let found;
927
+ let seen = 0;
928
+ for (const name of [snake, ...aliases]) {
929
+ if (obj.members.has(name)) {
930
+ seen++;
931
+ found = obj.members.get(name);
932
+ }
933
+ }
934
+ if (seen > 1) throw new BothSpellings(snake);
935
+ return found;
936
+ }
937
+ var ENTRY_ALIASES = {
938
+ entry_id: "entryId",
939
+ entry_type: "entryType",
940
+ timestamp: "timestamp",
941
+ run_id: "runId",
942
+ sequence_num: "sequenceNum",
943
+ global_seq: "globalSeq",
944
+ content_hash: "contentHash",
945
+ chain_hash: "chainHash",
946
+ format_version: "formatVersion",
947
+ salt: "salt",
948
+ leaf_hash: "leafHash",
949
+ erased: "erased"
950
+ };
951
+ var ENTRY_SNAKE = new Set(Object.keys(ENTRY_ALIASES));
952
+ var ENTRY_CAMEL = new Set(Object.values(ENTRY_ALIASES));
953
+ var ENTRY_REQUIRED = ["entry_id", "timestamp", "content_hash", "chain_hash"];
954
+ function notEntries(detail) {
955
+ return new EntriesFormatError(`not an entries file: ${detail}`);
956
+ }
957
+ function entryMember(obj, snake) {
958
+ const alias = ENTRY_ALIASES[snake];
959
+ try {
960
+ return alias === snake ? obj.members.get(snake) : pick(obj, snake, alias);
961
+ } catch (err) {
962
+ if (err instanceof BothSpellings) {
963
+ throw notEntries(`an entry carries ${snake} under two spellings`);
964
+ }
965
+ throw err;
966
+ }
967
+ }
968
+ function entryString(obj, snake, required) {
969
+ const v = entryMember(obj, snake);
970
+ if (v === void 0) {
971
+ if (required) throw notEntries(`an entry has no ${snake}`);
972
+ return "";
973
+ }
974
+ if (typeof v !== "string") throw notEntries(`an entry's ${snake} is not a string`);
975
+ return v;
976
+ }
977
+ function entryInteger(obj, snake) {
978
+ const v = entryMember(obj, snake);
979
+ if (v === void 0) return void 0;
980
+ const n = int64Of(v);
981
+ if (n === null) throw notEntries(`an entry's ${snake} is not an integer`);
982
+ return n;
983
+ }
984
+ function v4Members(obj) {
985
+ const out = {};
986
+ if (entryMember(obj, "salt") !== void 0) out.salt = entryString(obj, "salt", true);
987
+ if (entryMember(obj, "leaf_hash") !== void 0) {
988
+ out.leafHash = entryString(obj, "leaf_hash", true);
989
+ }
990
+ const erased = entryMember(obj, "erased");
991
+ if (erased !== void 0) {
992
+ if (typeof erased !== "boolean") throw notEntries("an entry's erased is not a boolean");
993
+ out.erased = erased;
994
+ }
995
+ return out;
996
+ }
997
+ function entryV4From(value, formatVersion) {
998
+ const present = /* @__PURE__ */ new Set();
999
+ for (const snake of ENTRY_SNAKE) {
1000
+ if (entryMember(value, snake) !== void 0) present.add(snake);
1001
+ }
1002
+ const sequenceNum = narrow(entryInteger(value, "sequence_num") ?? 0n);
1003
+ if (typeof sequenceNum !== "number") {
1004
+ throw notEntries("an entry's sequence_num is out of range");
1005
+ }
1006
+ return {
1007
+ entryId: entryString(value, "entry_id", false),
1008
+ entryType: entryString(value, "entry_type", false),
1009
+ timestamp: entryString(value, "timestamp", true),
1010
+ runId: entryString(value, "run_id", false),
1011
+ sequenceNum,
1012
+ globalSeq: narrow(entryInteger(value, "global_seq") ?? 0n),
1013
+ contentHash: entryString(value, "content_hash", false),
1014
+ chainHash: entryString(value, "chain_hash", true),
1015
+ formatVersion: Number(formatVersion),
1016
+ ...v4Members(value),
1017
+ presentMembers: present
1018
+ };
1019
+ }
1020
+ function entryFrom(value) {
1021
+ if (!(value instanceof JsonObject)) {
1022
+ throw notEntries("an entry is not a JSON object");
1023
+ }
1024
+ if (hasMiscasedMember(value, ENTRY_SNAKE, ENTRY_CAMEL)) {
1025
+ throw notEntries("an entry member is spelled in another case");
1026
+ }
1027
+ const declared = entryInteger(value, "format_version");
1028
+ if (declared === 4n) return entryV4From(value, declared);
1029
+ const [entryId, timestamp, contentHash, chainHash] = ENTRY_REQUIRED.map(
1030
+ (name) => entryString(value, name, true)
1031
+ );
1032
+ const sequenceNum = narrow(entryInteger(value, "sequence_num") ?? 0n);
1033
+ if (typeof sequenceNum !== "number") {
1034
+ throw notEntries("an entry's sequence_num is out of range");
1035
+ }
1036
+ const formatVersion = entryInteger(value, "format_version");
1037
+ return {
1038
+ entryId,
1039
+ entryType: entryString(value, "entry_type", false),
1040
+ timestamp,
1041
+ runId: entryString(value, "run_id", false),
1042
+ sequenceNum,
1043
+ globalSeq: narrow(entryInteger(value, "global_seq") ?? 0n),
1044
+ contentHash,
1045
+ chainHash,
1046
+ ...formatVersion === void 0 ? {} : { formatVersion: Number(formatVersion) },
1047
+ // A v4 member on a v2 or v3 entry is carried through for the verifier to
1048
+ // report as format_field_misuse (format-spec §2.2), never dropped.
1049
+ ...v4Members(value)
1050
+ };
1051
+ }
1052
+ function strictDocument(bytes) {
1053
+ const r = parseStrictJson(bytes, MAX_ENTRIES_DEPTH);
1054
+ if (!r.ok) return void 0;
1055
+ if (r.duplicateKey) throw notEntries("a member name is repeated");
1056
+ return r.value;
1057
+ }
1058
+ function strictValue(bytes, what) {
1059
+ const v = strictDocument(bytes);
1060
+ if (v === void 0) throw notEntries(`${what} is not valid JSON`);
1061
+ return v;
1062
+ }
1063
+ function firstNonSpace(b) {
1064
+ for (const c of b) {
1065
+ if (c !== 32 && c !== 9 && c !== 10 && c !== 13) return c;
1066
+ }
1067
+ return void 0;
1068
+ }
1069
+ function parseEntriesText(text) {
1070
+ const bytes = toBytes(text);
1071
+ const first = firstNonSpace(bytes);
1072
+ if (first === void 0) throw notEntries("the document is empty");
1073
+ let values;
1074
+ if (first === 91) {
1075
+ const doc = strictValue(bytes, "the document");
1076
+ if (!Array.isArray(doc)) throw notEntries("the document is not an array of entries");
1077
+ values = doc;
1078
+ } else if (first === 123) {
1079
+ const doc = strictDocument(bytes);
1080
+ if (doc instanceof JsonObject) {
1081
+ const wrapped = doc.members.get("entries");
1082
+ if (Array.isArray(wrapped)) values = wrapped;
1083
+ }
1084
+ }
1085
+ if (values === void 0) {
1086
+ values = [];
1087
+ let start = 0;
1088
+ while (start < bytes.length) {
1089
+ let end = bytes.indexOf(10, start);
1090
+ if (end === -1) end = bytes.length;
1091
+ const line = bytes.subarray(start, end);
1092
+ if (firstNonSpace(line) !== void 0) values.push(strictValue(line, "a line"));
1093
+ start = end + 1;
1094
+ }
1095
+ }
1096
+ if (values.length === 0) throw notEntries("it holds no entries");
1097
+ return values.map(entryFrom);
1098
+ }
1099
+ var ANCHOR_ALIASES = {
1100
+ version: [],
1101
+ anchor_id: ["anchorId"],
1102
+ subject: ["subjectId"],
1103
+ company_id: ["companyId"],
1104
+ chain_hash: ["chainHash"],
1105
+ range: [],
1106
+ entry_count: ["entryCount"],
1107
+ signature: [],
1108
+ signing_key_id: ["signingKeyId"],
1109
+ created_at_ms: ["createdAtMs"],
1110
+ previous_anchor_id: ["previousAnchorId"],
1111
+ previous_anchor_hash: ["previousAnchorHash"],
1112
+ verified_through: ["verifiedThrough"]
1113
+ };
1114
+ var RANGE_ALIASES = {
1115
+ start_entry_id: ["startEntryId"],
1116
+ end_entry_id: ["endEntryId"],
1117
+ start_seq: ["startSeq"],
1118
+ end_seq: ["endSeq"]
1119
+ };
1120
+ var V7_REQUIRED = [
1121
+ "version",
1122
+ "anchor_id",
1123
+ "subject",
1124
+ "chain_hash",
1125
+ "range",
1126
+ "entry_count",
1127
+ "signature",
1128
+ "signing_key_id",
1129
+ "created_at_ms",
1130
+ "previous_anchor_id",
1131
+ "previous_anchor_hash"
1132
+ ];
1133
+ var DECIMAL_TEXT = /^(0|[1-9][0-9]*)$/;
1134
+ function nameSets(table) {
1135
+ return {
1136
+ table,
1137
+ snake: new Set(Object.keys(table)),
1138
+ aliases: new Set(Object.values(table).flat())
1139
+ };
1140
+ }
1141
+ var ANCHOR_NAMES = nameSets(ANCHOR_ALIASES);
1142
+ var RANGE_NAMES = nameSets(RANGE_ALIASES);
1143
+ function refused(detail) {
1144
+ return new AnchorVerificationError(`anchor refused: ${detail}`);
1145
+ }
1146
+ function onlyKnownMembers(obj, names, where) {
1147
+ if (hasMiscasedMember(obj, names.snake, names.aliases)) {
1148
+ throw refused(`${where} has a member spelled in another case`);
1149
+ }
1150
+ for (const name of obj.members.keys()) {
1151
+ if (!names.snake.has(name) && !names.aliases.has(name)) {
1152
+ throw refused(`${where} has an unknown member`);
1153
+ }
1154
+ }
1155
+ }
1156
+ function anchorMember(obj, names, snake) {
1157
+ try {
1158
+ return pick(obj, snake, ...names.table[snake]);
1159
+ } catch (err) {
1160
+ if (err instanceof BothSpellings) throw refused(`${snake} is given under two spellings`);
1161
+ throw err;
1162
+ }
1163
+ }
1164
+ function stringMember(obj, names, snake) {
1165
+ const v = anchorMember(obj, names, snake);
1166
+ if (v === void 0) return "";
1167
+ if (typeof v !== "string") throw refused(`${snake} is not a string`);
1168
+ return v;
1169
+ }
1170
+ function integerMember(obj, names, snake) {
1171
+ const v = anchorMember(obj, names, snake);
1172
+ if (v === void 0) return 0n;
1173
+ const n = int64Of(v);
1174
+ if (n === null) throw refused(`${snake} is not an integer`);
1175
+ return n;
1176
+ }
1177
+ function decimalMember(obj, names, snake) {
1178
+ const v = anchorMember(obj, names, snake);
1179
+ if (v === void 0) throw refused(`a v7 anchor has no ${snake}`);
1180
+ if (typeof v !== "string") throw refused(`${snake} is not a string`);
1181
+ if (!DECIMAL_TEXT.test(v) || v.length > 19 || BigInt(v) > MAX_INT643) {
1182
+ throw refused(`${snake} is not a decimal integer from 0 to 2^63 - 1`);
1183
+ }
1184
+ return BigInt(v);
1185
+ }
1186
+ function anchorV7From(doc, range) {
1187
+ for (const name of V7_REQUIRED) {
1188
+ if (anchorMember(doc, ANCHOR_NAMES, name) === void 0) {
1189
+ throw refused(`a v7 anchor has no ${name}`);
1190
+ }
1191
+ }
1192
+ if (anchorMember(doc, ANCHOR_NAMES, "verified_through") !== void 0) {
1193
+ throw refused("a v7 anchor has no verified_through");
1194
+ }
1195
+ if (range === void 0) throw refused("a v7 anchor has no range");
1196
+ for (const name of ["start_entry_id", "end_entry_id"]) {
1197
+ if (anchorMember(range, RANGE_NAMES, name) !== void 0) {
1198
+ throw refused("a v7 range names start_seq and end_seq, not entry ids");
1199
+ }
1200
+ }
1201
+ return {
1202
+ version: 7,
1203
+ anchorId: stringMember(doc, ANCHOR_NAMES, "anchor_id"),
1204
+ subject: stringMember(doc, ANCHOR_NAMES, "subject"),
1205
+ chainHash: stringMember(doc, ANCHOR_NAMES, "chain_hash"),
1206
+ range: {
1207
+ startEntryId: "",
1208
+ endEntryId: "",
1209
+ startSeq: decimalMember(range, RANGE_NAMES, "start_seq"),
1210
+ endSeq: decimalMember(range, RANGE_NAMES, "end_seq")
1211
+ },
1212
+ entryCount: decimalMember(doc, ANCHOR_NAMES, "entry_count"),
1213
+ signature: stringMember(doc, ANCHOR_NAMES, "signature"),
1214
+ signingKeyId: stringMember(doc, ANCHOR_NAMES, "signing_key_id"),
1215
+ createdAtMs: narrow(integerMember(doc, ANCHOR_NAMES, "created_at_ms")),
1216
+ previousAnchorId: stringMember(doc, ANCHOR_NAMES, "previous_anchor_id"),
1217
+ previousAnchorHash: stringMember(doc, ANCHOR_NAMES, "previous_anchor_hash")
1218
+ };
1219
+ }
1220
+ function parseAnchorText(text) {
1221
+ const r = parseStrictJson(toBytes(text), MAX_ANCHOR_DEPTH);
1222
+ if (!r.ok) throw refused("not valid JSON for an anchor");
1223
+ if (r.duplicateKey) throw refused("a member name is repeated");
1224
+ const doc = r.value;
1225
+ if (!(doc instanceof JsonObject)) throw refused("not a JSON object");
1226
+ onlyKnownMembers(doc, ANCHOR_NAMES, "the anchor");
1227
+ const declared = integerMember(doc, ANCHOR_NAMES, "version");
1228
+ const version = declared < 0n || declared > 1000n ? -1 : Number(declared);
1229
+ const subjectName = version === 6 || version === 7 ? "subject" : "company_id";
1230
+ const otherName = subjectName === "subject" ? "company_id" : "subject";
1231
+ if (anchorMember(doc, ANCHOR_NAMES, otherName) !== void 0) {
1232
+ throw refused(`this version names its subject ${subjectName}, not ${otherName}`);
1233
+ }
1234
+ const rangeValue = anchorMember(doc, ANCHOR_NAMES, "range");
1235
+ let range = new JsonObject();
1236
+ if (rangeValue !== void 0) {
1237
+ if (!(rangeValue instanceof JsonObject)) throw refused("range is not an object");
1238
+ range = rangeValue;
1239
+ onlyKnownMembers(range, RANGE_NAMES, "range");
1240
+ }
1241
+ if (version === 7) return anchorV7From(doc, rangeValue === void 0 ? void 0 : range);
1242
+ if (anchorMember(range, RANGE_NAMES, "start_seq") !== void 0 || anchorMember(range, RANGE_NAMES, "end_seq") !== void 0) {
1243
+ throw refused("a v1-v6 range names start_entry_id and end_entry_id, not sequence numbers");
1244
+ }
1245
+ if (anchorMember(doc, ANCHOR_NAMES, "previous_anchor_hash") !== void 0) {
1246
+ throw refused("only a v7 anchor carries previous_anchor_hash");
1247
+ }
1248
+ return {
1249
+ version,
1250
+ anchorId: stringMember(doc, ANCHOR_NAMES, "anchor_id"),
1251
+ subject: stringMember(doc, ANCHOR_NAMES, subjectName),
1252
+ chainHash: stringMember(doc, ANCHOR_NAMES, "chain_hash"),
1253
+ range: {
1254
+ startEntryId: stringMember(range, RANGE_NAMES, "start_entry_id"),
1255
+ endEntryId: stringMember(range, RANGE_NAMES, "end_entry_id")
1256
+ },
1257
+ entryCount: narrow(integerMember(doc, ANCHOR_NAMES, "entry_count")),
1258
+ signature: stringMember(doc, ANCHOR_NAMES, "signature"),
1259
+ signingKeyId: stringMember(doc, ANCHOR_NAMES, "signing_key_id"),
1260
+ createdAtMs: narrow(integerMember(doc, ANCHOR_NAMES, "created_at_ms")),
1261
+ previousAnchorId: stringMember(doc, ANCHOR_NAMES, "previous_anchor_id"),
1262
+ verifiedThrough: narrow(integerMember(doc, ANCHOR_NAMES, "verified_through"))
1263
+ };
1264
+ }
1265
+
1266
+ // src/anchorLoaders.ts
1267
+ async function loadAnchor(path) {
1268
+ const fs = await import('fs/promises');
1269
+ return parseAnchorText(new Uint8Array(await fs.readFile(path)));
1270
+ }
1271
+ async function loadExportedEntries(path) {
1272
+ const fs = await import('fs/promises');
1273
+ return parseEntriesText(new Uint8Array(await fs.readFile(path)));
1274
+ }
1275
+ var FORMAT_V4 = 4;
1276
+ var CHAIN_LEAF_DOMAIN_V4 = "aleutian.chain.leaf.v4:";
1277
+ var CHAIN_HASH_PREFIX_V4 = "aleutian.chain.v4:";
1278
+ var HEX_64 = /^[0-9a-f]{64}$/;
1279
+ var CONTROL = /[\u0000-\u001f\u007f]/;
1280
+ var LONE_SURROGATE2 = /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;
1281
+ var LEAF_DOMAIN_BYTES = utf8ToBytes(CHAIN_LEAF_DOMAIN_V4);
1282
+ function isLeafText(value) {
1283
+ if (typeof value !== "string" || value === "") return false;
1284
+ if (LONE_SURROGATE2.test(value) || CONTROL.test(value)) return false;
1285
+ if (value.length > 256) return false;
1286
+ return utf8ToBytes(value).length <= 256;
1287
+ }
1288
+ function isSalt(value) {
1289
+ return typeof value === "string" && HEX_64.test(value);
1290
+ }
1291
+ function hasTombstoneShapeV4(entryId, entryType, contentHash) {
1292
+ return entryType === "tombstone" || (entryId?.startsWith("tomb_") ?? false) || (contentHash?.startsWith("TOMBSTONE:") ?? false);
1293
+ }
1294
+ function u32(n) {
1295
+ return new Uint8Array([n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255]);
1296
+ }
1297
+ function concat(parts) {
1298
+ let size = 0;
1299
+ for (const p of parts) size += p.length;
1300
+ const out = new Uint8Array(size);
1301
+ let at = 0;
1302
+ for (const p of parts) {
1303
+ out.set(p, at);
1304
+ at += p.length;
1305
+ }
1306
+ return out;
1307
+ }
1308
+ function leafPreimageV4(entryId, entryType, contentHash, salt) {
1309
+ const id = utf8ToBytes(entryId);
1310
+ const type = utf8ToBytes(entryType);
1311
+ return concat([
1312
+ LEAF_DOMAIN_BYTES,
1313
+ u32(id.length),
1314
+ id,
1315
+ u32(type.length),
1316
+ type,
1317
+ utf8ToBytes(contentHash),
1318
+ utf8ToBytes(salt)
1319
+ ]);
1320
+ }
1321
+ function leafHashV4Sync(entryId, entryType, contentHash, salt) {
1322
+ return bytesToHex(sha512(leafPreimageV4(entryId, entryType, contentHash, salt)));
1323
+ }
1324
+ function chainHashV4Sync(previousHash, globalSeq, timestamp, leafHash) {
1325
+ return bytesToHex(
1326
+ sha512(
1327
+ utf8ToBytes(
1328
+ CHAIN_HASH_PREFIX_V4 + previousHash + "|" + globalSeq + "|" + timestamp + "|" + leafHash
1329
+ )
1330
+ )
1331
+ );
1332
+ }
1333
+
1334
+ // src/internal/constantTime.ts
1335
+ var _nodeTimingSafeEqual = null;
1336
+ try {
1337
+ const nodeCrypto = __require("crypto");
1338
+ if (typeof nodeCrypto.timingSafeEqual === "function") {
1339
+ _nodeTimingSafeEqual = nodeCrypto.timingSafeEqual;
1340
+ }
1341
+ } catch {
1342
+ }
1343
+ function constantTimeEqual(a, b) {
1344
+ if (a.length !== b.length) {
1345
+ let acc = 1;
1346
+ const n = Math.max(a.length, b.length);
1347
+ for (let i = 0; i < n; i++) {
1348
+ const ai = i < a.length ? a[i] : 0;
1349
+ const bi = i < b.length ? b[i] : 0;
1350
+ acc |= ai ^ bi;
1351
+ }
1352
+ return acc === 0;
1353
+ }
1354
+ if (_nodeTimingSafeEqual !== null) {
1355
+ return _nodeTimingSafeEqual(a, b);
1356
+ }
1357
+ let diff = 0;
1358
+ for (let i = 0; i < a.length; i++) {
1359
+ diff |= a[i] ^ b[i];
1360
+ }
1361
+ return diff === 0;
1362
+ }
1363
+
1364
+ // src/verify.ts
1365
+ var HEX_ENCODER = new TextEncoder();
1366
+ function chainHashEqual(a, b) {
1367
+ return constantTimeEqual(HEX_ENCODER.encode(a), HEX_ENCODER.encode(b));
1368
+ }
1369
+ var MAX_INT645 = 2n ** 63n - 1n;
1370
+ function exactInteger(value) {
1371
+ if (typeof value === "bigint") return value;
1372
+ if (typeof value === "number" && Number.isSafeInteger(value)) return BigInt(value);
1373
+ return null;
1374
+ }
1375
+ function isTombstoneEntry(entry) {
1376
+ return typeof entry.entryType === "string" && typeof entry.entryId === "string" && isTombstone(entry.entryType, entry.entryId);
1377
+ }
1378
+ function carries(entry, snake) {
1379
+ const present = entry.presentMembers;
1380
+ if (present !== void 0) return present.has(snake);
1381
+ const e = entry;
1382
+ switch (snake) {
1383
+ case "entry_id":
1384
+ return e.entryId !== void 0 && e.entryId !== "";
1385
+ case "entry_type":
1386
+ return e.entryType !== void 0 && e.entryType !== "";
1387
+ case "content_hash":
1388
+ return e.contentHash !== void 0 && e.contentHash !== "";
1389
+ case "run_id":
1390
+ return e.runId !== void 0 && e.runId !== "";
1391
+ case "sequence_num":
1392
+ return e.sequenceNum !== void 0 && e.sequenceNum !== 0;
1393
+ case "salt":
1394
+ return e.salt !== void 0;
1395
+ case "leaf_hash":
1396
+ return e.leafHash !== void 0;
1397
+ case "erased":
1398
+ return e.erased !== void 0;
1399
+ case "global_seq":
1400
+ return e.globalSeq !== void 0;
1401
+ default:
1402
+ return true;
1403
+ }
1404
+ }
1405
+ var V4_LIVE_ONLY = ["entry_id", "entry_type", "content_hash", "salt"];
1406
+ function v4ShapeProblem(entry, seq, hasPredecessor) {
1407
+ if (!carries(entry, "global_seq") || !carries(entry, "leaf_hash")) {
1408
+ return { field: "shape", detail: "a v4 entry carries global_seq and leaf_hash" };
1409
+ }
1410
+ if (entry.erased !== void 0 || carries(entry, "erased")) {
1411
+ if (entry.erased !== true) {
1412
+ return { field: "erased", detail: "a live v4 entry carries no erased member" };
1413
+ }
1414
+ for (const name of V4_LIVE_ONLY) {
1415
+ if (carries(entry, name)) {
1416
+ return {
1417
+ field: "shape",
1418
+ detail: "an erased v4 entry carries no entry_id, entry_type, content_hash or salt"
1419
+ };
1420
+ }
1421
+ }
1422
+ } else {
1423
+ for (const name of V4_LIVE_ONLY) {
1424
+ if (!carries(entry, name)) {
1425
+ return {
1426
+ field: "shape",
1427
+ detail: "a live v4 entry carries entry_id, entry_type, content_hash and salt"
1428
+ };
1429
+ }
1430
+ }
1431
+ if (!isLeafText(entry.entryId)) {
1432
+ return {
1433
+ field: "entryId",
1434
+ detail: "entryId must be 1 to 256 UTF-8 bytes with no control character"
1435
+ };
1436
+ }
1437
+ if (!isLeafText(entry.entryType)) {
1438
+ return {
1439
+ field: "entryType",
1440
+ detail: "entryType must be 1 to 256 UTF-8 bytes with no control character"
1441
+ };
1442
+ }
1443
+ if (!isChainDigest(entry.contentHash)) {
1444
+ return {
1445
+ field: "contentHash",
1446
+ detail: "a v4 contentHash must be exactly 128 lowercase hex characters"
1447
+ };
1448
+ }
1449
+ if (!isSalt(entry.salt)) {
1450
+ return { field: "salt", detail: "salt must be exactly 64 lowercase hex characters" };
1451
+ }
1452
+ }
1453
+ if (!isChainDigest(entry.leafHash)) {
1454
+ return { field: "leafHash", detail: "leafHash must be exactly 128 lowercase hex characters" };
1455
+ }
1456
+ if (seq === null || seq < 0n || seq > MAX_INT645) {
1457
+ return {
1458
+ field: "globalSeq",
1459
+ detail: "globalSeq must be an integer from 0 to 2^63 - 1 (a bigint beyond 2^53)"
1460
+ };
1461
+ }
1462
+ if (hasPredecessor !== seq > 0n) {
1463
+ return {
1464
+ field: "globalSeq",
1465
+ detail: hasPredecessor ? "an entry with a predecessor has a globalSeq above 0" : "a v4 entry with no predecessor has globalSeq 0: walk a segment from its predecessor hash"
1466
+ };
1467
+ }
1468
+ return null;
1469
+ }
1470
+ async function walkChain(entries, startHash, maxBreaks) {
1471
+ const breaks = [];
1472
+ const startMalformed = startHash !== "" && !isChainDigest(startHash);
1473
+ let previousHash = startMalformed ? null : startHash;
1474
+ let previousSeq = null;
1475
+ let tombstonesFound = 0;
1476
+ let lastTombstone = -1;
1477
+ const tombstonePositions = [];
1478
+ let erasedFound = 0;
1479
+ let seenV4 = false;
1480
+ const report = (position, entry, breakType, context, expectedHash = "") => {
1481
+ breaks.push({
1482
+ position,
1483
+ entryId: typeof entry.entryId === "string" ? entry.entryId : "",
1484
+ expectedHash,
1485
+ actualHash: isChainDigest(entry.chainHash) ? entry.chainHash : "",
1486
+ breakType,
1487
+ context
1488
+ });
1489
+ };
1490
+ for (let i = 0; i < entries.length; i++) {
1491
+ if (maxBreaks > 0 && breaks.length >= maxBreaks) {
1492
+ break;
1493
+ }
1494
+ const entry = entries[i];
1495
+ const seq = exactInteger(entry.globalSeq);
1496
+ const declared = entry.formatVersion;
1497
+ const entryFormat = declared === void 0 || typeof declared === "number" ? normalizeFormatVersion(declared) : Number.NaN;
1498
+ const isV4 = entryFormat === FORMAT_V4;
1499
+ const isV2orV3 = entryFormat === FORMAT_V2 || entryFormat === FORMAT_V3;
1500
+ const formatKnown = isV2orV3 || isV4;
1501
+ let crossFormat = null;
1502
+ if (isV2orV3 && seenV4) {
1503
+ crossFormat = "a v2 or v3 entry after a v4 entry: a chain never goes back from v4";
1504
+ } else if (isV2orV3 && (carries(entry, "salt") || carries(entry, "leaf_hash") || carries(entry, "erased"))) {
1505
+ crossFormat = "a v2 or v3 entry must not carry salt, leaf_hash or erased";
1506
+ } else if (isV4 && (carries(entry, "run_id") || carries(entry, "sequence_num"))) {
1507
+ crossFormat = "a v4 entry carries no run_id and no sequence_num";
1508
+ } else if (isV4 && hasTombstoneShapeV4(
1509
+ carries(entry, "entry_id") ? entry.entryId : void 0,
1510
+ carries(entry, "entry_type") ? entry.entryType : void 0,
1511
+ carries(entry, "content_hash") ? entry.contentHash : void 0
1512
+ )) {
1513
+ crossFormat = "a v4 entry has no tombstone type, tomb_ id or TOMBSTONE: content hash";
1514
+ }
1515
+ if (isV4) seenV4 = true;
1516
+ if (previousSeq !== null && seq !== null && seq !== previousSeq + 1n) {
1517
+ report(i, entry, "sequence_gap", {
1518
+ expectedSeq: (previousSeq + 1n).toString(),
1519
+ actualSeq: seq.toString()
1520
+ });
1521
+ }
1522
+ previousSeq = seq;
1523
+ if (!isChainDigest(entry.chainHash)) {
1524
+ report(i, entry, "invalid_field", {
1525
+ field: "chainHash",
1526
+ detail: "chainHash must be exactly 128 lowercase hex characters"
1527
+ });
1528
+ previousHash = null;
1529
+ continue;
1530
+ }
1531
+ if (isV4 && entry.erased === true) erasedFound++;
1532
+ const stop = (breakType, context) => {
1533
+ report(i, entry, breakType, context);
1534
+ previousHash = entry.chainHash;
1535
+ };
1536
+ if (crossFormat !== null) {
1537
+ stop("format_field_misuse", { detail: crossFormat });
1538
+ continue;
1539
+ }
1540
+ const unknownFormat = () => {
1541
+ const shown = typeof declared === "number" && Number.isSafeInteger(declared) ? String(declared) : "(not an integer)";
1542
+ stop("unknown_format", {
1543
+ detail: `chain hash format version ${shown} is not implemented by this build`
1544
+ });
1545
+ };
1546
+ const runFieldMisuse = entryFormat === FORMAT_V3 && Boolean(entry.runId || entry.sequenceNum);
1547
+ const fieldMisuse = () => stop("format_field_misuse", {
1548
+ detail: "a v3 entry must not carry runId or sequenceNum; neither is bound into its hash"
1549
+ });
1550
+ const seqInvalid = seq === null || seq < 0n || seq > MAX_INT645;
1551
+ const invalidSeq = () => stop("invalid_field", {
1552
+ field: "globalSeq",
1553
+ detail: "globalSeq must be an integer from 0 to 2^63 - 1 (a bigint beyond 2^53)"
1554
+ });
1555
+ const invalidStart = () => stop("invalid_field", {
1556
+ field: "previousHash",
1557
+ detail: "previousHash must be empty or exactly 128 lowercase hex characters"
1558
+ });
1559
+ if (!isV4 && isTombstoneEntry(entry)) {
1560
+ tombstonesFound++;
1561
+ lastTombstone = i;
1562
+ tombstonePositions.push(i);
1563
+ if (!validateTombstoneContentHash(entry.contentHash)) {
1564
+ stop("invalid_tombstone", {
1565
+ detail: 'a tombstone contentHash must be "TOMBSTONE:" + 64 lowercase hex'
1566
+ });
1567
+ } else if (!formatKnown) {
1568
+ unknownFormat();
1569
+ } else if (runFieldMisuse) {
1570
+ fieldMisuse();
1571
+ } else if (seqInvalid) {
1572
+ invalidSeq();
1573
+ } else if (i === 0 && startMalformed) {
1574
+ invalidStart();
1575
+ } else {
1576
+ previousHash = entry.chainHash;
1577
+ }
1578
+ continue;
1579
+ }
1580
+ if (!isChainTimestamp(entry.timestamp)) {
1581
+ stop("invalid_timestamp", {
1582
+ detail: "timestamp must be YYYY-MM-DDTHH:MM:SS.ffffffZ (UTC, six digits)"
1583
+ });
1584
+ continue;
1585
+ }
1586
+ if (!formatKnown) {
1587
+ unknownFormat();
1588
+ continue;
1589
+ }
1590
+ if (runFieldMisuse) {
1591
+ fieldMisuse();
1592
+ continue;
1593
+ }
1594
+ if (i === 0 && startMalformed) {
1595
+ invalidStart();
1596
+ continue;
1597
+ }
1598
+ if (isV4) {
1599
+ const problem = v4ShapeProblem(entry, seq, i > 0 || startHash !== "");
1600
+ if (problem !== null) {
1601
+ stop("invalid_field", problem);
1602
+ continue;
1603
+ }
1604
+ if (previousHash === null) {
1605
+ previousHash = entry.chainHash;
1606
+ continue;
1607
+ }
1608
+ const storedLeaf = entry.leafHash;
1609
+ const leaf = entry.erased === true ? storedLeaf : leafHashV4Sync(entry.entryId, entry.entryType, entry.contentHash, entry.salt);
1610
+ const expectedHash2 = chainHashV4Sync(previousHash, seq.toString(), entry.timestamp, leaf);
1611
+ if (!chainHashEqual(leaf, storedLeaf)) {
1612
+ report(i, entry, "hash_mismatch", { field: "leafHash" }, expectedHash2);
1613
+ } else if (!chainHashEqual(entry.chainHash, expectedHash2)) {
1614
+ report(i, entry, "hash_mismatch", {}, expectedHash2);
1615
+ }
1616
+ previousHash = expectedHash2;
1617
+ continue;
1618
+ }
1619
+ if (!isChainDigest(entry.contentHash) && !(typeof entry.contentHash === "string" && validateTombstoneContentHash(entry.contentHash))) {
1620
+ stop("invalid_field", {
1621
+ field: "contentHash",
1622
+ detail: 'contentHash must be 128 lowercase hex characters, or a tombstone value ("TOMBSTONE:" + 64 lowercase hex)'
1623
+ });
1624
+ continue;
1625
+ }
1626
+ if (seqInvalid) {
1627
+ invalidSeq();
1628
+ continue;
1629
+ }
1630
+ if (entryFormat === FORMAT_V2) {
1631
+ if (typeof entry.runId !== "string" || entry.runId === "") {
1632
+ stop("invalid_field", {
1633
+ field: "runId",
1634
+ detail: "a v2 entry's runId must not be empty"
1635
+ });
1636
+ continue;
1637
+ }
1638
+ const sequenceNum = entry.sequenceNum;
1639
+ if (typeof sequenceNum !== "number" || !Number.isSafeInteger(sequenceNum) || sequenceNum < 0) {
1640
+ stop("invalid_field", {
1641
+ field: "sequenceNum",
1642
+ detail: "a v2 entry's sequenceNum must be a non-negative integer"
1643
+ });
1644
+ continue;
1645
+ }
1646
+ }
1647
+ if (previousHash === null) {
1648
+ previousHash = entry.chainHash;
1649
+ continue;
1650
+ }
1651
+ const expectedHash = entryFormat === FORMAT_V2 ? await computeChainHash(
1652
+ previousHash,
1653
+ entry.runId,
1654
+ entry.sequenceNum,
1655
+ entry.timestamp,
1656
+ entry.contentHash
1657
+ ) : await computeChainHashV3(previousHash, seq, entry.timestamp, entry.contentHash);
1658
+ if (!chainHashEqual(entry.chainHash, expectedHash)) {
1659
+ report(i, entry, "hash_mismatch", {}, expectedHash);
1660
+ }
1661
+ previousHash = expectedHash;
1662
+ }
1663
+ return { breaks, tombstonesFound, lastTombstone, tombstonePositions, erasedFound };
1664
+ }
1665
+ function walkResult(walk, processed, durationMs, anchorOutcome) {
1666
+ const entriesNotVerified = walk.lastTombstone + 1;
1667
+ return {
1668
+ valid: walk.breaks.length === 0,
1669
+ entriesVerified: processed - entriesNotVerified,
1670
+ entriesProcessed: processed,
1671
+ tombstonesFound: walk.tombstonesFound,
1672
+ erasedFound: walk.erasedFound,
1673
+ lastTombstone: walk.lastTombstone,
1674
+ entriesNotVerified,
1675
+ breaks: walk.breaks,
1676
+ durationMs,
1677
+ ...anchorOutcome === void 0 ? {} : { anchorOutcome }
1678
+ };
1679
+ }
1680
+ async function bindOutcome(anchor, entries, chainValid, previousAnchorHash) {
1681
+ if (!chainValid) return "chain_broken";
1682
+ const tip = entries[entries.length - 1];
1683
+ if (entries[0].entryId !== anchor.range.startEntryId) return "range_start_mismatch";
1684
+ if (tip.entryId !== anchor.range.endEntryId) return "range_end_mismatch";
1685
+ if (BigInt(anchor.entryCount) !== BigInt(entries.length)) return "height_mismatch";
1686
+ const namesSeed = anchor.previousAnchorId === SEED_ANCHOR_ID;
1687
+ const usesSeed = previousAnchorHash === SEED_ANCHOR_HASH;
1688
+ if (namesSeed !== usesSeed) return "predecessor_mismatch";
1689
+ const expected = await computeAnchorChainHash(
1690
+ previousAnchorHash,
1691
+ anchor.subject,
1692
+ anchor.range.startEntryId,
1693
+ anchor.range.endEntryId,
1694
+ tip.chainHash
1695
+ );
1696
+ return chainHashEqual(expected, anchor.chainHash) ? "bound" : "head_mismatch";
1697
+ }
1698
+ function bindOutcomeV7(anchor, entries, chainValid, expect) {
1699
+ if (!chainValid) return { outcome: "chain_broken", tipIndex: -1 };
1700
+ const startSeq = BigInt(anchor.range.startSeq);
1701
+ const endSeq = BigInt(anchor.range.endSeq);
1702
+ const firstSeq = exactInteger(entries[0].globalSeq);
1703
+ if (firstSeq === null || firstSeq !== startSeq) {
1704
+ return { outcome: "range_start_mismatch", tipIndex: -1 };
1705
+ }
1706
+ const offset = endSeq - startSeq;
1707
+ const tipIndex = offset < BigInt(entries.length) ? Number(offset) : -1;
1708
+ if (tipIndex < 0 || exactInteger(entries[tipIndex].globalSeq) !== endSeq) {
1709
+ return { outcome: "height_mismatch", tipIndex: -1 };
1710
+ }
1711
+ const expectedPrev = expect.previousAnchorHash ?? "";
1712
+ if (expectedPrev !== "" && !chainHashEqual(expectedPrev, anchor.previousAnchorHash)) {
1713
+ return { outcome: "predecessor_mismatch", tipIndex };
1714
+ }
1715
+ const expectedSubject = expect.subject ?? "";
1716
+ if (expectedSubject !== "" && expectedSubject !== anchor.subject) {
1717
+ return { outcome: "tenant_mismatch", tipIndex };
1718
+ }
1719
+ const expected = anchorChainHashV7Sync(
1720
+ anchor.previousAnchorHash,
1721
+ anchor.subject,
1722
+ startSeq.toString(),
1723
+ endSeq.toString(),
1724
+ entries[tipIndex].chainHash
1725
+ );
1726
+ return {
1727
+ outcome: chainHashEqual(expected, anchor.chainHash) ? "bound" : "head_mismatch",
1728
+ tipIndex
1729
+ };
1730
+ }
1731
+ async function verifyChainOffline(entries, options = {}) {
1732
+ if (entries.length === 0) {
1733
+ throw new ChainVerificationError("entries array cannot be empty");
1734
+ }
1735
+ const startHash = options.previousHash ?? "";
1736
+ if (options.anchor && startHash !== "") {
1737
+ throw new ChainVerificationError(
1738
+ "an anchor cannot be bound to a segment: omit previousHash and pass the chain from its first entry"
1739
+ );
1740
+ }
1741
+ if (options.anchor) {
1742
+ checkAnchorWellFormed(options.anchor);
1743
+ }
1744
+ const maxEntries = options.maxEntries ?? 0;
1745
+ const entriesToProcess = maxEntries > 0 && entries.length > maxEntries ? entries.slice(0, maxEntries) : entries;
1746
+ const startTime = performance.now();
1747
+ const walk = await walkChain(entriesToProcess, startHash, options.maxBreaks ?? 0);
1748
+ const breaks = walk.breaks;
1749
+ const lastEntry = entriesToProcess[entriesToProcess.length - 1];
1750
+ let anchorOutcome;
1751
+ if (options.anchor) {
1752
+ const anchor = options.anchor;
1753
+ let prevAnchorHash = options.previousAnchorHash ?? "";
1754
+ if (prevAnchorHash === "" && anchor.previousAnchorId === SEED_ANCHOR_ID) {
1755
+ prevAnchorHash = SEED_ANCHOR_HASH;
1756
+ }
1757
+ if (anchor.version === 7) {
1758
+ const expectedPrev = options.previousAnchorHash ?? "";
1759
+ anchorOutcome = expectedPrev !== "" && !isChainDigest(expectedPrev) ? breaks.length === 0 ? "predecessor_mismatch" : "chain_broken" : bindOutcomeV7(anchor, entriesToProcess, breaks.length === 0, {
1760
+ previousAnchorHash: expectedPrev
1761
+ }).outcome;
1762
+ } else if (!isChainDigest(prevAnchorHash)) {
1763
+ anchorOutcome = breaks.length === 0 ? "predecessor_mismatch" : "chain_broken";
1764
+ } else {
1765
+ anchorOutcome = await bindOutcome(
1766
+ anchor,
1767
+ entriesToProcess,
1768
+ breaks.length === 0,
1769
+ prevAnchorHash
1770
+ );
1771
+ }
1772
+ if (anchorOutcome !== "bound" && anchorOutcome !== "chain_broken") {
1773
+ breaks.push({
1774
+ position: entriesToProcess.length - 1,
1775
+ entryId: typeof lastEntry.entryId === "string" ? lastEntry.entryId : "",
1776
+ expectedHash: "",
1777
+ actualHash: "",
1778
+ breakType: "anchor_mismatch",
1779
+ context: { outcome: anchorOutcome }
1780
+ });
1781
+ }
1782
+ } else {
1783
+ const expectedLastHash = options.expectedLastHash ?? "";
1784
+ const tipHash = isChainDigest(lastEntry.chainHash) ? lastEntry.chainHash : "";
1785
+ if (expectedLastHash && !chainHashEqual(tipHash, expectedLastHash)) {
1786
+ breaks.push({
1787
+ position: entriesToProcess.length - 1,
1788
+ entryId: typeof lastEntry.entryId === "string" ? lastEntry.entryId : "",
1789
+ expectedHash: expectedLastHash,
1790
+ actualHash: tipHash,
1791
+ breakType: "anchor_mismatch",
1792
+ context: {}
1793
+ });
1794
+ }
1795
+ }
1796
+ return walkResult(walk, entriesToProcess.length, performance.now() - startTime, anchorOutcome);
1797
+ }
1798
+
1799
+ // src/manifestFreshness.ts
1800
+ var MANIFEST_FRESHNESS_MAX_AGE_DAYS = 30;
1801
+ var MS_PER_DAY = 24 * 60 * 60 * 1e3;
1802
+ function evaluateManifestFreshness(version, generatedAtMs, nowMs, lastSeenVersion) {
1803
+ const ageMs = nowMs - generatedAtMs;
1804
+ const ageDays = ageMs > 0 ? Math.floor(ageMs / MS_PER_DAY) : 0;
1805
+ return {
1806
+ version,
1807
+ generatedAtMs,
1808
+ ageDays,
1809
+ stale: ageDays > MANIFEST_FRESHNESS_MAX_AGE_DAYS,
1810
+ rollbackDetected: lastSeenVersion >= 0
1811
+ };
1812
+ }
1813
+ function manifestGeneratedDate(f) {
1814
+ return new Date(f.generatedAtMs).toISOString().slice(0, 10);
1815
+ }
1816
+ function manifestRevocationNote(f) {
1817
+ let note = `Revocation checked against trust manifest v${f.version} dated ${manifestGeneratedDate(f)} \u2014 a key revoked after that date is not reflected.`;
1818
+ if (f.stale) {
1819
+ note += ` [WARNING: manifest is ${f.ageDays} days old]`;
1820
+ }
1821
+ return note;
1822
+ }
1823
+
1824
+ // src/keyId.ts
1825
+ var KEYID_DOMAIN = "proof.keyid.v1:";
1826
+ var MLDSA65_PUBLIC_KEY_SIZE = 1952;
1827
+ var MLDSA65_ALGORITHM_IDENTIFIER = hexToUint8Array("300b0609608648016503040312");
1828
+ function derLength(n) {
1829
+ if (n < 128) return Uint8Array.of(n);
1830
+ const body = [];
1831
+ for (let v = n; v > 0; v = Math.floor(v / 256)) body.unshift(v % 256);
1832
+ return Uint8Array.of(128 | body.length, ...body);
1833
+ }
1834
+ function concat2(...parts) {
1835
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
1836
+ let at = 0;
1837
+ for (const p of parts) {
1838
+ out.set(p, at);
1839
+ at += p.length;
1840
+ }
1841
+ return out;
1842
+ }
1843
+ function mldsa65Spki(publicKey) {
1844
+ if (publicKey.length !== MLDSA65_PUBLIC_KEY_SIZE) {
1845
+ throw new Error(
1846
+ `an ML-DSA-65 public key is ${MLDSA65_PUBLIC_KEY_SIZE} bytes, got ${publicKey.length}`
1847
+ );
1848
+ }
1849
+ const bitString = concat2(
1850
+ Uint8Array.of(3),
1851
+ derLength(publicKey.length + 1),
1852
+ Uint8Array.of(0),
1853
+ publicKey
1854
+ );
1855
+ const body = concat2(MLDSA65_ALGORITHM_IDENTIFIER, bitString);
1856
+ return concat2(Uint8Array.of(48), derLength(body.length), body);
1857
+ }
1858
+ async function keyIdMLDSA65(publicKey) {
1859
+ const input = concat2(new TextEncoder().encode(KEYID_DOMAIN), mldsa65Spki(publicKey));
1860
+ return (await sha512HexRaw(input)).slice(0, 32);
1861
+ }
1862
+
1863
+ // src/sarEntries.ts
1864
+ var _CONCRETE_TAG_EXHAUSTIVE_PIN = {
1865
+ sar_intent: true,
1866
+ sar_acknowledged: true,
1867
+ sar_fulfilled: true,
1868
+ sar_rejected: true,
1869
+ sar_extended: true
1870
+ };
1871
+ function _buildImmutableTagSet(tags) {
1872
+ const s = new Set(tags);
1873
+ const throwMutation = (op) => {
1874
+ throw new TypeError(
1875
+ `CONCRETE_SAR_ENTRY_TYPE_TAGS is immutable \u2014 refusing ${op}()`
1876
+ );
1877
+ };
1878
+ Object.defineProperty(s, "add", {
1879
+ value: () => throwMutation("add"),
1880
+ writable: false,
1881
+ configurable: false
1882
+ });
1883
+ Object.defineProperty(s, "delete", {
1884
+ value: () => throwMutation("delete"),
1885
+ writable: false,
1886
+ configurable: false
1887
+ });
1888
+ Object.defineProperty(s, "clear", {
1889
+ value: () => throwMutation("clear"),
1890
+ writable: false,
1891
+ configurable: false
1892
+ });
1893
+ return Object.freeze(s);
1894
+ }
1895
+ _buildImmutableTagSet(
1896
+ Object.keys(_CONCRETE_TAG_EXHAUSTIVE_PIN)
1897
+ );
1898
+
1899
+ // src/canonicalV3.ts
1900
+ var DOMAIN_PREFIX_V3 = "aleutian.chain.entry.v3:";
1901
+ new TextEncoder().encode(
1902
+ DOMAIN_PREFIX_V3
1903
+ );
1904
+
1905
+ // src/proofBundleReader.ts
1906
+ var BUNDLE_FORMAT_V1 = "aleutian.proof.bundle.v1";
1907
+ var BUNDLE_FORMAT_V2 = "aleutian.proof.bundle.v2";
1908
+ var BUNDLE_MAX_DEPTH = 8;
1909
+ var LBRACE = 123;
1910
+ var RBRACE = 125;
1911
+ var LBRACKET = 91;
1912
+ var RBRACKET = 93;
1913
+ var QUOTE2 = 34;
1914
+ var COMMA = 44;
1915
+ var COLON = 58;
1916
+ var ENTRY_MEMBERS = [
1917
+ "entry_id",
1918
+ "entry_type",
1919
+ "global_seq",
1920
+ "previous_hash",
1921
+ "timestamp",
1922
+ "content_hash",
1923
+ "chain_hash"
1924
+ ];
1925
+ var ENTRY_INDEX = new Map(ENTRY_MEMBERS.map((name, i) => [name, i]));
1926
+ var ENTRY_ALL = (1 << ENTRY_MEMBERS.length) - 1;
1927
+ var FORMAT_MAX_RAW = 6 * Math.max(BUNDLE_FORMAT_V1.length, BUNDLE_FORMAT_V2.length) + 2;
1928
+ var BundleReader = class extends StrictJsonScanner {
1929
+ constructor(bytes, limits) {
1930
+ super(bytes, BUNDLE_MAX_DEPTH);
1931
+ this.limits = limits;
1932
+ }
1933
+ values = 0;
1934
+ valueLimitHit = false;
1935
+ duplicateKey = false;
1936
+ topIsObject = false;
1937
+ formatOk = false;
1938
+ formatVersion = 1;
1939
+ /**
1940
+ * Whether an entry carried a v2-only member (`salt`, `leaf_hash`), and
1941
+ * whether one lacked either. Members may come in any order, `format` last,
1942
+ * so which of the two is a structure violation is decided at the end.
1943
+ */
1944
+ sawV2Member = false;
1945
+ missedV2Member = false;
1946
+ structureBad = false;
1947
+ countExceeded = false;
1948
+ chainCount = 0;
1949
+ entryCount = 0;
1950
+ checkpointCount = 0;
1951
+ /** False once the outcome cannot be readable: nothing more is built. */
1952
+ building = true;
1953
+ anyMember = (_name, depth) => this.any(depth);
1954
+ anyElement = (depth) => this.any(depth);
1955
+ /** Scan the whole document; the chains, while still building. */
1956
+ document() {
1957
+ this.skipWhitespace();
1958
+ const chains = this.root();
1959
+ this.expectEnd();
1960
+ return chains;
1961
+ }
1962
+ /** The lowest-numbered §9.1 reason after positions 1 and 2, or the chains. */
1963
+ outcome(chains) {
1964
+ const v2 = this.formatOk && this.formatVersion === 2;
1965
+ if (this.valueLimitHit || this.values > (v2 ? this.v2ValueLimit : this.limits.values)) {
1966
+ return { ok: false, reason: "too-large" };
1967
+ }
1968
+ if (this.duplicateKey) return { ok: false, reason: "duplicate-key" };
1969
+ if (this.topIsObject && !this.formatOk) return { ok: false, reason: "format" };
1970
+ if (this.structureBad) return { ok: false, reason: "structure" };
1971
+ if (v2 ? this.missedV2Member : this.sawV2Member) return { ok: false, reason: "structure" };
1972
+ if (this.countExceeded || chains === void 0)
1973
+ return { ok: false, reason: "too-large" };
1974
+ return { ok: true, format: this.formatVersion, chains };
1975
+ }
1976
+ /** §16 §5's value limit (17,000,000); v1's when none is given. */
1977
+ get v2ValueLimit() {
1978
+ return this.limits.valuesV2 ?? this.limits.values;
1979
+ }
1980
+ /**
1981
+ * Past this many values no outcome can be readable under either format, so
1982
+ * nothing more is built or kept (the format may come last).
1983
+ */
1984
+ get valueCutoff() {
1985
+ return Math.max(this.limits.values, this.v2ValueLimit);
1986
+ }
1987
+ /** Whether §4 is still being checked: no structure violation, under the value limit. */
1988
+ get checking() {
1989
+ return !this.structureBad && !this.valueLimitHit;
1990
+ }
1991
+ violate() {
1992
+ this.structureBad = true;
1993
+ this.building = false;
1994
+ }
1995
+ /** A value begins: count it (§5). Past the higher of the two formats' limits, stop building. */
1996
+ begin() {
1997
+ this.values++;
1998
+ if (!this.valueLimitHit && this.values > this.valueCutoff) {
1999
+ this.valueLimitHit = true;
2000
+ this.building = false;
2001
+ }
2002
+ }
2003
+ // ---------------------------------------------------------------------------
2004
+ // Grammar only
2005
+ // ---------------------------------------------------------------------------
2006
+ /** Any value, checked against §3 only (its names still tracked). */
2007
+ any(depth) {
2008
+ this.begin();
2009
+ this.skipBody(depth);
2010
+ }
2011
+ /** The value at the cursor, already counted, checked against §3 only. */
2012
+ skipBody(depth) {
2013
+ const c = this.peek();
2014
+ if (c === LBRACE) this.object(depth, this.anyMember);
2015
+ else if (c === LBRACKET) this.array(depth, this.anyElement);
2016
+ else if (c === QUOTE2) this.skipString();
2017
+ else if (c === 116) this.literal("true");
2018
+ else if (c === 102) this.literal("false");
2019
+ else if (c === 110) this.literal("null");
2020
+ else this.skipNumber();
2021
+ }
2022
+ /**
2023
+ * An object: grammar, depth and duplicate names (until the value limit).
2024
+ * `member` reads each first-seen member's value; a repeated name's value is
2025
+ * only scanned, since the outcome is already duplicate-key or worse.
2026
+ */
2027
+ object(depth, member) {
2028
+ if (depth > this.maxDepth) this.fail();
2029
+ this.pos++;
2030
+ this.skipWhitespace();
2031
+ if (this.peek() === RBRACE) {
2032
+ this.pos++;
2033
+ return;
2034
+ }
2035
+ let names;
2036
+ for (; ; ) {
2037
+ this.skipWhitespace();
2038
+ if (this.peek() !== QUOTE2) this.fail();
2039
+ if (this.valueLimitHit) {
2040
+ names = void 0;
2041
+ this.skipString();
2042
+ this.colon();
2043
+ this.any(depth + 1);
2044
+ } else {
2045
+ const name = this.readString();
2046
+ names ??= /* @__PURE__ */ new Set();
2047
+ const fresh = !names.has(name);
2048
+ if (fresh) {
2049
+ names.add(name);
2050
+ } else {
2051
+ this.duplicateKey = true;
2052
+ this.building = false;
2053
+ }
2054
+ this.colon();
2055
+ if (fresh) member(name, depth + 1);
2056
+ else this.any(depth + 1);
2057
+ }
2058
+ this.skipWhitespace();
2059
+ const c = this.peek();
2060
+ if (c === COMMA) {
2061
+ this.pos++;
2062
+ continue;
2063
+ }
2064
+ if (c === RBRACE) {
2065
+ this.pos++;
2066
+ return;
2067
+ }
2068
+ this.fail();
2069
+ }
2070
+ }
2071
+ /** The colon after a member name, with the whitespace around it. */
2072
+ colon() {
2073
+ this.skipWhitespace();
2074
+ this.expect(COLON);
2075
+ this.skipWhitespace();
2076
+ }
2077
+ /** An array: grammar and depth; `element` reads each element. Returns the length. */
2078
+ array(depth, element) {
2079
+ if (depth > this.maxDepth) this.fail();
2080
+ this.pos++;
2081
+ this.skipWhitespace();
2082
+ if (this.peek() === RBRACKET) {
2083
+ this.pos++;
2084
+ return 0;
2085
+ }
2086
+ let n = 0;
2087
+ for (; ; ) {
2088
+ this.skipWhitespace();
2089
+ element(depth + 1);
2090
+ n++;
2091
+ this.skipWhitespace();
2092
+ const c = this.peek();
2093
+ if (c === COMMA) {
2094
+ this.pos++;
2095
+ continue;
2096
+ }
2097
+ if (c === RBRACKET) {
2098
+ this.pos++;
2099
+ return n;
2100
+ }
2101
+ this.fail();
2102
+ }
2103
+ }
2104
+ // ---------------------------------------------------------------------------
2105
+ // §4 positions. Each begins by counting its value; when §4 is no longer
2106
+ // checked, it only scans. A wrong JSON type is a structure violation.
2107
+ // ---------------------------------------------------------------------------
2108
+ /** Scan a value of the wrong type for its position. */
2109
+ wrongType(depth) {
2110
+ this.violate();
2111
+ this.skipBody(depth);
2112
+ }
2113
+ /** §4.1: the top-level object. */
2114
+ root() {
2115
+ this.begin();
2116
+ if (this.peek() !== LBRACE) {
2117
+ this.wrongType(1);
2118
+ return void 0;
2119
+ }
2120
+ this.topIsObject = true;
2121
+ let chains;
2122
+ let hasChains = false;
2123
+ this.object(1, (name, depth) => {
2124
+ if (name === "format") this.format(depth);
2125
+ else if (!this.checking) this.any(depth);
2126
+ else if (name === "chains") {
2127
+ hasChains = true;
2128
+ chains = this.chains(depth);
2129
+ } else {
2130
+ this.violate();
2131
+ this.any(depth);
2132
+ }
2133
+ });
2134
+ if (this.checking && !hasChains) this.violate();
2135
+ return this.building ? chains : void 0;
2136
+ }
2137
+ /** `format`: judged even after a structure violation (position 5 outranks 6). */
2138
+ format(depth) {
2139
+ this.begin();
2140
+ if (this.valueLimitHit || this.peek() !== QUOTE2) {
2141
+ this.building = false;
2142
+ this.skipBody(depth);
2143
+ return;
2144
+ }
2145
+ const start = this.pos;
2146
+ this.skipString();
2147
+ if (this.pos - start <= FORMAT_MAX_RAW) {
2148
+ this.pos = start;
2149
+ const format = this.readString();
2150
+ if (format === BUNDLE_FORMAT_V1) {
2151
+ this.formatOk = true;
2152
+ this.formatVersion = 1;
2153
+ } else if (format === BUNDLE_FORMAT_V2) {
2154
+ this.formatOk = true;
2155
+ this.formatVersion = 2;
2156
+ }
2157
+ }
2158
+ if (!this.formatOk) this.building = false;
2159
+ }
2160
+ /** A string member or checkpoint text; undefined unless building. */
2161
+ str(depth) {
2162
+ this.begin();
2163
+ if (!this.checking) {
2164
+ this.skipBody(depth);
2165
+ return void 0;
2166
+ }
2167
+ if (this.peek() !== QUOTE2) {
2168
+ this.wrongType(depth);
2169
+ return void 0;
2170
+ }
2171
+ if (!this.building) {
2172
+ this.skipString();
2173
+ return void 0;
2174
+ }
2175
+ return this.readString();
2176
+ }
2177
+ /** §4.1 `chains`. */
2178
+ chains(depth) {
2179
+ this.begin();
2180
+ if (!this.checking) {
2181
+ this.skipBody(depth);
2182
+ return void 0;
2183
+ }
2184
+ if (this.peek() !== LBRACKET) {
2185
+ this.wrongType(depth);
2186
+ return void 0;
2187
+ }
2188
+ let out = this.building ? [] : void 0;
2189
+ this.array(depth, (d) => {
2190
+ if (!this.checking) return this.any(d);
2191
+ if (++this.chainCount > this.limits.chains) this.overCount();
2192
+ const chain = this.chain(d);
2193
+ if (this.building && out !== void 0 && chain !== void 0) out.push(chain);
2194
+ else out = void 0;
2195
+ });
2196
+ return this.building ? out : void 0;
2197
+ }
2198
+ overCount() {
2199
+ this.countExceeded = true;
2200
+ this.building = false;
2201
+ }
2202
+ /** §4.2 a chain. */
2203
+ chain(depth) {
2204
+ this.begin();
2205
+ if (!this.checking) {
2206
+ this.skipBody(depth);
2207
+ return void 0;
2208
+ }
2209
+ if (this.peek() !== LBRACE) {
2210
+ this.wrongType(depth);
2211
+ return void 0;
2212
+ }
2213
+ let chainId;
2214
+ let entries;
2215
+ let checkpoints;
2216
+ let seen = 0;
2217
+ this.object(depth, (name, d) => {
2218
+ if (!this.checking) return this.any(d);
2219
+ if (name === "chain_id") {
2220
+ seen |= 1;
2221
+ chainId = this.str(d);
2222
+ } else if (name === "entries") {
2223
+ seen |= 2;
2224
+ entries = this.entries(d);
2225
+ } else if (name === "checkpoints") {
2226
+ seen |= 4;
2227
+ checkpoints = this.checkpoints(d);
2228
+ } else {
2229
+ this.violate();
2230
+ this.any(d);
2231
+ }
2232
+ });
2233
+ if (this.checking && seen !== 7) this.violate();
2234
+ if (!this.building || chainId === void 0 || entries === void 0 || checkpoints === void 0) {
2235
+ return void 0;
2236
+ }
2237
+ return { chainId, entries, checkpoints };
2238
+ }
2239
+ /** §4.2 `entries`: not empty. */
2240
+ entries(depth) {
2241
+ this.begin();
2242
+ if (!this.checking) {
2243
+ this.skipBody(depth);
2244
+ return void 0;
2245
+ }
2246
+ if (this.peek() !== LBRACKET) {
2247
+ this.wrongType(depth);
2248
+ return void 0;
2249
+ }
2250
+ let out = this.building ? [] : void 0;
2251
+ const n = this.array(depth, (d) => {
2252
+ if (!this.checking) return this.any(d);
2253
+ if (++this.entryCount > this.limits.entries) this.overCount();
2254
+ const entry = this.entry(d);
2255
+ if (this.building && out !== void 0 && entry !== void 0) out.push(entry);
2256
+ else out = void 0;
2257
+ });
2258
+ if (this.checking && n === 0) this.violate();
2259
+ return this.building ? out : void 0;
2260
+ }
2261
+ /** §4.2 `checkpoints`: strings. */
2262
+ checkpoints(depth) {
2263
+ this.begin();
2264
+ if (!this.checking) {
2265
+ this.skipBody(depth);
2266
+ return void 0;
2267
+ }
2268
+ if (this.peek() !== LBRACKET) {
2269
+ this.wrongType(depth);
2270
+ return void 0;
2271
+ }
2272
+ let out = this.building ? [] : void 0;
2273
+ this.array(depth, (d) => {
2274
+ if (!this.checking) return this.any(d);
2275
+ if (++this.checkpointCount > this.limits.checkpoints) this.overCount();
2276
+ const text = this.str(d);
2277
+ if (this.building && out !== void 0 && text !== void 0) out.push(text);
2278
+ else out = void 0;
2279
+ });
2280
+ return this.building ? out : void 0;
2281
+ }
2282
+ /** §4.3 an entry. */
2283
+ entry(depth) {
2284
+ this.begin();
2285
+ if (!this.checking) {
2286
+ this.skipBody(depth);
2287
+ return void 0;
2288
+ }
2289
+ if (this.peek() !== LBRACE) {
2290
+ this.wrongType(depth);
2291
+ return void 0;
2292
+ }
2293
+ const fields = [];
2294
+ let seen = 0;
2295
+ let recordSignature;
2296
+ let disclosed;
2297
+ let salt;
2298
+ let leafHash;
2299
+ let v2Seen = 0;
2300
+ this.object(depth, (name, d) => {
2301
+ if (!this.checking) return this.any(d);
2302
+ const i = ENTRY_INDEX.get(name);
2303
+ if (i !== void 0) {
2304
+ seen |= 1 << i;
2305
+ fields[i] = this.str(d);
2306
+ } else if (name === "salt") {
2307
+ v2Seen |= 1;
2308
+ salt = this.str(d);
2309
+ } else if (name === "leaf_hash") {
2310
+ v2Seen |= 2;
2311
+ leafHash = this.str(d);
2312
+ } else if (name === "record_signature") {
2313
+ const p = this.pair(d, "key_id", "signature");
2314
+ if (p !== void 0) recordSignature = { keyId: p[0], signature: p[1] };
2315
+ } else if (name === "disclosed") {
2316
+ const p = this.pair(d, "content", "nonce");
2317
+ if (p !== void 0) disclosed = { content: p[0], nonce: p[1] };
2318
+ } else {
2319
+ this.violate();
2320
+ this.any(d);
2321
+ }
2322
+ });
2323
+ if (this.checking && seen !== ENTRY_ALL) this.violate();
2324
+ if (v2Seen !== 0) this.sawV2Member = true;
2325
+ if (v2Seen !== 3) this.missedV2Member = true;
2326
+ if (!this.building) return void 0;
2327
+ const [
2328
+ entryId,
2329
+ entryType,
2330
+ globalSeq,
2331
+ previousHash,
2332
+ timestamp,
2333
+ contentHash,
2334
+ chainHash
2335
+ ] = fields;
2336
+ return {
2337
+ entryId,
2338
+ entryType,
2339
+ globalSeq,
2340
+ previousHash,
2341
+ timestamp,
2342
+ contentHash,
2343
+ chainHash,
2344
+ ...salt === void 0 ? {} : { salt },
2345
+ ...leafHash === void 0 ? {} : { leafHash },
2346
+ recordSignature,
2347
+ disclosed
2348
+ };
2349
+ }
2350
+ /** `record_signature` or `disclosed`: exactly its two string members (§4.3). */
2351
+ pair(depth, a, b) {
2352
+ this.begin();
2353
+ if (!this.checking) {
2354
+ this.skipBody(depth);
2355
+ return void 0;
2356
+ }
2357
+ if (this.peek() !== LBRACE) {
2358
+ this.wrongType(depth);
2359
+ return void 0;
2360
+ }
2361
+ let x;
2362
+ let y;
2363
+ let seen = 0;
2364
+ this.object(depth, (name, d) => {
2365
+ if (!this.checking) return this.any(d);
2366
+ if (name === a) {
2367
+ seen |= 1;
2368
+ x = this.str(d);
2369
+ } else if (name === b) {
2370
+ seen |= 2;
2371
+ y = this.str(d);
2372
+ } else {
2373
+ this.violate();
2374
+ this.any(d);
2375
+ }
2376
+ });
2377
+ if (this.checking && seen !== 3) this.violate();
2378
+ return this.building && x !== void 0 && y !== void 0 ? [x, y] : void 0;
2379
+ }
2380
+ };
2381
+ function readProofBundle(bytes, limits) {
2382
+ if (bytes.length > limits.bundleBytes) return { ok: false, reason: "too-large" };
2383
+ const reader = new BundleReader(bytes, limits);
2384
+ const scanned = runStrict(() => reader.document());
2385
+ if (!scanned.ok) return { ok: false, reason: "json" };
2386
+ return reader.outcome(scanned.value);
2387
+ }
2388
+
2389
+ // src/commitment.ts
2390
+ var COMMITMENT_DOMAIN = "aleutian.commit.v1:";
2391
+ var COMMITMENT_NONCE_SIZE = 32;
2392
+ new TextEncoder().encode(COMMITMENT_DOMAIN);
2393
+
2394
+ // src/proofBundleHashes.ts
2395
+ var COMMITMENT_DOMAIN_BYTES = utf8ToBytes(COMMITMENT_DOMAIN);
2396
+ function sha512HexSync(text) {
2397
+ return bytesToHex(sha512(utf8ToBytes(text)));
2398
+ }
2399
+ function chainHashV3Sync(previousHash, globalSeq, timestamp, contentHash) {
2400
+ return sha512HexSync(
2401
+ CHAIN_HASH_PREFIX_V3 + previousHash + "|" + globalSeq + "|" + timestamp + "|" + contentHash
2402
+ );
2403
+ }
2404
+ function anchorChainHashSync(previousAnchorHash, subject, startEntryId, endEntryId, tipChainHash) {
2405
+ return sha512HexSync(
2406
+ ANCHOR_CHAIN_DOMAIN_V2 + previousAnchorHash + "|" + subject + "|" + startEntryId + "|" + endEntryId + "|" + tipChainHash
2407
+ );
2408
+ }
2409
+ function openCommitmentSync(nonce, content, commitment) {
2410
+ if (nonce.length !== COMMITMENT_NONCE_SIZE) return false;
2411
+ const inner = sha512(content);
2412
+ const outer = new Uint8Array(
2413
+ COMMITMENT_DOMAIN_BYTES.length + nonce.length + inner.length
2414
+ );
2415
+ outer.set(COMMITMENT_DOMAIN_BYTES, 0);
2416
+ outer.set(nonce, COMMITMENT_DOMAIN_BYTES.length);
2417
+ outer.set(inner, COMMITMENT_DOMAIN_BYTES.length + nonce.length);
2418
+ return bytesToHex(sha512(outer)) === commitment;
2419
+ }
2420
+
2421
+ // src/sinkShapes.ts
2422
+ var SINK_CHAIN_ID = /^[a-z0-9][a-z0-9_-]{0,30}\.[0-9a-f]{32}$/;
2423
+ var SINK_ENTRY_ID = /^sink-[0-9a-f]{32}$/;
2424
+ var HEX_1282 = /^[0-9a-f]{128}$/;
2425
+ var HEX_642 = /^[0-9a-f]{64}$/;
2426
+ var HEX_32 = /^[0-9a-f]{32}$/;
2427
+ var DECIMAL = /^(0|[1-9][0-9]*)$/;
2428
+ var MAX_GLOBAL_SEQ2 = 2n ** 63n - 1n;
2429
+ var TIMESTAMP = /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})\.[0-9]{6}Z$/;
2430
+ function isGlobalSeq(s) {
2431
+ if (!DECIMAL.test(s)) return false;
2432
+ return s.length <= 19 && BigInt(s) <= MAX_GLOBAL_SEQ2;
2433
+ }
2434
+ function daysInMonth(year, month) {
2435
+ if (month === 2) {
2436
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
2437
+ return leap ? 29 : 28;
2438
+ }
2439
+ return month === 4 || month === 6 || month === 9 || month === 11 ? 30 : 31;
2440
+ }
2441
+ function isSinkTimestamp(s) {
2442
+ const m = TIMESTAMP.exec(s);
2443
+ if (!m) return false;
2444
+ const year = Number(m[1]);
2445
+ const month = Number(m[2]);
2446
+ const day = Number(m[3]);
2447
+ const hour = Number(m[4]);
2448
+ const minute = Number(m[5]);
2449
+ const second = Number(m[6]);
2450
+ if (year < 1 || month < 1 || month > 12) return false;
2451
+ if (day < 1 || day > daysInMonth(year, month)) return false;
2452
+ return hour <= 23 && minute <= 59 && second <= 59;
2453
+ }
2454
+
2455
+ // src/recordEnvelope.ts
2456
+ var RECORD_ENVELOPE_DOMAIN = "aleutian.proof.record.v1";
2457
+ var MLDSA65_PUBLIC_KEY_SIZE2 = 1952;
2458
+ var MLDSA65_SIGNATURE_SIZE = 3309;
2459
+ var EMPTY_CONTEXT2 = new Uint8Array(0);
2460
+ function isValidRecordEnvelopeFields(f) {
2461
+ if (!SINK_CHAIN_ID.test(f.chainId)) return false;
2462
+ if (!SINK_ENTRY_ID.test(f.entryId)) return false;
2463
+ if (f.entryType !== "sink.event" && f.entryType !== "sink.erasure") return false;
2464
+ if (!isGlobalSeq(f.globalSeq)) return false;
2465
+ if (f.globalSeq === "0") {
2466
+ if (f.previousHash !== "") return false;
2467
+ } else if (!HEX_1282.test(f.previousHash)) {
2468
+ return false;
2469
+ }
2470
+ if (!isSinkTimestamp(f.timestamp)) return false;
2471
+ if (!HEX_1282.test(f.contentHash)) return false;
2472
+ return HEX_32.test(f.signingKeyId);
2473
+ }
2474
+ function buildRecordEnvelopeV1(fields) {
2475
+ if (!isValidRecordEnvelopeFields(fields)) {
2476
+ throw new Error("record envelope: a field is outside sink-format \xA79.1");
2477
+ }
2478
+ return encodeEnvelope(RECORD_ENVELOPE_DOMAIN, [
2479
+ fields.chainId,
2480
+ fields.entryId,
2481
+ fields.entryType,
2482
+ fields.globalSeq,
2483
+ fields.previousHash,
2484
+ fields.timestamp,
2485
+ fields.contentHash,
2486
+ fields.signingKeyId
2487
+ ]);
2488
+ }
2489
+ function encodeEnvelope(domainText, fieldValues) {
2490
+ const enc = new TextEncoder();
2491
+ const values = fieldValues.map((v) => enc.encode(v));
2492
+ const domain = enc.encode(domainText);
2493
+ let size = domain.length + 1;
2494
+ for (const v of values) size += 5 + v.length;
2495
+ const out = new Uint8Array(size);
2496
+ const view = new DataView(out.buffer);
2497
+ out.set(domain, 0);
2498
+ let at = domain.length;
2499
+ out[at++] = 0;
2500
+ values.forEach((v, i) => {
2501
+ out[at++] = i + 1;
2502
+ view.setUint32(at, v.length, false);
2503
+ at += 4;
2504
+ out.set(v, at);
2505
+ at += v.length;
2506
+ });
2507
+ return out;
2508
+ }
2509
+ function verifyMLDSA65Pure(publicKey, message, signature) {
2510
+ return verifyMLDSA65PureWithContext(publicKey, message, signature, EMPTY_CONTEXT2);
2511
+ }
2512
+ function verifyMLDSA65PureWithContext(publicKey, message, signature, context) {
2513
+ if (publicKey.length !== MLDSA65_PUBLIC_KEY_SIZE2) return false;
2514
+ if (signature.length !== MLDSA65_SIGNATURE_SIZE) return false;
2515
+ if (!(context instanceof Uint8Array) || context.length > 255) return false;
2516
+ try {
2517
+ return ml_dsa65.verify(signature, message, publicKey, { context });
2518
+ } catch {
2519
+ return false;
2520
+ }
2521
+ }
2522
+ var RECORD_ENVELOPE_DOMAIN_V2 = "aleutian.proof.record.v2";
2523
+ var RECORD_SIGNATURE_CONTEXT_V2 = "aleutian.proof.record.v2";
2524
+ new TextEncoder().encode(RECORD_SIGNATURE_CONTEXT_V2);
2525
+ function isValidRecordEnvelopeFieldsV2(f) {
2526
+ return isValidRecordEnvelopeFields(f) && typeof f.leafHash === "string" && HEX_1282.test(f.leafHash);
2527
+ }
2528
+ function buildRecordEnvelopeV2(fields) {
2529
+ if (!isValidRecordEnvelopeFieldsV2(fields)) {
2530
+ throw new Error("record envelope: a field is outside sink-format \xA79.1");
2531
+ }
2532
+ return encodeEnvelope(RECORD_ENVELOPE_DOMAIN_V2, [
2533
+ fields.chainId,
2534
+ fields.entryId,
2535
+ fields.entryType,
2536
+ fields.globalSeq,
2537
+ fields.previousHash,
2538
+ fields.timestamp,
2539
+ fields.contentHash,
2540
+ fields.signingKeyId,
2541
+ fields.leafHash
2542
+ ]);
2543
+ }
2544
+
2545
+ // src/proofBundle.ts
2546
+ var PROOF_BUNDLE_RESULT_ID = "aleutian.proof.bundle-result.v1";
2547
+ var PROOF_BUNDLE_LIMITS = Object.freeze({
2548
+ bundleBytes: 256 * 1024 * 1024,
2549
+ values: 15e6,
2550
+ valuesV2: 17e6,
2551
+ chains: 1e5,
2552
+ entries: 1e6,
2553
+ checkpoints: 1e5,
2554
+ checkpointTextBytes: 1024 * 1024
2555
+ });
2556
+ var MAX_DEPTH = BUNDLE_MAX_DEPTH;
2557
+ var ProofBundleVerifierError = class extends Error {
2558
+ constructor(message) {
2559
+ super(message);
2560
+ this.name = "ProofBundleVerifierError";
2561
+ Object.setPrototypeOf(this, new.target.prototype);
2562
+ }
2563
+ };
2564
+ function hasExactMembers(obj, required) {
2565
+ if (obj.members.size !== required.length) return false;
2566
+ for (const name of required) if (!obj.members.has(name)) return false;
2567
+ return true;
2568
+ }
2569
+ function str(obj, name) {
2570
+ const v = obj.members.get(name);
2571
+ return typeof v === "string" ? v : void 0;
2572
+ }
2573
+ function stringPair(v, a, b) {
2574
+ if (!(v instanceof JsonObject) || !hasExactMembers(v, [a, b])) return null;
2575
+ const x = str(v, a);
2576
+ const y = str(v, b);
2577
+ return x === void 0 || y === void 0 ? null : [x, y];
2578
+ }
2579
+ var CHECKPOINT_MEMBERS = [
2580
+ "version",
2581
+ "anchor_id",
2582
+ "chain_hash",
2583
+ "created_at_ms",
2584
+ "entry_count",
2585
+ "previous_anchor_id",
2586
+ "range",
2587
+ "signature",
2588
+ "signing_key_id",
2589
+ "subject",
2590
+ "verified_through"
2591
+ ];
2592
+ var CHECKPOINT_NUMBER = /^(0|[1-9][0-9]*)$/;
2593
+ var MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
2594
+ var MLDSA65_SIGNATURE_SIZE2 = 3309;
2595
+ function checkpointNumber(v) {
2596
+ if (!(v instanceof JsonNumber) || !CHECKPOINT_NUMBER.test(v.text)) return null;
2597
+ const n = BigInt(v.text);
2598
+ return n <= MAX_SAFE ? n : null;
2599
+ }
2600
+ function pipeFree(s) {
2601
+ return s !== void 0 && s !== "" && !s.includes("|");
2602
+ }
2603
+ function parseCheckpoint(text, limits) {
2604
+ const bytes = new TextEncoder().encode(text);
2605
+ if (bytes.length > limits.checkpointTextBytes) return null;
2606
+ const parsed = parseStrictJson(bytes, MAX_DEPTH);
2607
+ if (!parsed.ok || parsed.duplicateKey) return null;
2608
+ const obj = parsed.value;
2609
+ if (!(obj instanceof JsonObject) || !hasExactMembers(obj, CHECKPOINT_MEMBERS))
2610
+ return null;
2611
+ const version = checkpointNumber(obj.members.get("version"));
2612
+ const createdAtMs = checkpointNumber(obj.members.get("created_at_ms"));
2613
+ const entryCount = checkpointNumber(obj.members.get("entry_count"));
2614
+ const verifiedThrough = checkpointNumber(obj.members.get("verified_through"));
2615
+ if (version !== 6n || createdAtMs === null) return null;
2616
+ if (entryCount === null || entryCount < 1n) return null;
2617
+ if (verifiedThrough === null || verifiedThrough < 1n) return null;
2618
+ const anchorId = str(obj, "anchor_id");
2619
+ const previousAnchorId = str(obj, "previous_anchor_id");
2620
+ const subject = str(obj, "subject");
2621
+ if (!pipeFree(anchorId) || !pipeFree(previousAnchorId) || !pipeFree(subject))
2622
+ return null;
2623
+ const chainHash = str(obj, "chain_hash");
2624
+ const signingKeyId = str(obj, "signing_key_id");
2625
+ if (chainHash === void 0 || !HEX_1282.test(chainHash)) return null;
2626
+ if (signingKeyId === void 0 || !HEX_32.test(signingKeyId)) return null;
2627
+ const signatureText = str(obj, "signature");
2628
+ const signature = signatureText === void 0 ? null : decodeCanonicalBase64(signatureText);
2629
+ if (signature === null || signature.length !== MLDSA65_SIGNATURE_SIZE2) return null;
2630
+ const range = stringPair(obj.members.get("range"), "start_entry_id", "end_entry_id");
2631
+ if (range === null || !SINK_ENTRY_ID.test(range[0]) || !SINK_ENTRY_ID.test(range[1]))
2632
+ return null;
2633
+ return {
2634
+ anchorId,
2635
+ chainHash,
2636
+ createdAtMs,
2637
+ entryCount: Number(entryCount),
2638
+ previousAnchorId,
2639
+ startEntryId: range[0],
2640
+ endEntryId: range[1],
2641
+ signature,
2642
+ signingKeyId,
2643
+ subject,
2644
+ verifiedThrough
2645
+ };
2646
+ }
2647
+ function checkpointCanonicalBytes(cp) {
2648
+ const canonical = canonicalizeAnchorV6({
2649
+ version: 6,
2650
+ anchorId: cp.anchorId,
2651
+ subject: cp.subject,
2652
+ chainHash: cp.chainHash,
2653
+ range: { startEntryId: cp.startEntryId, endEntryId: cp.endEntryId },
2654
+ entryCount: BigInt(cp.entryCount),
2655
+ signingKeyId: cp.signingKeyId,
2656
+ createdAtMs: cp.createdAtMs,
2657
+ previousAnchorId: cp.previousAnchorId,
2658
+ verifiedThrough: cp.verifiedThrough
2659
+ });
2660
+ return new TextEncoder().encode(canonical);
2661
+ }
2662
+ var CHECKPOINT_V7_MEMBERS = [
2663
+ "version",
2664
+ "anchor_id",
2665
+ "chain_hash",
2666
+ "created_at_ms",
2667
+ "entry_count",
2668
+ "previous_anchor_hash",
2669
+ "previous_anchor_id",
2670
+ "range",
2671
+ "signature",
2672
+ "signing_key_id",
2673
+ "subject"
2674
+ ];
2675
+ var ANCHOR_CONTEXT_V7 = new TextEncoder().encode(ANCHOR_SIGNATURE_CONTEXT_V7);
2676
+ var RECORD_CONTEXT_V2 = new TextEncoder().encode(RECORD_SIGNATURE_CONTEXT_V2);
2677
+ function parseCheckpointV7(text, limits) {
2678
+ const bytes = new TextEncoder().encode(text);
2679
+ if (bytes.length > limits.checkpointTextBytes) return null;
2680
+ const parsed = parseStrictJson(bytes, MAX_DEPTH);
2681
+ if (!parsed.ok || parsed.duplicateKey) return null;
2682
+ const obj = parsed.value;
2683
+ if (!(obj instanceof JsonObject) || !hasExactMembers(obj, CHECKPOINT_V7_MEMBERS))
2684
+ return null;
2685
+ const version = checkpointNumber(obj.members.get("version"));
2686
+ const createdAtMs = checkpointNumber(obj.members.get("created_at_ms"));
2687
+ if (version !== 7n || createdAtMs === null) return null;
2688
+ const entryCountText = str(obj, "entry_count");
2689
+ if (entryCountText === void 0 || !isGlobalSeq(entryCountText)) return null;
2690
+ const entryCount = BigInt(entryCountText);
2691
+ if (entryCount < 1n) return null;
2692
+ const range = stringPair(obj.members.get("range"), "start_seq", "end_seq");
2693
+ if (range === null || !isGlobalSeq(range[0]) || !isGlobalSeq(range[1])) return null;
2694
+ if (range[0] !== "0" || entryCount !== BigInt(range[1]) + 1n) return null;
2695
+ const anchorId = str(obj, "anchor_id");
2696
+ const previousAnchorId = str(obj, "previous_anchor_id");
2697
+ const subject = str(obj, "subject");
2698
+ if (!pipeFree(anchorId) || !pipeFree(previousAnchorId) || !pipeFree(subject))
2699
+ return null;
2700
+ const chainHash = str(obj, "chain_hash");
2701
+ const previousAnchorHash = str(obj, "previous_anchor_hash");
2702
+ const signingKeyId = str(obj, "signing_key_id");
2703
+ if (chainHash === void 0 || !HEX_1282.test(chainHash)) return null;
2704
+ if (previousAnchorHash === void 0 || !HEX_1282.test(previousAnchorHash)) return null;
2705
+ if (signingKeyId === void 0 || !HEX_32.test(signingKeyId)) return null;
2706
+ if (previousAnchorId === SEED_ANCHOR_ID !== (previousAnchorHash === SEED_ANCHOR_HASH))
2707
+ return null;
2708
+ const signatureText = str(obj, "signature");
2709
+ const signature = signatureText === void 0 ? null : decodeCanonicalBase64(signatureText);
2710
+ if (signature === null || signature.length !== MLDSA65_SIGNATURE_SIZE2) return null;
2711
+ return {
2712
+ anchorId,
2713
+ chainHash,
2714
+ createdAtMs,
2715
+ entryCount,
2716
+ endSeq: range[1],
2717
+ previousAnchorHash,
2718
+ previousAnchorId,
2719
+ signature,
2720
+ signingKeyId,
2721
+ subject
2722
+ };
2723
+ }
2724
+ function checkpointCanonicalBytesV7(cp) {
2725
+ const canonical = canonicalizeAnchorV7({
2726
+ version: 7,
2727
+ anchorId: cp.anchorId,
2728
+ subject: cp.subject,
2729
+ chainHash: cp.chainHash,
2730
+ range: { startEntryId: "", endEntryId: "", startSeq: 0n, endSeq: BigInt(cp.endSeq) },
2731
+ entryCount: cp.entryCount,
2732
+ signingKeyId: cp.signingKeyId,
2733
+ createdAtMs: cp.createdAtMs,
2734
+ previousAnchorId: cp.previousAnchorId,
2735
+ previousAnchorHash: cp.previousAnchorHash
2736
+ });
2737
+ return new TextEncoder().encode(canonical);
2738
+ }
2739
+ var ProblemSet = class {
2740
+ byCode = /* @__PURE__ */ new Map();
2741
+ add(code, index) {
2742
+ const p = this.byCode.get(code);
2743
+ if (p === void 0) {
2744
+ this.byCode.set(code, { count: 1, first: index });
2745
+ } else {
2746
+ p.count++;
2747
+ if (index !== null && (p.first === null || index < p.first)) p.first = index;
2748
+ }
2749
+ }
2750
+ get empty() {
2751
+ return this.byCode.size === 0;
2752
+ }
2753
+ /** Sorted by code in byte order (codes are ASCII, so UTF-16 order is byte order). */
2754
+ list() {
2755
+ return [...this.byCode.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([code, p]) => ({ code, count: p.count, first: p.first }));
2756
+ }
2757
+ };
2758
+ var ERASURE_DOMAIN = "aleutian.sink.erasure.v1:";
2759
+ function erasureHash(previousGlobalSeq) {
2760
+ const record = '{"erased":"every earlier event on this chain","through_global_seq":"' + previousGlobalSeq + '"}';
2761
+ return sha512HexSync(ERASURE_DOMAIN + record);
2762
+ }
2763
+ function disclosedInShape(d) {
2764
+ if (!HEX_642.test(d.nonce)) return false;
2765
+ if (d.content.length > 87384) return false;
2766
+ const content = decodeCanonicalBase64(d.content);
2767
+ return content !== null && content.length >= 1 && content.length <= 65536;
2768
+ }
2769
+ function isWellFormed(e, format) {
2770
+ if (format === 2 && !(HEX_642.test(e.salt ?? "") && HEX_1282.test(e.leafHash ?? ""))) {
2771
+ return false;
2772
+ }
2773
+ return SINK_ENTRY_ID.test(e.entryId) && isGlobalSeq(e.globalSeq) && (e.previousHash === "" || HEX_1282.test(e.previousHash)) && isSinkTimestamp(e.timestamp) && HEX_1282.test(e.contentHash) && HEX_1282.test(e.chainHash);
2774
+ }
2775
+ function hashesRecompute(e, format) {
2776
+ if (format === 1) {
2777
+ return chainHashV3Sync(e.previousHash, e.globalSeq, e.timestamp, e.contentHash) === e.chainHash;
2778
+ }
2779
+ const leafHash = e.leafHash;
2780
+ if (leafHashV4Sync(e.entryId, e.entryType, e.contentHash, e.salt) !== leafHash) return false;
2781
+ return chainHashV4Sync(e.previousHash, e.globalSeq, e.timestamp, leafHash) === e.chainHash;
2782
+ }
2783
+ function checkCheckpointV7(text, head, ctx) {
2784
+ const n = BigInt(ctx.entries.length);
2785
+ const cp = parseCheckpointV7(text, ctx.limits);
2786
+ if (cp === null) return "checkpoint-malformed";
2787
+ if (!ctx.chainIdValid || cp.subject !== ctx.chainId) return "checkpoint-subject";
2788
+ if (cp.previousAnchorId !== head.anchorId || cp.previousAnchorHash !== head.anchorHash || cp.entryCount <= head.count) {
2789
+ return "checkpoint-sequence";
2790
+ }
2791
+ if (cp.entryCount > n) return "checkpoint-overreach";
2792
+ if (cp.entryCount > BigInt(ctx.firstMalformed)) return "checkpoint-binding";
2793
+ const tip = ctx.entries[Number(cp.entryCount) - 1];
2794
+ const expected = anchorChainHashV7Sync(
2795
+ cp.previousAnchorHash,
2796
+ ctx.chainId,
2797
+ "0",
2798
+ cp.endSeq,
2799
+ tip.chainHash
2800
+ );
2801
+ if (cp.chainHash !== expected) return "checkpoint-binding";
2802
+ if (ctx.trust.checkpoint.size > 0) {
2803
+ const key = ctx.trust.checkpoint.get(cp.signingKeyId);
2804
+ if (key === void 0) return "checkpoint-unknown-key";
2805
+ if (!verifyMLDSA65PureWithContext(
2806
+ key,
2807
+ checkpointCanonicalBytesV7(cp),
2808
+ cp.signature,
2809
+ ANCHOR_CONTEXT_V7
2810
+ )) {
2811
+ return "checkpoint-bad-signature";
2812
+ }
2813
+ }
2814
+ return { anchorId: cp.anchorId, anchorHash: cp.chainHash, count: cp.entryCount };
2815
+ }
2816
+ function verifyChain(chain, trust, limits, format) {
2817
+ const { chainId, entries } = chain;
2818
+ const n = entries.length;
2819
+ const problems = new ProblemSet();
2820
+ const checkpointTrust = trust.checkpoint.size > 0;
2821
+ const recordTrust = trust.record.size > 0;
2822
+ const chainIdValid = SINK_CHAIN_ID.test(chainId);
2823
+ if (!chainIdValid) problems.add("invalid-chain-id", null);
2824
+ const wellFormed = [];
2825
+ const disclosedBad = [];
2826
+ const seenIds = /* @__PURE__ */ new Set();
2827
+ for (let i = 0; i < n; i++) {
2828
+ const e = entries[i];
2829
+ const wf = isWellFormed(e, format);
2830
+ const dBad = e.disclosed !== void 0 && (e.entryType !== "sink.event" || !disclosedInShape(e.disclosed));
2831
+ wellFormed.push(wf);
2832
+ disclosedBad.push(dBad);
2833
+ if (!wf || dBad) problems.add("field-shape", i);
2834
+ if (seenIds.has(e.entryId)) problems.add("duplicate-entry-id", i);
2835
+ if (wf) seenIds.add(e.entryId);
2836
+ }
2837
+ const holdsErasureHash = new Array(n).fill(false);
2838
+ let lastErasure = -1;
2839
+ for (let i = 0; i < n; i++) {
2840
+ if (!wellFormed[i]) continue;
2841
+ const e = entries[i];
2842
+ if (i === 0) {
2843
+ if (e.globalSeq !== "0" || e.previousHash !== "") problems.add("first-entry", i);
2844
+ } else if (wellFormed[i - 1]) {
2845
+ const prev = entries[i - 1];
2846
+ if (BigInt(e.globalSeq) !== BigInt(prev.globalSeq) + 1n)
2847
+ problems.add("sequence", i);
2848
+ if (e.previousHash !== prev.chainHash) problems.add("link", i);
2849
+ if (e.contentHash === erasureHash(prev.globalSeq)) {
2850
+ holdsErasureHash[i] = true;
2851
+ lastErasure = i;
2852
+ }
2853
+ }
2854
+ if (!hashesRecompute(e, format)) problems.add("chain-hash", i);
2855
+ }
2856
+ const E = lastErasure;
2857
+ let events = 0;
2858
+ let erasures = 0;
2859
+ let opened = 0;
2860
+ let committed = 0;
2861
+ let erased = 0;
2862
+ const genuineErasure = new Array(n).fill(false);
2863
+ for (let i = 0; i < n; i++) {
2864
+ const e = entries[i];
2865
+ if (e.entryType === "sink.event") events++;
2866
+ if (!wellFormed[i]) continue;
2867
+ if (E >= 0 && i > E) {
2868
+ problems.add("after-erasure", i);
2869
+ continue;
2870
+ }
2871
+ if (disclosedBad[i]) continue;
2872
+ if (holdsErasureHash[i]) {
2873
+ if (e.entryType === "sink.erasure") {
2874
+ genuineErasure[i] = true;
2875
+ erasures++;
2876
+ } else {
2877
+ problems.add("erasure-invalid", i);
2878
+ }
2879
+ } else if (e.entryType === "sink.erasure") {
2880
+ problems.add("erasure-invalid", i);
2881
+ } else if (e.entryType === "sink.event") {
2882
+ if (i < E) {
2883
+ if (e.disclosed === void 0) erased++;
2884
+ else problems.add("erased-event-disclosed", i);
2885
+ } else if (e.disclosed === void 0) {
2886
+ committed++;
2887
+ } else {
2888
+ const nonce = hexToUint8Array(e.disclosed.nonce);
2889
+ const content = decodeCanonicalBase64(e.disclosed.content);
2890
+ if (openCommitmentSync(nonce, content, e.contentHash)) opened++;
2891
+ else problems.add("modified", i);
2892
+ }
2893
+ } else {
2894
+ problems.add("entry-type", i);
2895
+ }
2896
+ }
2897
+ const malformedAt = wellFormed.indexOf(false);
2898
+ const firstMalformed = malformedAt === -1 ? n : malformedAt;
2899
+ let passedCheckpoints = 0;
2900
+ let covered = 0;
2901
+ let prevAnchorId = SEED_ANCHOR_ID;
2902
+ let prevAnchorHash = SEED_ANCHOR_HASH;
2903
+ let prevCount = 0;
2904
+ if (format === 2) {
2905
+ let head = { anchorId: SEED_ANCHOR_ID, anchorHash: SEED_ANCHOR_HASH, count: 0n };
2906
+ for (let k = 0; k < chain.checkpoints.length; k++) {
2907
+ const step = checkCheckpointV7(chain.checkpoints[k], head, {
2908
+ chainId,
2909
+ chainIdValid,
2910
+ entries,
2911
+ firstMalformed,
2912
+ trust,
2913
+ limits
2914
+ });
2915
+ if (typeof step === "string") {
2916
+ problems.add(step, k);
2917
+ break;
2918
+ }
2919
+ head = step;
2920
+ passedCheckpoints++;
2921
+ covered = Number(step.count);
2922
+ }
2923
+ }
2924
+ for (let k = 0; format === 1 && k < chain.checkpoints.length; k++) {
2925
+ const cp = parseCheckpoint(chain.checkpoints[k], limits);
2926
+ if (cp === null) {
2927
+ problems.add("checkpoint-malformed", k);
2928
+ break;
2929
+ }
2930
+ if (!chainIdValid || cp.subject !== chainId) {
2931
+ problems.add("checkpoint-subject", k);
2932
+ break;
2933
+ }
2934
+ if (cp.previousAnchorId !== prevAnchorId || cp.entryCount < 1 || cp.entryCount <= prevCount) {
2935
+ problems.add("checkpoint-sequence", k);
2936
+ break;
2937
+ }
2938
+ if (cp.entryCount > n) {
2939
+ problems.add("checkpoint-overreach", k);
2940
+ break;
2941
+ }
2942
+ const count = cp.entryCount;
2943
+ let bound = count <= firstMalformed;
2944
+ if (bound) {
2945
+ const first = entries[0];
2946
+ const tip = entries[count - 1];
2947
+ bound = cp.startEntryId === first.entryId && cp.endEntryId === tip.entryId && cp.chainHash === anchorChainHashSync(
2948
+ prevAnchorHash,
2949
+ chainId,
2950
+ cp.startEntryId,
2951
+ cp.endEntryId,
2952
+ tip.chainHash
2953
+ );
2954
+ }
2955
+ if (!bound) {
2956
+ problems.add("checkpoint-binding", k);
2957
+ break;
2958
+ }
2959
+ if (checkpointTrust) {
2960
+ const key = trust.checkpoint.get(cp.signingKeyId);
2961
+ if (key === void 0) {
2962
+ problems.add("checkpoint-unknown-key", k);
2963
+ break;
2964
+ }
2965
+ if (!verifyMLDSA65Pure(key, checkpointCanonicalBytes(cp), cp.signature)) {
2966
+ problems.add("checkpoint-bad-signature", k);
2967
+ break;
2968
+ }
2969
+ }
2970
+ passedCheckpoints++;
2971
+ covered = count;
2972
+ prevAnchorId = cp.anchorId;
2973
+ prevAnchorHash = cp.chainHash;
2974
+ prevCount = count;
2975
+ }
2976
+ let recordSignaturesPresent = 0;
2977
+ const recordAuthenticated = new Array(n).fill(false);
2978
+ for (let i = 0; i < n; i++) {
2979
+ const e = entries[i];
2980
+ const rs = e.recordSignature;
2981
+ if (rs !== void 0) recordSignaturesPresent++;
2982
+ if (!recordTrust) continue;
2983
+ if (rs === void 0) {
2984
+ problems.add("unsigned", i);
2985
+ continue;
2986
+ }
2987
+ const signature = HEX_32.test(rs.keyId) ? decodeCanonicalBase64(rs.signature) : null;
2988
+ if (signature === null || signature.length !== MLDSA65_SIGNATURE_SIZE2) {
2989
+ problems.add("malformed-signature", i);
2990
+ continue;
2991
+ }
2992
+ const key = trust.record.get(rs.keyId);
2993
+ if (key === void 0) {
2994
+ problems.add("unknown-key", i);
2995
+ continue;
2996
+ }
2997
+ const envelopeBuildable = wellFormed[i] && chainIdValid && (e.entryType === "sink.event" || e.entryType === "sink.erasure") && e.previousHash === "" === (e.globalSeq === "0");
2998
+ if (!envelopeBuildable) {
2999
+ problems.add("bad-signature", i);
3000
+ continue;
3001
+ }
3002
+ const fields = {
3003
+ chainId,
3004
+ entryId: e.entryId,
3005
+ entryType: e.entryType,
3006
+ globalSeq: e.globalSeq,
3007
+ previousHash: e.previousHash,
3008
+ timestamp: e.timestamp,
3009
+ contentHash: e.contentHash,
3010
+ signingKeyId: rs.keyId
3011
+ };
3012
+ const verified = format === 1 ? verifyMLDSA65Pure(key, buildRecordEnvelopeV1(fields), signature) : verifyMLDSA65PureWithContext(
3013
+ key,
3014
+ buildRecordEnvelopeV2({ ...fields, leafHash: e.leafHash }),
3015
+ signature,
3016
+ RECORD_CONTEXT_V2
3017
+ );
3018
+ if (verified) recordAuthenticated[i] = true;
3019
+ else problems.add("bad-signature", i);
3020
+ }
3021
+ let authenticated = 0;
3022
+ let checkpointAuthenticated = 0;
3023
+ let recordAuthenticatedCount = 0;
3024
+ const checkpointAuthenticatedAt = (i) => checkpointTrust && wellFormed[i] === true && i < covered;
3025
+ for (let i = 0; i < n; i++) {
3026
+ const byCheckpoint = checkpointAuthenticatedAt(i);
3027
+ const byRecord = recordAuthenticated[i] === true;
3028
+ if (byCheckpoint) checkpointAuthenticated++;
3029
+ if (byRecord) recordAuthenticatedCount++;
3030
+ if (byCheckpoint || byRecord) authenticated++;
3031
+ }
3032
+ let erasure = "none";
3033
+ if (E >= 0 && genuineErasure[E]) {
3034
+ if (problems.empty && checkpointAuthenticatedAt(E)) erasure = "anchored";
3035
+ else if (problems.empty && recordAuthenticated[E]) erasure = "signed";
3036
+ else erasure = "recorded";
3037
+ }
3038
+ const unauthenticated = n - authenticated;
3039
+ const verdict = !problems.empty ? "failed" : unauthenticated > 0 ? "unauthenticated" : "ok";
3040
+ return {
3041
+ verdict,
3042
+ erasure,
3043
+ counts: {
3044
+ entries: n,
3045
+ events,
3046
+ erasures,
3047
+ opened,
3048
+ committed,
3049
+ erased,
3050
+ checkpoints: passedCheckpoints,
3051
+ unanchored: n - covered,
3052
+ authenticated,
3053
+ unauthenticated,
3054
+ checkpoint_authenticated: checkpointAuthenticated,
3055
+ record_authenticated: recordAuthenticatedCount,
3056
+ record_signatures_present: recordSignaturesPresent
3057
+ },
3058
+ problems: problems.list()
3059
+ };
3060
+ }
3061
+ function checkTrustShape(trust) {
3062
+ const t = trust;
3063
+ if (typeof t !== "object" || t === null || !Array.isArray(t.checkpointKeys) || !Array.isArray(t.recordKeys)) {
3064
+ throw new ProofBundleVerifierError(
3065
+ "trust must be an object with checkpointKeys and recordKeys arrays of Uint8Array"
3066
+ );
3067
+ }
3068
+ }
3069
+ async function keyMap(keys, role) {
3070
+ const map = /* @__PURE__ */ new Map();
3071
+ for (const key of keys) {
3072
+ if (!(key instanceof Uint8Array) || key.length !== MLDSA65_PUBLIC_KEY_SIZE) {
3073
+ throw new ProofBundleVerifierError(
3074
+ `${role} trust: every key must be a raw ${MLDSA65_PUBLIC_KEY_SIZE}-byte ML-DSA-65 public key`
3075
+ );
3076
+ }
3077
+ map.set(await keyIdMLDSA65(key), key);
3078
+ }
3079
+ return map;
3080
+ }
3081
+ async function verifyProofBundleWithLimits(bundle, trust, limits) {
3082
+ if (!(bundle instanceof Uint8Array)) {
3083
+ throw new ProofBundleVerifierError("bundle must be a Uint8Array");
3084
+ }
3085
+ checkTrustShape(trust);
3086
+ const keys = {
3087
+ checkpoint: await keyMap(trust.checkpointKeys, "checkpoint"),
3088
+ record: await keyMap(trust.recordKeys, "record")
3089
+ };
3090
+ const read = readProofBundle(bundle, limits);
3091
+ if (!read.ok)
3092
+ return { result: PROOF_BUNDLE_RESULT_ID, readable: false, reason: read.reason };
3093
+ const chains = read.chains;
3094
+ const occurrences = /* @__PURE__ */ new Map();
3095
+ for (const c of chains)
3096
+ occurrences.set(c.chainId, (occurrences.get(c.chainId) ?? 0) + 1);
3097
+ const bundleProblems = new ProblemSet();
3098
+ const seen = /* @__PURE__ */ new Set();
3099
+ chains.forEach((c, i) => {
3100
+ if (seen.has(c.chainId)) bundleProblems.add("duplicate-chain", i);
3101
+ seen.add(c.chainId);
3102
+ });
3103
+ const results = {};
3104
+ let anyFailed = false;
3105
+ let anyUnauthenticated = false;
3106
+ for (let i = 0; i < chains.length; i++) {
3107
+ const c = chains[i];
3108
+ const r = verifyChain(c, keys, limits, read.format);
3109
+ const keyedById = SINK_CHAIN_ID.test(c.chainId) && occurrences.get(c.chainId) === 1;
3110
+ results[keyedById ? c.chainId : `#${i}`] = r;
3111
+ if (r.verdict === "failed") anyFailed = true;
3112
+ if (r.verdict === "unauthenticated") anyUnauthenticated = true;
3113
+ }
3114
+ const verdict = anyFailed || !bundleProblems.empty ? "failed" : anyUnauthenticated || chains.length === 0 ? "unauthenticated" : "ok";
3115
+ return {
3116
+ result: PROOF_BUNDLE_RESULT_ID,
3117
+ readable: true,
3118
+ checkpoint_trust: keys.checkpoint.size > 0 ? "checked" : "not_checked",
3119
+ record_trust: keys.record.size > 0 ? "checked" : "not_checked",
3120
+ verdict,
3121
+ bundle_problems: bundleProblems.list(),
3122
+ chains: results
3123
+ };
3124
+ }
3125
+ function verifyProofBundle(bundle, trust) {
3126
+ return verifyProofBundleWithLimits(bundle, trust, PROOF_BUNDLE_LIMITS);
3127
+ }
3128
+ async function keysTrustedForBothRoles(trust) {
3129
+ checkTrustShape(trust);
3130
+ const checkpoint = await keyMap(trust.checkpointKeys, "checkpoint");
3131
+ const record = await keyMap(trust.recordKeys, "record");
3132
+ return [...checkpoint.keys()].filter((id) => record.has(id)).sort();
3133
+ }
3134
+
3135
+ // src/proofKeyFile.ts
3136
+ var ProofKeyFileError = class extends Error {
3137
+ constructor(message) {
3138
+ super(message);
3139
+ this.name = "ProofKeyFileError";
3140
+ Object.setPrototypeOf(this, new.target.prototype);
3141
+ }
3142
+ };
3143
+ var BEGIN = "-----BEGIN PUBLIC KEY-----";
3144
+ var END = "-----END PUBLIC KEY-----";
3145
+ var SPKI_SIZE = 4 + 13 + 5 + MLDSA65_PUBLIC_KEY_SIZE;
3146
+ var WHITESPACE_ONLY = /^[ \t\r\n]*$/;
3147
+ var BODY_LINE = /^[A-Za-z0-9+/=]+$/;
3148
+ function isAscii(bytes) {
3149
+ for (const b of bytes) if (b > 127) return false;
3150
+ return true;
3151
+ }
3152
+ function parseProofPublicKeyFile(bytes) {
3153
+ if (!isAscii(bytes)) throw new ProofKeyFileError("key file: not an ASCII PEM file");
3154
+ let text = "";
3155
+ for (const b of bytes) text += String.fromCharCode(b);
3156
+ const begin = text.indexOf(BEGIN);
3157
+ if (begin < 0 || !WHITESPACE_ONLY.test(text.slice(0, begin))) {
3158
+ throw new ProofKeyFileError(
3159
+ "key file: not exactly one PEM block labelled PUBLIC KEY"
3160
+ );
3161
+ }
3162
+ const afterBegin = begin + BEGIN.length;
3163
+ const end = text.indexOf(END, afterBegin);
3164
+ if (end < 0) throw new ProofKeyFileError("key file: PEM block not terminated");
3165
+ if (!WHITESPACE_ONLY.test(text.slice(end + END.length))) {
3166
+ throw new ProofKeyFileError(
3167
+ "key file: something other than whitespace after the PEM block"
3168
+ );
3169
+ }
3170
+ const inner = text.slice(afterBegin, end);
3171
+ const lines = inner.split("\n");
3172
+ if (lines.length < 3 || lines[0] !== "" && lines[0] !== "\r" || lines[lines.length - 1] !== "") {
3173
+ throw new ProofKeyFileError("key file: malformed PEM body");
3174
+ }
3175
+ let body = "";
3176
+ for (const raw of lines.slice(1, -1)) {
3177
+ const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
3178
+ if (!BODY_LINE.test(line))
3179
+ throw new ProofKeyFileError("key file: malformed PEM body");
3180
+ body += line;
3181
+ }
3182
+ const der = decodeCanonicalBase64(body);
3183
+ if (der === null) throw new ProofKeyFileError("key file: body is not canonical base64");
3184
+ if (der.length !== SPKI_SIZE) {
3185
+ throw new ProofKeyFileError("key file: not the SPKI of an ML-DSA-65 public key");
3186
+ }
3187
+ const key = der.slice(SPKI_SIZE - MLDSA65_PUBLIC_KEY_SIZE);
3188
+ const expected = mldsa65Spki(key);
3189
+ for (let i = 0; i < SPKI_SIZE; i++) {
3190
+ if (der[i] !== expected[i]) {
3191
+ throw new ProofKeyFileError("key file: not the SPKI of an ML-DSA-65 public key");
3192
+ }
3193
+ }
3194
+ return key;
3195
+ }
3196
+
3197
+ // src/fixtures/trust-root-dev.json
3198
+ var trust_root_dev_default = { public_key_b64: "R+dFhtvIZmQtyLr2S3xal9jvJAcfihEtBBuEclDzwxsIPWO1N3mkxAPIr/NUnVQllItEDs1Rtp3idRUT/qDULpnQr37XYivMrPbv35tjM9BELAQ17B1TPk//sacNZQ/6Utfp7YzawCEfnvMnqdQNJuWxgxGSIce+wzh5WsgPWDDGmCHgBrxDQKx0D9/AyTcbWq2E+qhe8kxVXm9Nitsz59vO4N/Ru6H3WrZAIhFFAiLpDYi0Sth8jPuwMr8kLHcbPwVkMbxlTbd+dUQYUL5URm/COlMWxogKSkpLhkEQdlS+ibamwdV8sSydSricaDkezeCCuzKZEHd5k09GBTN+UMmwo1RzB+Aq5G+9FhSvm9rj4r8zyu7zEVA4iBwM9vofVQ14+SDgcMbUBtQkqqsKri7IPeQ8QAyybztBIMA4DeUtYWmI1d65mqPH2EqoxAqVno/q6oQdPrrBo0zBeR71S420AXULV3rZymVjYFxTz5gv4A1myVsIbjEoh3k5Zv6Akp7fvjkhgYOyg37JSJn193chl9nCkUSWMr8oh/psY6u++oHycOkSe+HZgbZ4iFoCnh5KeOGrbs5v9iuuaEaesB1woJArCGI/OC6w7cvHITUw805YxNaW4xOCGWyZHp0IBUUYsUtJkm95qF07poIlXpVOOxDhAUtyaTShqGEcD65TN+V6+uYmF/oAtB8c1W+SCHwE5NWIeTN6GS4fNPD9pjRJ/BoxywGec+1CgMSamje/8gNI9HHGACTT8gc5rIQzeGJeHbA+M7eCUU7S9PPmIM+b0nTPdT3U8UV71NfiKxI5kz2wwRF9+DQiTchqZDW1YZtLWKZil91lu49Vl/OQLj1N28epsb+8xdCNW2nge+0Mf7T5NuQlJbDGkCltKM00lMLHqxvUwZK3Wm/25ih6CiEPJ1WMN53Ng8V7AbpmTIWUi4cosjsnffIKinEYO/ncuUrIfHgFPweZu1U/kZpgCfO8QxsneG7ro1dqpSa5/AHQi1/GuOe1NngaQefvz07sReR4DIEiUcbG77/Z923uVPBuEta5fYZFBEsQTc8zWfTg581tMW6Qog1Nn2pVr4PqSbHx7PUWgoegCfs0aRAEe1VR3yvuagPJwjMZ9UrwjAqW+7MIGZYtGVzn7Wx1Xlr/M0hLH/rATyPBTPESQNRFEuZUqIcyzuVr3vkNtaZnZC8xbKaOKt3S55BxQeUsprESq/T7CiVRU5IqpyzfpSp6fhRJiC+J+lDzw5jU3xyUb85FFLbOHPAyBMjVSeKXj2Pmfq89yaMxeGVS6ntdrgeD8WBd+echHhLtyuLSIgBtaD6I02IhT5YubpQ69MUsvaSZe1gpvRJ4sQUSjiy4kVbEbt8AYmzvWv7oheVA5N7kJPg0clFhDWvR7fgHA9W+L9tyUi8NAro02+n6hSnJQx9EtWbtLyYGP9pZJeqwjgxtpdtZDTnWTpMxwTqFHbebElUS1YyRHOkr/94kpFB4FxdrujPFNCv/udvJh1fWLTloEiJgX853WHUzQLbMCddeFjELaZgZIYZlVylW+h3jB6S0pGrb+RYmeJY4GNHcGpz7oz0DzV7zZVh4UMKQiQTQE/P04iysJo0Y7p01na+evgNxX3rQamzP5V2P40TfCJhdM+A/DiTsJ0C2B2PhMz46DdzuRngFWF831fg16lgqaIl00jK5UR4iIxNLdGmwlwxpWO5K5KXE/ETcVdKcqPI6IX8alvHbJJhNXUmV7znA2fRIeXY1w1VLMxTdgVgCa4e7+saXrcdIxSK5B7LcAHaV2/vX/6aFO29Kd2lxjlO7qHGjgOAw3rxz2EqA9m06VH4MsOvhNOgme1nPAH2aICN8ZphN2RSE0GqbCccKi8uTfZL943ZcZApgPXNImEMneiXr2gw62Z7zAs7rYXRE1bRby/VugLmR1G95SH6Zyy0n2nXoG0JFyNkMIWGIByoFTjodx1Jw5Pq1xheHi+14UIqHHWwVEj7mpREpBAOcAC0Ul2L1OJtmVq826WJaBzBf+zfoUr7upW4ZK1uUa2f2XFoKUxsxIBF8FosKiMRnqM6MBHveLJnQD2bQXwd4EYCPSc8m5aPBYDfNIwjqLmtIn1taFYJ6V7S78WTgzT5q+Uhj/rLA7g33JYSiD8YS2ahoKFJpNpNEDv9Xl/cQRfkbvikNVNfXy/MG9S7jgGrwnx6epnTFZJMKRXpOqmx+7ImrhEk9Xmji0QR9Roo3AY0u4x4ZhNxJJVjoD3nHlzuFAy3+kpatHdC2l1HOe7fR0QygIrG1Tg44vXMi4igxA2hwK+7MKwldLsQ/bgMO1FMkYkhnH+N3tFj2rEXBhsXLiU3+Diwz44U8mjoegYu7GDQwLSsOtWTkILUJgmdnQcWOwbepy1/2qikglXWmNZj0ekLaOIO+bUq3hn9LP6nBWf4jmMZP1jUI/XvBKw1bCvbCb0dzxFdwLsoiHyAEqVdzdeyhjDdQyAYYzy0V6ASyzHg9lMZXgYdwY0ao+lJaR+mNcmGpQ7YNugIC/cpcZ2lGCN/4sZCcDM6zguWhkjuy9sVwLNGbvoFZQuEwZpcMxeKteQzKFZokXE5j/naVBlLaH4K+hBaAxbI=" };
3199
+
3200
+ // src/fixtures/trust-root-staging.json
3201
+ var trust_root_staging_default = { public_key_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" };
3202
+
3203
+ // src/fixtures/trust-root-prod.json
3204
+ var trust_root_prod_default = { public_key_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" };
3205
+
3206
+ // src/trustManifest.ts
3207
+ var MLDSA65_PUBLIC_KEY_SIZE3 = 1952;
3208
+ function selectTrustRootEnv() {
3209
+ const env = typeof process !== "undefined" && process.env && process.env.ALEUTIAN_ENV ? process.env.ALEUTIAN_ENV : "dev";
3210
+ if (env === "prod" || env === "staging" || env === "dev") {
3211
+ return env;
3212
+ }
3213
+ return "dev";
3214
+ }
3215
+ function loadTrustRootFixture(env) {
3216
+ const fixture = env === "prod" ? trust_root_prod_default : env === "staging" ? trust_root_staging_default : trust_root_dev_default;
3217
+ const b64 = fixture.public_key_b64;
3218
+ const raw = base64ToUint8(b64);
3219
+ if (raw.length !== MLDSA65_PUBLIC_KEY_SIZE3) {
3220
+ throw new Error(
3221
+ `verification: fixtures/trust-root-${env}.json yielded ${raw.length} bytes; want ${MLDSA65_PUBLIC_KEY_SIZE3}. Re-run 15n_fix_A4 Step 2 for ${env}.`
3222
+ );
3223
+ }
3224
+ return raw;
3225
+ }
3226
+ var ACTIVE_TRUST_ENV = selectTrustRootEnv();
3227
+ var TRUST_ROOT_PUBLIC_KEY = loadTrustRootFixture(
3228
+ ACTIVE_TRUST_ENV
3229
+ );
3230
+ var TrustManifestError = class extends Error {
3231
+ constructor(message, code) {
3232
+ super(message);
3233
+ this.code = code;
3234
+ this.name = "TrustManifestError";
3235
+ }
3236
+ };
3237
+ var ManifestMalformedError = class extends TrustManifestError {
3238
+ constructor(message) {
3239
+ super(message, "manifest_malformed");
3240
+ this.name = "ManifestMalformedError";
3241
+ }
3242
+ };
3243
+ var ManifestRootSignatureError = class extends TrustManifestError {
3244
+ constructor(message) {
3245
+ super(message, "manifest_root_signature");
3246
+ this.name = "ManifestRootSignatureError";
3247
+ }
3248
+ };
3249
+ function parseAndVerifyTrustManifest(raw) {
3250
+ const text = typeof raw === "string" ? raw : new TextDecoder().decode(raw);
3251
+ return parseVerifyAndIndex(text);
3252
+ }
3253
+ function parseVerifyAndIndex(rawText) {
3254
+ let parsed;
3255
+ try {
3256
+ parsed = JSON.parse(rawText);
3257
+ } catch (e) {
3258
+ throw new ManifestMalformedError(
3259
+ `JSON parse: ${e.message}`
3260
+ );
3261
+ }
3262
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3263
+ throw new ManifestMalformedError("manifest root is not a JSON object");
3264
+ }
3265
+ const body = parsed;
3266
+ const sigB64 = body.root_signature_b64;
3267
+ if (!sigB64 || typeof sigB64 !== "string") {
3268
+ throw new ManifestMalformedError(
3269
+ "missing or non-string root_signature_b64"
3270
+ );
3271
+ }
3272
+ let sig;
3273
+ try {
3274
+ sig = base64ToUint8(sigB64);
3275
+ } catch (e) {
3276
+ throw new ManifestMalformedError(
3277
+ `root_signature_b64 not base64: ${e.message}`
3278
+ );
3279
+ }
3280
+ const bodySansSig = { ...body };
3281
+ delete bodySansSig.root_signature_b64;
3282
+ const canonical = canonicalJson(bodySansSig);
3283
+ const canonicalBytes = new TextEncoder().encode(canonical);
3284
+ try {
3285
+ const ok = ml_dsa65.verify(sig, canonicalBytes, TRUST_ROOT_PUBLIC_KEY);
3286
+ if (!ok) {
3287
+ throw new Error("ml_dsa65.verify returned false");
3288
+ }
3289
+ } catch (e) {
3290
+ throw new ManifestRootSignatureError(
3291
+ `verify against pinned root: ${e.message ?? e}`
3292
+ );
3293
+ }
3294
+ const platformKeys = (body.platform_keys ?? []).map(parseEntry);
3295
+ const tenantKeys = (body.tenant_keys ?? []).map(parseEntry);
3296
+ const keys = /* @__PURE__ */ new Map();
3297
+ for (const e of [...platformKeys, ...tenantKeys]) {
3298
+ if (e.revokedAtMs !== null) continue;
3299
+ if (!e.publicKeyB64) continue;
3300
+ try {
3301
+ keys.set(e.keyId, base64ToUint8(e.publicKeyB64));
3302
+ } catch (err) {
3303
+ throw new ManifestMalformedError(
3304
+ `entry ${e.keyId}: public_key_b64 not base64: ${err.message}`
3305
+ );
3306
+ }
3307
+ }
3308
+ const generatedAtMs = numberFromBody(body.generated_at_ms);
3309
+ const manifest = Object.freeze({
3310
+ version: numberFromBody(body.version),
3311
+ generatedAt: new Date(generatedAtMs),
3312
+ env: stringFromBody(body.env),
3313
+ approvedBy: stringFromBody(body.approved_by),
3314
+ rootKeyId: stringFromBody(body.root_key_id),
3315
+ schemaNote: stringFromBody(body.schema_note),
3316
+ platformKeys: Object.freeze(platformKeys),
3317
+ tenantKeys: Object.freeze(tenantKeys),
3318
+ publicKey(keyId) {
3319
+ const k = keys.get(keyId);
3320
+ return k ? new Uint8Array(k) : void 0;
3321
+ },
3322
+ keyIds() {
3323
+ return Array.from(keys.keys()).sort();
3324
+ }
3325
+ });
3326
+ return manifest;
3327
+ }
3328
+ function parseEntry(raw) {
3329
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
3330
+ throw new ManifestMalformedError("entry is not a JSON object");
3331
+ }
3332
+ const e = raw;
3333
+ return Object.freeze({
3334
+ keyId: stringFromBody(e.key_id),
3335
+ kmsKeyVersion: stringFromBody(e.kms_key_version),
3336
+ publicKeyB64: stringFromBody(e.public_key_b64),
3337
+ algorithm: stringFromBody(e.algorithm),
3338
+ addedAtMs: numberFromBody(e.added_at_ms),
3339
+ revokedAtMs: e.revoked_at_ms == null ? null : numberFromBody(e.revoked_at_ms),
3340
+ notAfterMs: e.not_after_ms == null ? null : numberFromBody(e.not_after_ms),
3341
+ tenantId: stringFromBody(e.tenant_id)
3342
+ });
3343
+ }
3344
+ function stringFromBody(v) {
3345
+ if (typeof v === "string") return v;
3346
+ if (v == null) return "";
3347
+ return String(v);
3348
+ }
3349
+ function numberFromBody(v) {
3350
+ if (typeof v === "number") return v;
3351
+ if (typeof v === "string") {
3352
+ const n = Number(v);
3353
+ if (Number.isFinite(n)) return n;
3354
+ }
3355
+ return 0;
3356
+ }
3357
+ function canonicalJson(value) {
3358
+ return writeCanonical(value);
3359
+ }
3360
+ function writeCanonical(v) {
3361
+ if (v === null) return "null";
3362
+ if (typeof v === "boolean") return v ? "true" : "false";
3363
+ if (typeof v === "number") {
3364
+ if (!Number.isFinite(v)) {
3365
+ throw new ManifestMalformedError(
3366
+ `canonicalJson: non-finite number ${v}`
3367
+ );
3368
+ }
3369
+ return JSON.stringify(v);
3370
+ }
3371
+ if (typeof v === "string") {
3372
+ return JSON.stringify(v);
3373
+ }
3374
+ if (Array.isArray(v)) {
3375
+ return "[" + v.map(writeCanonical).join(",") + "]";
3376
+ }
3377
+ if (typeof v === "object") {
3378
+ const obj = v;
3379
+ const keys = Object.keys(obj).sort();
3380
+ const parts = keys.map(
3381
+ (k) => JSON.stringify(k) + ":" + writeCanonical(obj[k])
3382
+ );
3383
+ return "{" + parts.join(",") + "}";
3384
+ }
3385
+ throw new ManifestMalformedError(
3386
+ `canonicalJson: unsupported type ${typeof v}`
3387
+ );
3388
+ }
3389
+ function base64ToUint8(b64) {
3390
+ if (typeof atob === "function") {
3391
+ const bin = atob(b64);
3392
+ const out = new Uint8Array(bin.length);
3393
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
3394
+ return out;
3395
+ }
3396
+ const buf = globalThis.Buffer.from(b64, "base64");
3397
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
3398
+ }
3399
+ var PROOF_BUNDLE_EXIT = Object.freeze({
3400
+ ok: 0,
3401
+ failed: 1,
3402
+ usage: 2,
3403
+ unreadable: 3,
3404
+ unauthenticated: 4,
3405
+ internal: 5
3406
+ });
3407
+ var PROCESS_IO = {
3408
+ stdout: (text) => void process.stdout.write(text),
3409
+ stderr: (text) => void process.stderr.write(text)
3410
+ };
3411
+ var USAGE = [
3412
+ "usage: aleutian-verify verify-proof-bundle <bundle.json> [--checkpoint-key F]... [--record-key F]... [--json]",
3413
+ "",
3414
+ "Verify a proof sink export bundle (aleutian.proof.bundle.v1 or .v2) under the keys you trust.",
3415
+ " --checkpoint-key F a PEM public key trusted for checkpoints (repeatable)",
3416
+ " --record-key F a PEM public key trusted for record signatures (repeatable)",
3417
+ " --json print exactly the aleutian.proof.bundle-result.v1 JSON",
3418
+ "Exit 0 ok, 1 failed, 2 usage, 3 unreadable bundle, 4 unauthenticated, 5 internal error.",
3419
+ ""
3420
+ ].join("\n");
3421
+ var LIMITS_LINE = "Limits (bundle-format \xA71): a pass does not prove that the bundle holds every chain, that a chain is complete to its end, that erased content is gone anywhere, that timestamps are true time, or which sink a chain came from; it proves nothing against a writer who controls the evidence and every key you trust, and nothing about what happened after the export.";
3422
+ var UsageError = class extends Error {
3423
+ };
3424
+ var MAX_KEY_FILE_BYTES = 16 * 1024;
3425
+ function parseArgs(args) {
3426
+ let bundlePath;
3427
+ const checkpointKeyPaths = [];
3428
+ const recordKeyPaths = [];
3429
+ let json = false;
3430
+ for (let i = 0; i < args.length; i++) {
3431
+ const arg = args[i];
3432
+ const value = () => {
3433
+ const v = args[i + 1];
3434
+ if (v === void 0) throw new UsageError(`${arg} requires a file`);
3435
+ i++;
3436
+ return v;
3437
+ };
3438
+ if (arg === "--checkpoint-key") checkpointKeyPaths.push(value());
3439
+ else if (arg === "--record-key") recordKeyPaths.push(value());
3440
+ else if (arg === "--json") json = true;
3441
+ else if (arg.startsWith("-")) throw new UsageError(`unknown flag: ${arg}`);
3442
+ else if (bundlePath === void 0) bundlePath = arg;
3443
+ else throw new UsageError("exactly one bundle file is required");
3444
+ }
3445
+ if (bundlePath === void 0) throw new UsageError("a bundle file is required");
3446
+ return { bundlePath, checkpointKeyPaths, recordKeyPaths, json };
3447
+ }
3448
+ async function loadKey(path) {
3449
+ let bytes;
3450
+ try {
3451
+ const info = await stat(path);
3452
+ if (!info.isFile()) throw new UsageError(`key file is not a regular file: ${path}`);
3453
+ if (info.size > MAX_KEY_FILE_BYTES) {
3454
+ throw new UsageError(
3455
+ `key file is larger than ${MAX_KEY_FILE_BYTES} bytes: ${path}`
3456
+ );
3457
+ }
3458
+ bytes = await readFile(path);
3459
+ } catch (err) {
3460
+ if (err instanceof UsageError) throw err;
3461
+ throw new UsageError(`cannot read key file ${path}`);
3462
+ }
3463
+ if (bytes.length > MAX_KEY_FILE_BYTES) {
3464
+ throw new UsageError(`key file is larger than ${MAX_KEY_FILE_BYTES} bytes: ${path}`);
3465
+ }
3466
+ try {
3467
+ return parseProofPublicKeyFile(bytes);
3468
+ } catch (err) {
3469
+ if (err instanceof ProofKeyFileError)
3470
+ throw new UsageError(`invalid key file ${path}: ${err.message}`);
3471
+ throw err;
3472
+ }
3473
+ }
3474
+ async function loadBundle(path) {
3475
+ const limit = PROOF_BUNDLE_LIMITS.bundleBytes;
3476
+ let handle;
3477
+ try {
3478
+ handle = await open(path, constants.O_RDONLY | (constants.O_NONBLOCK ?? 0));
3479
+ } catch {
3480
+ throw new UsageError(`cannot read bundle file ${path}`);
3481
+ }
3482
+ try {
3483
+ const info = await handle.stat();
3484
+ if (!info.isFile()) throw new UsageError(`not a regular file: ${path}`);
3485
+ if (info.size > limit) return null;
3486
+ const cap = limit + 1;
3487
+ let buf = new Uint8Array(Math.min(info.size + 1, cap));
3488
+ let n = 0;
3489
+ for (; ; ) {
3490
+ if (n === buf.length) {
3491
+ if (buf.length >= cap) break;
3492
+ const grown = new Uint8Array(Math.min(buf.length * 2, cap));
3493
+ grown.set(buf);
3494
+ buf = grown;
3495
+ }
3496
+ const { bytesRead } = await handle.read(buf, n, buf.length - n, null);
3497
+ if (bytesRead === 0) break;
3498
+ n += bytesRead;
3499
+ }
3500
+ return n > limit ? null : buf.subarray(0, n);
3501
+ } catch (err) {
3502
+ if (err instanceof UsageError) throw err;
3503
+ throw new UsageError(`cannot read bundle file ${path}`);
3504
+ } finally {
3505
+ await handle.close().catch(() => void 0);
3506
+ }
3507
+ }
3508
+ function exitCode(result) {
3509
+ if (!result.readable) return PROOF_BUNDLE_EXIT.unreadable;
3510
+ if (result.verdict === "failed") return PROOF_BUNDLE_EXIT.failed;
3511
+ if (result.verdict === "unauthenticated") return PROOF_BUNDLE_EXIT.unauthenticated;
3512
+ return PROOF_BUNDLE_EXIT.ok;
3513
+ }
3514
+ function problemsText(problems, indexLabel) {
3515
+ return problems.map(
3516
+ (p) => `${p.code} x${p.count}${p.first === null ? "" : ` (first at ${indexLabel} ${p.first})`}`
3517
+ ).join(", ");
3518
+ }
3519
+ function chainLine(key, r) {
3520
+ const c = r.counts;
3521
+ const lines = [
3522
+ ` ${key}: ${r.verdict.toUpperCase()}, erasure ${r.erasure}; ${c.entries} entries (${c.authenticated} authenticated, ${c.unauthenticated} unauthenticated), ${c.checkpoints} checkpoints, ${c.unanchored} unanchored`
3523
+ ];
3524
+ if (r.problems.length > 0) {
3525
+ lines.push(` problems: ${problemsText(r.problems, "index")}`);
3526
+ }
3527
+ return lines;
3528
+ }
3529
+ function unreadableText(reason, trust) {
3530
+ return [
3531
+ `Proof bundle: UNREADABLE (${reason})`,
3532
+ `Checkpoint trust: NOT CHECKED (the bundle is unreadable; ${trust.checkpointKeys.length} key(s) supplied)`,
3533
+ `Record trust: NOT CHECKED (the bundle is unreadable; ${trust.recordKeys.length} key(s) supplied)`,
3534
+ LIMITS_LINE,
3535
+ ""
3536
+ ].join("\n");
3537
+ }
3538
+ function humanReadable(result, trust) {
3539
+ const chains = Object.entries(result.chains);
3540
+ const signaturesPresent = chains.reduce(
3541
+ (n, [, r]) => n + r.counts.record_signatures_present,
3542
+ 0
3543
+ );
3544
+ const tally = { ok: 0, unauthenticated: 0, failed: 0 };
3545
+ for (const [, r] of chains) tally[r.verdict]++;
3546
+ const lines = [`Proof bundle: ${result.verdict.toUpperCase()}`];
3547
+ lines.push(
3548
+ result.checkpoint_trust === "checked" ? `Checkpoint trust: CHECKED (${trust.checkpointKeys.length} key(s))` : "Checkpoint trust: NOT CHECKED (checkpoints checked for structure only; they authenticate nothing)"
3549
+ );
3550
+ lines.push(
3551
+ result.record_trust === "checked" ? `Record trust: CHECKED (${trust.recordKeys.length} key(s))` : `Record trust: NOT CHECKED (${signaturesPresent} entries carry record signatures; none were verified)`
3552
+ );
3553
+ if (result.bundle_problems.length > 0) {
3554
+ lines.push(`Bundle problems: ${problemsText(result.bundle_problems, "chain")}`);
3555
+ }
3556
+ lines.push(
3557
+ `Chains: ${chains.length} (ok ${tally.ok}, unauthenticated ${tally.unauthenticated}, failed ${tally.failed})`
3558
+ );
3559
+ if (chains.length === 0) lines.push(" (no chains: an empty bundle proves nothing)");
3560
+ for (const [key, r] of chains) lines.push(...chainLine(key, r));
3561
+ lines.push(LIMITS_LINE);
3562
+ return lines.join("\n") + "\n";
3563
+ }
3564
+ async function runVerifyProofBundle(argv, io = PROCESS_IO) {
3565
+ if (argv.includes("--help") || argv.includes("-h")) {
3566
+ io.stdout(USAGE);
3567
+ return PROOF_BUNDLE_EXIT.ok;
3568
+ }
3569
+ try {
3570
+ const args = parseArgs(argv);
3571
+ const trust = {
3572
+ checkpointKeys: await Promise.all(args.checkpointKeyPaths.map(loadKey)),
3573
+ recordKeys: await Promise.all(args.recordKeyPaths.map(loadKey))
3574
+ };
3575
+ const bytes = await loadBundle(args.bundlePath);
3576
+ for (const id of await keysTrustedForBothRoles(trust)) {
3577
+ io.stderr(
3578
+ `warning: key ${id} is trusted for both checkpoints and records; one key for both roles means one compromise forges both
3579
+ `
3580
+ );
3581
+ }
3582
+ const result = bytes === null ? { result: PROOF_BUNDLE_RESULT_ID, readable: false, reason: "too-large" } : await verifyProofBundle(bytes, trust);
3583
+ if (args.json) {
3584
+ io.stdout(JSON.stringify(result) + "\n");
3585
+ } else if (!result.readable) {
3586
+ io.stdout(unreadableText(result.reason, trust));
3587
+ } else {
3588
+ io.stdout(humanReadable(result, trust));
3589
+ }
3590
+ return exitCode(result);
3591
+ } catch (err) {
3592
+ if (err instanceof UsageError) {
3593
+ io.stderr(`${err.message}
3594
+ ${USAGE}`);
3595
+ return PROOF_BUNDLE_EXIT.usage;
3596
+ }
3597
+ io.stderr("internal error in the verifier: no verdict on the bundle\n");
3598
+ return PROOF_BUNDLE_EXIT.internal;
3599
+ }
3600
+ }
3601
+
3602
+ // src/cli/verifyBundle.ts
3603
+ var INTERFACE_NAME = "js-sdk";
3604
+ var EXIT_VALID = 0;
3605
+ var EXIT_INVALID = 1;
3606
+ var EXIT_USAGE = 2;
3607
+ var EXIT_IO_OR_PARSE = 3;
3608
+ async function main(argv) {
3609
+ if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
3610
+ printUsage();
3611
+ return argv[0] ? EXIT_VALID : EXIT_USAGE;
3612
+ }
3613
+ if (argv[0] === "verify-proof-bundle") {
3614
+ return runVerifyProofBundle(argv.slice(1));
3615
+ }
3616
+ if (argv[0] !== "verify") {
3617
+ process.stderr.write(`unknown command: ${argv[0]}
3618
+ `);
3619
+ printUsage();
3620
+ return EXIT_USAGE;
3621
+ }
3622
+ let parsed;
3623
+ try {
3624
+ parsed = parseArgs2(argv.slice(1));
3625
+ } catch (err) {
3626
+ process.stderr.write(`${err.message}
3627
+ `);
3628
+ printUsage();
3629
+ return EXIT_USAGE;
3630
+ }
3631
+ return runVerify(parsed);
3632
+ }
3633
+ async function runVerify(args) {
3634
+ const bundleSha = await safeSha256(args.entriesPath);
3635
+ const manifestBlock = await loadManifestAdvisory(args.manifestPath);
3636
+ let anchor;
3637
+ let entries;
3638
+ try {
3639
+ anchor = await loadAnchor(args.anchorPath);
3640
+ entries = await loadExportedEntries(args.entriesPath);
3641
+ } catch (err) {
3642
+ emitResult({
3643
+ status: "invalid",
3644
+ chain_length: 0,
3645
+ bundle_sha256: bundleSha,
3646
+ reason: `load: ${err.message}`,
3647
+ manifest: manifestBlock
3648
+ });
3649
+ return EXIT_IO_OR_PARSE;
3650
+ }
3651
+ let result;
3652
+ try {
3653
+ result = await verifyChainOffline(entries, {
3654
+ anchor,
3655
+ previousAnchorHash: args.previousAnchorHash,
3656
+ maxEntries: args.maxEntries,
3657
+ maxBreaks: 100
3658
+ });
3659
+ } catch (err) {
3660
+ emitResult({
3661
+ status: "invalid",
3662
+ chain_length: 0,
3663
+ bundle_sha256: bundleSha,
3664
+ reason: `verify: ${err.message}`,
3665
+ manifest: manifestBlock
3666
+ });
3667
+ return EXIT_IO_OR_PARSE;
3668
+ }
3669
+ const exposed = result.tombstonesFound > 0 ? {
3670
+ tombstones: result.tombstonesFound,
3671
+ entries_not_bound: result.entriesNotVerified,
3672
+ note: notVerifiedNote(result.entriesNotVerified, result.valid)
3673
+ } : {};
3674
+ if (result.valid) {
3675
+ emitResult({
3676
+ status: "valid",
3677
+ chain_length: result.entriesProcessed,
3678
+ bundle_sha256: bundleSha,
3679
+ manifest: manifestBlock,
3680
+ ...exposed
3681
+ });
3682
+ return EXIT_VALID;
3683
+ }
3684
+ const firstBreak = result.breaks[0];
3685
+ const reason = firstBreak ? `${firstBreak.breakType} at position ${firstBreak.position}` : "unspecified verification failure";
3686
+ emitResult({
3687
+ status: "invalid",
3688
+ chain_length: result.entriesProcessed,
3689
+ bundle_sha256: bundleSha,
3690
+ reason,
3691
+ breaks: result.breaks.map((b) => ({
3692
+ position: b.position,
3693
+ entry_id: b.entryId,
3694
+ break_type: b.breakType
3695
+ })),
3696
+ manifest: manifestBlock,
3697
+ ...exposed
3698
+ });
3699
+ return EXIT_INVALID;
3700
+ }
3701
+ function notVerifiedNote(entriesNotVerified, valid) {
3702
+ const which = entriesNotVerified === 1 ? "entry 0 (the last tombstone)" : `entries 0 to ${entriesNotVerified - 1} (up to and including the last tombstone)`;
3703
+ const are = entriesNotVerified === 1 ? "is" : "are";
3704
+ return valid ? `${which} ${are} NOT verified and NOT bound by the anchor; status covers only the entries after` : `${which} ${are} NOT verified and NOT bound by the anchor`;
3705
+ }
3706
+ async function loadManifestAdvisory(manifestPath) {
3707
+ if (manifestPath === void 0) return void 0;
3708
+ let raw;
3709
+ try {
3710
+ raw = await readFile(manifestPath, "utf-8");
3711
+ } catch (err) {
3712
+ return { error: `read manifest: ${err.message}` };
3713
+ }
3714
+ try {
3715
+ const manifest = parseAndVerifyTrustManifest(raw);
3716
+ const f = evaluateManifestFreshness(
3717
+ manifest.version,
3718
+ manifest.generatedAt.getTime(),
3719
+ Date.now(),
3720
+ -1
3721
+ );
3722
+ return {
3723
+ version: f.version,
3724
+ generated_at: manifestGeneratedDate(f),
3725
+ age_days: f.ageDays,
3726
+ stale: f.stale,
3727
+ revocation_note: manifestRevocationNote(f)
3728
+ };
3729
+ } catch (err) {
3730
+ return { error: `verify manifest: ${err.message}` };
3731
+ }
3732
+ }
3733
+ function emitResult(r) {
3734
+ const out = {
3735
+ bundle_sha256: r.bundle_sha256,
3736
+ chain_length: r.chain_length,
3737
+ interface: INTERFACE_NAME,
3738
+ status: r.status
3739
+ };
3740
+ if (r.reason !== void 0) out.reason = r.reason;
3741
+ if (r.breaks && r.breaks.length > 0) out.breaks = r.breaks;
3742
+ if (r.manifest !== void 0) out.manifest = r.manifest;
3743
+ if (r.tombstones !== void 0) out.tombstones = r.tombstones;
3744
+ if (r.entries_not_bound !== void 0) out.entries_not_bound = r.entries_not_bound;
3745
+ if (r.note !== void 0) out.note = r.note;
3746
+ const sortedKeys = Object.keys(out).sort();
3747
+ const sorted = {};
3748
+ for (const k of sortedKeys) sorted[k] = out[k];
3749
+ process.stdout.write(`${JSON.stringify(sorted)}
3750
+ `);
3751
+ }
3752
+ function parseArgs2(args) {
3753
+ let anchorPath;
3754
+ let entriesPath;
3755
+ let previousAnchorHash = "";
3756
+ let maxEntries = 0;
3757
+ let manifestPath;
3758
+ for (let i = 0; i < args.length; i++) {
3759
+ const arg = args[i];
3760
+ const next = () => {
3761
+ const v = args[i + 1];
3762
+ if (v === void 0) throw new Error(`${arg} requires a value`);
3763
+ i++;
3764
+ return v;
3765
+ };
3766
+ switch (arg) {
3767
+ case "--anchor":
3768
+ anchorPath = next();
3769
+ break;
3770
+ case "--entries":
3771
+ entriesPath = next();
3772
+ break;
3773
+ case "--previous-anchor-hash":
3774
+ previousAnchorHash = next();
3775
+ break;
3776
+ case "--max-entries":
3777
+ maxEntries = Number.parseInt(next(), 10);
3778
+ if (Number.isNaN(maxEntries) || maxEntries < 0) {
3779
+ throw new Error("--max-entries must be a non-negative integer");
3780
+ }
3781
+ break;
3782
+ case "--manifest":
3783
+ manifestPath = next();
3784
+ break;
3785
+ default:
3786
+ throw new Error(`unknown flag: ${arg}`);
3787
+ }
3788
+ }
3789
+ if (!anchorPath) throw new Error("--anchor is required");
3790
+ if (!entriesPath) throw new Error("--entries is required");
3791
+ return {
3792
+ anchorPath,
3793
+ entriesPath,
3794
+ previousAnchorHash,
3795
+ maxEntries,
3796
+ manifestPath
3797
+ };
3798
+ }
3799
+ function printUsage() {
3800
+ process.stderr.write(
3801
+ [
3802
+ "usage: aleutian-verify verify --anchor PATH --entries PATH [--max-entries N]",
3803
+ "",
3804
+ "Verify an extracted chain export (anchor + entries JSON/JSONL).",
3805
+ "Emits a single JSON line to stdout matching the locked contract.",
3806
+ "Exit 0 = valid, 1 = invalid, 2 = usage error, 3 = IO/parse error.",
3807
+ "",
3808
+ "usage: aleutian-verify verify-proof-bundle <bundle.json> [--checkpoint-key F]... [--record-key F]... [--json]",
3809
+ "",
3810
+ "Verify a proof sink export bundle under the keys you trust (see --help on the verb).",
3811
+ ""
3812
+ ].join("\n")
3813
+ );
3814
+ }
3815
+ async function safeSha256(path) {
3816
+ try {
3817
+ const h = createHash("sha256");
3818
+ await new Promise((resolve, reject) => {
3819
+ const stream = createReadStream(path);
3820
+ stream.on("data", (chunk) => h.update(chunk));
3821
+ stream.on("end", () => resolve());
3822
+ stream.on("error", (err) => reject(err));
3823
+ });
3824
+ return h.digest("hex");
3825
+ } catch {
3826
+ return "";
3827
+ }
3828
+ }
3829
+
3830
+ // src/cli/aleutian-verify.ts
3831
+ main(process.argv.slice(2)).then(
3832
+ (code) => {
3833
+ process.exitCode = code;
3834
+ },
3835
+ () => {
3836
+ process.stderr.write("internal error in the verifier\n");
3837
+ process.exitCode = 5;
3838
+ }
3839
+ );
3840
+ //# sourceMappingURL=aleutian-verify.js.map
3841
+ //# sourceMappingURL=aleutian-verify.js.map