@orangecheck/stamp-ots 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OrangeCheck
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # @orangecheck/stamp-ots
2
+
3
+ OpenTimestamps calendar client, proof helpers, and anchor-verification hooks for [OC Stamp](https://github.com/orangecheck/oc-stamp-protocol) envelopes.
4
+
5
+ ## Install
6
+
7
+ ```
8
+ npm i @orangecheck/stamp-core @orangecheck/stamp-ots
9
+ ```
10
+
11
+ ## What it does
12
+
13
+ OC Stamp envelopes carry an optional `ots` field that anchors the envelope `id` to a Bitcoin block via the [OpenTimestamps](https://opentimestamps.org) protocol. This package provides the thin client-side glue:
14
+
15
+ - **`submitToCalendars(id, opts)`** — POST the envelope's 32-byte digest to one or more OTS calendars. Returns a pending `OtsProof` you can fold into the envelope's `ots` field.
16
+ - **`upgradeProof(proof, id, opts)`** — poll calendars for an upgraded proof once OTS has anchored the containing batch to a Bitcoin block. Returns a confirmed `OtsProof` when available.
17
+ - **`createCalendarClient(url)`** — low-level HTTP client implementing the minimal OTS calendar API (`POST /digest`, `GET /timestamp/<hex>`).
18
+ - **`makeAnchorVerifier({ walkProof, headerSource })`** — adapter that turns a proof-parser plus a block-header source into a function `verify()` (from `@orangecheck/stamp-core`) can call via its `verifyOtsAnchor` parameter.
19
+
20
+ ## What it does NOT do
21
+
22
+ This package does **not** ship a full OpenTimestamps proof parser. Parsing the binary proof format (Merkle path chunks, calendar attestations, Bitcoin attestations) requires a larger, maintained library like `javascript-opentimestamps`. We keep this package's dependency surface narrow and expose `walkProof` as a plug-in point.
23
+
24
+ Consumers who want fully-offline verification should combine:
25
+
26
+ ```ts
27
+ import { verify } from '@orangecheck/stamp-core';
28
+ import { makeAnchorVerifier, adaptAnchorVerifier, base64Decode, hexDecode } from '@orangecheck/stamp-ots';
29
+ import { myOtsParser, myHeaderSource } from './your-adapters';
30
+
31
+ const anchor = makeAnchorVerifier({
32
+ walkProof: myOtsParser,
33
+ headerSource: myHeaderSource,
34
+ });
35
+
36
+ const result = await verify({
37
+ envelope: env,
38
+ verifyOtsAnchor: adaptAnchorVerifier(anchor, (blockHash) => hexDecode(env.id)),
39
+ verifyBip322: myBip322Verifier,
40
+ });
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ### Submit an envelope to calendars
46
+
47
+ ```ts
48
+ import { stamp } from '@orangecheck/stamp-core';
49
+ import { submitToCalendars, toStampOts } from '@orangecheck/stamp-ots';
50
+
51
+ const env = await stamp({ /* ... */ });
52
+ const proof = await submitToCalendars(env.id); // uses DEFAULT_CALENDARS
53
+ const envWithOts = { ...env, ots: toStampOts(proof) };
54
+ // envWithOts now has a pending OTS proof.
55
+ ```
56
+
57
+ ### Upgrade later
58
+
59
+ ```ts
60
+ import { upgradeProof, fromStampOts, toStampOts } from '@orangecheck/stamp-ots';
61
+
62
+ const current = fromStampOts(envWithOts.ots!);
63
+ const upgraded = await upgradeProof(current, envWithOts.id, {
64
+ parseAnchor: myOtsParser, // plug in your OTS proof parser
65
+ });
66
+ const envUpgraded = { ...envWithOts, ots: toStampOts(upgraded) };
67
+ ```
68
+
69
+ ## Exports
70
+
71
+ - `submitToCalendars(id, opts)` / `upgradeProof(proof, id, opts)`
72
+ - `createCalendarClient(url, opts)` / `DEFAULT_CALENDARS`
73
+ - `makeAnchorVerifier(config)` / `adaptAnchorVerifier(verifier, digestLookup)`
74
+ - `toStampOts(proof)` / `fromStampOts(stampOts)` — shape adapters
75
+ - `base64Encode` / `base64Decode` / `hexEncode` / `hexDecode`
76
+ - Types: `OtsProof`, `CalendarClient`, `AnchorVerifier`, `BlockHeaderSource`, etc.
77
+
78
+ See [`SPEC.md` §6](https://github.com/orangecheck/oc-stamp-protocol/blob/main/SPEC.md#6-opentimestamps-integration) for the normative OTS integration.
@@ -0,0 +1,83 @@
1
+ import { StampOts } from '@orangecheck/stamp-core';
2
+
3
+ type OtsProofStatus = 'pending' | 'confirmed';
4
+ interface OtsProof {
5
+ status: OtsProofStatus;
6
+ proof: string;
7
+ calendars: string[];
8
+ blockHeight: number | null;
9
+ blockHash: string | null;
10
+ upgradedAt: string | null;
11
+ }
12
+ interface CalendarClient {
13
+ readonly url: string;
14
+ submit(digest: Uint8Array, signal?: AbortSignal): Promise<Uint8Array>;
15
+ fetchProof(digest: Uint8Array, signal?: AbortSignal): Promise<Uint8Array | null>;
16
+ }
17
+ interface AnchorVerificationInput {
18
+ proofBytes: Uint8Array;
19
+ digest: Uint8Array;
20
+ blockHeight: number;
21
+ blockHash: string;
22
+ headerSource?: BlockHeaderSource;
23
+ }
24
+ interface BlockHeaderSource {
25
+ getHeaderAt(height: number): Promise<Uint8Array | null>;
26
+ }
27
+ type AnchorVerifier = (input: AnchorVerificationInput) => Promise<AnchorVerificationResult>;
28
+ type AnchorVerificationResult = {
29
+ ok: true;
30
+ merkleRoot: string;
31
+ } | {
32
+ ok: false;
33
+ reason: string;
34
+ };
35
+ declare function toStampOts(p: OtsProof): StampOts;
36
+ declare function fromStampOts(s: StampOts): OtsProof;
37
+
38
+ declare function base64Encode(bytes: Uint8Array): string;
39
+ declare function base64Decode(s: string): Uint8Array;
40
+ declare function hexDecode(s: string): Uint8Array;
41
+ declare function hexEncode(bytes: Uint8Array): string;
42
+
43
+ declare const DEFAULT_CALENDARS: readonly string[];
44
+ interface HttpCalendarOptions {
45
+ fetch?: typeof fetch;
46
+ timeoutMs?: number;
47
+ }
48
+ declare function createCalendarClient(url: string, opts?: HttpCalendarOptions): CalendarClient;
49
+
50
+ interface SubmitOptions {
51
+ calendars?: readonly (string | CalendarClient)[];
52
+ minSuccesses?: number;
53
+ fetch?: typeof fetch;
54
+ timeoutMs?: number;
55
+ signal?: AbortSignal;
56
+ }
57
+ declare function submitToCalendars(idHex: string, opts?: SubmitOptions): Promise<OtsProof>;
58
+
59
+ interface UpgradeOptions {
60
+ parseAnchor: (proofBytes: Uint8Array) => Promise<{
61
+ blockHeight: number;
62
+ blockHash: string;
63
+ } | null>;
64
+ fetch?: typeof fetch;
65
+ timeoutMs?: number;
66
+ signal?: AbortSignal;
67
+ }
68
+ declare function upgradeProof(current: OtsProof, idHex: string, opts: UpgradeOptions): Promise<OtsProof>;
69
+
70
+ interface AnchorVerifierConfig {
71
+ walkProof: (input: {
72
+ proofBytes: Uint8Array;
73
+ digest: Uint8Array;
74
+ }) => Promise<{
75
+ merkleRoot: Uint8Array;
76
+ blockHeight: number;
77
+ } | null>;
78
+ headerSource: BlockHeaderSource;
79
+ }
80
+ declare function makeAnchorVerifier(cfg: AnchorVerifierConfig): AnchorVerifier;
81
+ declare function adaptAnchorVerifier(verifier: AnchorVerifier, digestLookup: (blockHash: string) => Uint8Array): (proofB64: string, blockHeight: number, blockHash: string) => Promise<boolean>;
82
+
83
+ export { type AnchorVerificationInput, type AnchorVerificationResult, type AnchorVerifier, type AnchorVerifierConfig, type BlockHeaderSource, type CalendarClient, DEFAULT_CALENDARS, type HttpCalendarOptions, type OtsProof, type OtsProofStatus, type SubmitOptions, type UpgradeOptions, adaptAnchorVerifier, base64Decode, base64Encode, createCalendarClient, fromStampOts, hexDecode, hexEncode, makeAnchorVerifier, submitToCalendars, toStampOts, upgradeProof };
@@ -0,0 +1,83 @@
1
+ import { StampOts } from '@orangecheck/stamp-core';
2
+
3
+ type OtsProofStatus = 'pending' | 'confirmed';
4
+ interface OtsProof {
5
+ status: OtsProofStatus;
6
+ proof: string;
7
+ calendars: string[];
8
+ blockHeight: number | null;
9
+ blockHash: string | null;
10
+ upgradedAt: string | null;
11
+ }
12
+ interface CalendarClient {
13
+ readonly url: string;
14
+ submit(digest: Uint8Array, signal?: AbortSignal): Promise<Uint8Array>;
15
+ fetchProof(digest: Uint8Array, signal?: AbortSignal): Promise<Uint8Array | null>;
16
+ }
17
+ interface AnchorVerificationInput {
18
+ proofBytes: Uint8Array;
19
+ digest: Uint8Array;
20
+ blockHeight: number;
21
+ blockHash: string;
22
+ headerSource?: BlockHeaderSource;
23
+ }
24
+ interface BlockHeaderSource {
25
+ getHeaderAt(height: number): Promise<Uint8Array | null>;
26
+ }
27
+ type AnchorVerifier = (input: AnchorVerificationInput) => Promise<AnchorVerificationResult>;
28
+ type AnchorVerificationResult = {
29
+ ok: true;
30
+ merkleRoot: string;
31
+ } | {
32
+ ok: false;
33
+ reason: string;
34
+ };
35
+ declare function toStampOts(p: OtsProof): StampOts;
36
+ declare function fromStampOts(s: StampOts): OtsProof;
37
+
38
+ declare function base64Encode(bytes: Uint8Array): string;
39
+ declare function base64Decode(s: string): Uint8Array;
40
+ declare function hexDecode(s: string): Uint8Array;
41
+ declare function hexEncode(bytes: Uint8Array): string;
42
+
43
+ declare const DEFAULT_CALENDARS: readonly string[];
44
+ interface HttpCalendarOptions {
45
+ fetch?: typeof fetch;
46
+ timeoutMs?: number;
47
+ }
48
+ declare function createCalendarClient(url: string, opts?: HttpCalendarOptions): CalendarClient;
49
+
50
+ interface SubmitOptions {
51
+ calendars?: readonly (string | CalendarClient)[];
52
+ minSuccesses?: number;
53
+ fetch?: typeof fetch;
54
+ timeoutMs?: number;
55
+ signal?: AbortSignal;
56
+ }
57
+ declare function submitToCalendars(idHex: string, opts?: SubmitOptions): Promise<OtsProof>;
58
+
59
+ interface UpgradeOptions {
60
+ parseAnchor: (proofBytes: Uint8Array) => Promise<{
61
+ blockHeight: number;
62
+ blockHash: string;
63
+ } | null>;
64
+ fetch?: typeof fetch;
65
+ timeoutMs?: number;
66
+ signal?: AbortSignal;
67
+ }
68
+ declare function upgradeProof(current: OtsProof, idHex: string, opts: UpgradeOptions): Promise<OtsProof>;
69
+
70
+ interface AnchorVerifierConfig {
71
+ walkProof: (input: {
72
+ proofBytes: Uint8Array;
73
+ digest: Uint8Array;
74
+ }) => Promise<{
75
+ merkleRoot: Uint8Array;
76
+ blockHeight: number;
77
+ } | null>;
78
+ headerSource: BlockHeaderSource;
79
+ }
80
+ declare function makeAnchorVerifier(cfg: AnchorVerifierConfig): AnchorVerifier;
81
+ declare function adaptAnchorVerifier(verifier: AnchorVerifier, digestLookup: (blockHash: string) => Uint8Array): (proofB64: string, blockHeight: number, blockHash: string) => Promise<boolean>;
82
+
83
+ export { type AnchorVerificationInput, type AnchorVerificationResult, type AnchorVerifier, type AnchorVerifierConfig, type BlockHeaderSource, type CalendarClient, DEFAULT_CALENDARS, type HttpCalendarOptions, type OtsProof, type OtsProofStatus, type SubmitOptions, type UpgradeOptions, adaptAnchorVerifier, base64Decode, base64Encode, createCalendarClient, fromStampOts, hexDecode, hexEncode, makeAnchorVerifier, submitToCalendars, toStampOts, upgradeProof };
package/dist/index.js ADDED
@@ -0,0 +1,326 @@
1
+ 'use strict';
2
+
3
+ // src/types.ts
4
+ function toStampOts(p) {
5
+ return {
6
+ status: p.status,
7
+ proof: p.proof,
8
+ calendars: p.calendars,
9
+ block_height: p.blockHeight,
10
+ block_hash: p.blockHash,
11
+ upgraded_at: p.upgradedAt
12
+ };
13
+ }
14
+ function fromStampOts(s) {
15
+ return {
16
+ status: s.status,
17
+ proof: s.proof,
18
+ calendars: s.calendars,
19
+ blockHeight: s.block_height,
20
+ blockHash: s.block_hash,
21
+ upgradedAt: s.upgraded_at
22
+ };
23
+ }
24
+
25
+ // src/base64.ts
26
+ var B64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
27
+ var B64_DECODE_TABLE = (() => {
28
+ const t = new Int16Array(128).fill(-1);
29
+ for (let i = 0; i < B64_CHARS.length; i++) t[B64_CHARS.charCodeAt(i)] = i;
30
+ t["=".charCodeAt(0)] = -2;
31
+ return t;
32
+ })();
33
+ function base64Encode(bytes) {
34
+ let out = "";
35
+ let i = 0;
36
+ for (; i + 2 < bytes.length; i += 3) {
37
+ const a = bytes[i];
38
+ const b = bytes[i + 1];
39
+ const c = bytes[i + 2];
40
+ out += B64_CHARS[a >> 2] + B64_CHARS[(a & 3) << 4 | b >> 4] + B64_CHARS[(b & 15) << 2 | c >> 6] + B64_CHARS[c & 63];
41
+ }
42
+ if (i < bytes.length) {
43
+ const a = bytes[i];
44
+ const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
45
+ out += B64_CHARS[a >> 2];
46
+ out += B64_CHARS[(a & 3) << 4 | b >> 4];
47
+ out += i + 1 < bytes.length ? B64_CHARS[(b & 15) << 2] : "=";
48
+ out += "=";
49
+ }
50
+ return out;
51
+ }
52
+ function base64Decode(s) {
53
+ const str = s.replace(/\s+/g, "");
54
+ const len = str.length;
55
+ if (len === 0) return new Uint8Array(0);
56
+ if (len % 4 !== 0) throw new Error("base64: bad length");
57
+ let padding = 0;
58
+ if (str[len - 1] === "=") padding++;
59
+ if (str[len - 2] === "=") padding++;
60
+ const outLen = len / 4 * 3 - padding;
61
+ const out = new Uint8Array(outLen);
62
+ let o = 0;
63
+ for (let i = 0; i < len; i += 4) {
64
+ const c0 = B64_DECODE_TABLE[str.charCodeAt(i)];
65
+ const c1 = B64_DECODE_TABLE[str.charCodeAt(i + 1)];
66
+ const c2 = B64_DECODE_TABLE[str.charCodeAt(i + 2)];
67
+ const c3 = B64_DECODE_TABLE[str.charCodeAt(i + 3)];
68
+ if (c0 < 0 || c1 < 0 || c2 < -2 || c3 < -2) throw new Error("base64: bad char");
69
+ out[o++] = c0 << 2 | c1 >> 4;
70
+ if (c2 >= 0) out[o++] = (c1 & 15) << 4 | c2 >> 2;
71
+ if (c3 >= 0) out[o++] = (c2 & 3) << 6 | c3;
72
+ }
73
+ return out;
74
+ }
75
+ function hexDecode(s) {
76
+ if (s.length % 2 !== 0) throw new Error("hex: odd length");
77
+ const out = new Uint8Array(s.length / 2);
78
+ for (let i = 0; i < out.length; i++) {
79
+ const hi = parseInt(s[i * 2], 16);
80
+ const lo = parseInt(s[i * 2 + 1], 16);
81
+ if (Number.isNaN(hi) || Number.isNaN(lo)) throw new Error("hex: bad char");
82
+ out[i] = hi << 4 | lo;
83
+ }
84
+ return out;
85
+ }
86
+ function hexEncode(bytes) {
87
+ const HEX = "0123456789abcdef";
88
+ let out = "";
89
+ for (let i = 0; i < bytes.length; i++) {
90
+ const b = bytes[i];
91
+ out += HEX[b >> 4 & 15] + HEX[b & 15];
92
+ }
93
+ return out;
94
+ }
95
+
96
+ // src/calendar.ts
97
+ var DEFAULT_CALENDARS = [
98
+ "https://alice.btc.calendar.opentimestamps.org",
99
+ "https://bob.btc.calendar.opentimestamps.org",
100
+ "https://finney.calendar.eternitywall.com"
101
+ ];
102
+ function createCalendarClient(url, opts = {}) {
103
+ const fetchImpl = opts.fetch ?? globalThis.fetch;
104
+ if (typeof fetchImpl !== "function") {
105
+ throw new Error(
106
+ "@orangecheck/stamp-ots: no fetch implementation available. Pass one via { fetch }."
107
+ );
108
+ }
109
+ const timeoutMs = opts.timeoutMs ?? 3e4;
110
+ const base = url.replace(/\/$/, "");
111
+ return {
112
+ url: base,
113
+ async submit(digest, signal) {
114
+ if (digest.byteLength !== 32) {
115
+ throw new Error("OTS: digest must be exactly 32 bytes");
116
+ }
117
+ const ac = withTimeout(signal, timeoutMs);
118
+ const resp = await fetchImpl(`${base}/digest`, {
119
+ method: "POST",
120
+ headers: { "Content-Type": "application/vnd.opentimestamps.v1" },
121
+ body: digest,
122
+ signal: ac.signal
123
+ });
124
+ ac.clear();
125
+ if (!resp.ok) {
126
+ throw new Error(`OTS calendar ${base} submit failed: HTTP ${resp.status}`);
127
+ }
128
+ const buf = await resp.arrayBuffer();
129
+ return new Uint8Array(buf);
130
+ },
131
+ async fetchProof(digest, signal) {
132
+ if (digest.byteLength !== 32) {
133
+ throw new Error("OTS: digest must be exactly 32 bytes");
134
+ }
135
+ const ac = withTimeout(signal, timeoutMs);
136
+ const resp = await fetchImpl(`${base}/timestamp/${hexEncode(digest)}`, {
137
+ method: "GET",
138
+ headers: { Accept: "application/vnd.opentimestamps.v1" },
139
+ signal: ac.signal
140
+ });
141
+ ac.clear();
142
+ if (resp.status === 404) return null;
143
+ if (!resp.ok) {
144
+ throw new Error(`OTS calendar ${base} fetchProof failed: HTTP ${resp.status}`);
145
+ }
146
+ const buf = await resp.arrayBuffer();
147
+ return new Uint8Array(buf);
148
+ }
149
+ };
150
+ }
151
+ function withTimeout(parent, ms) {
152
+ const ac = new AbortController();
153
+ const onParent = () => ac.abort(parent.reason);
154
+ if (parent) {
155
+ if (parent.aborted) ac.abort(parent.reason);
156
+ else parent.addEventListener("abort", onParent, { once: true });
157
+ }
158
+ const t = setTimeout(() => ac.abort(new Error(`OTS request timed out after ${ms}ms`)), ms);
159
+ return {
160
+ signal: ac.signal,
161
+ clear: () => {
162
+ clearTimeout(t);
163
+ if (parent) parent.removeEventListener("abort", onParent);
164
+ }
165
+ };
166
+ }
167
+
168
+ // src/submit.ts
169
+ async function submitToCalendars(idHex, opts = {}) {
170
+ const digest = hexDecode(idHex);
171
+ if (digest.byteLength !== 32) {
172
+ throw new Error("submitToCalendars: idHex must be 32 bytes (64 hex chars)");
173
+ }
174
+ const clients = (opts.calendars ?? DEFAULT_CALENDARS).map(
175
+ (c) => typeof c === "string" ? createCalendarClient(c, { fetch: opts.fetch, timeoutMs: opts.timeoutMs }) : c
176
+ );
177
+ if (clients.length === 0) {
178
+ throw new Error("submitToCalendars: at least one calendar is required");
179
+ }
180
+ const minSuccesses = Math.max(1, opts.minSuccesses ?? 1);
181
+ const settled = await Promise.allSettled(
182
+ clients.map(async (c) => {
183
+ const proof = await c.submit(digest, opts.signal);
184
+ return { url: c.url, proof };
185
+ })
186
+ );
187
+ const successes = settled.filter((r) => r.status === "fulfilled").map((r) => r.value);
188
+ const failures = settled.filter((r) => r.status === "rejected").map((r) => r.reason instanceof Error ? r.reason.message : String(r.reason));
189
+ if (successes.length < minSuccesses) {
190
+ const msg = `OTS submission did not reach ${minSuccesses} calendar(s). Errors: ${failures.join("; ")}`;
191
+ const err = new Error(msg);
192
+ err.errors = failures;
193
+ throw err;
194
+ }
195
+ const first = successes[0];
196
+ return {
197
+ status: "pending",
198
+ proof: base64Encode(first.proof),
199
+ calendars: successes.map((s) => s.url),
200
+ blockHeight: null,
201
+ blockHash: null,
202
+ upgradedAt: null
203
+ };
204
+ }
205
+
206
+ // src/upgrade.ts
207
+ async function upgradeProof(current, idHex, opts) {
208
+ if (current.status === "confirmed") return current;
209
+ const digest = hexDecode(idHex);
210
+ const clients = current.calendars.map(
211
+ (url) => createCalendarClient(url, { fetch: opts.fetch, timeoutMs: opts.timeoutMs })
212
+ );
213
+ if (clients.length === 0) {
214
+ throw new Error("upgradeProof: no calendars listed in current proof");
215
+ }
216
+ const attempts = await Promise.allSettled(
217
+ clients.map(async (c) => {
218
+ const proof = await c.fetchProof(digest, opts.signal);
219
+ if (!proof) return null;
220
+ const anchor = await opts.parseAnchor(proof);
221
+ return { url: c.url, proof, anchor };
222
+ })
223
+ );
224
+ for (const r of attempts) {
225
+ if (r.status !== "fulfilled" || r.value === null) continue;
226
+ const { url, proof, anchor } = r.value;
227
+ if (anchor) {
228
+ return {
229
+ status: "confirmed",
230
+ proof: base64Encode(proof),
231
+ calendars: uniqueFirst(current.calendars, url),
232
+ blockHeight: anchor.blockHeight,
233
+ blockHash: anchor.blockHash,
234
+ upgradedAt: (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z")
235
+ };
236
+ }
237
+ }
238
+ let longest = null;
239
+ for (const r of attempts) {
240
+ if (r.status !== "fulfilled" || r.value === null) continue;
241
+ if (!longest || r.value.proof.byteLength > longest.proof.byteLength) {
242
+ longest = { url: r.value.url, proof: r.value.proof };
243
+ }
244
+ }
245
+ if (longest) {
246
+ const currentBytes = base64Decode(current.proof);
247
+ if (longest.proof.byteLength > currentBytes.byteLength) {
248
+ return {
249
+ status: "pending",
250
+ proof: base64Encode(longest.proof),
251
+ calendars: uniqueFirst(current.calendars, longest.url),
252
+ blockHeight: null,
253
+ blockHash: null,
254
+ upgradedAt: null
255
+ };
256
+ }
257
+ }
258
+ return current;
259
+ }
260
+ function uniqueFirst(arr, first) {
261
+ const out = [first];
262
+ for (const x of arr) if (x !== first && !out.includes(x)) out.push(x);
263
+ return out;
264
+ }
265
+
266
+ // src/anchor.ts
267
+ function makeAnchorVerifier(cfg) {
268
+ return async (input) => {
269
+ const walked = await cfg.walkProof({ proofBytes: input.proofBytes, digest: input.digest });
270
+ if (!walked) {
271
+ return { ok: false, reason: "proof could not be walked to a Bitcoin commitment" };
272
+ }
273
+ if (walked.blockHeight !== input.blockHeight) {
274
+ return {
275
+ ok: false,
276
+ reason: `proof anchors at block ${walked.blockHeight}, envelope declares ${input.blockHeight}`
277
+ };
278
+ }
279
+ const header = await cfg.headerSource.getHeaderAt(input.blockHeight);
280
+ if (!header) {
281
+ return { ok: false, reason: `no block header available at height ${input.blockHeight}` };
282
+ }
283
+ if (header.byteLength < 80) {
284
+ return { ok: false, reason: "malformed Bitcoin header (too short)" };
285
+ }
286
+ const headerMerkleLE = header.subarray(36, 68);
287
+ const headerMerkleBE = new Uint8Array(headerMerkleLE).reverse();
288
+ const matchesLE = bytesEqual(headerMerkleLE, walked.merkleRoot);
289
+ const matchesBE = bytesEqual(headerMerkleBE, walked.merkleRoot);
290
+ if (!matchesLE && !matchesBE) {
291
+ return { ok: false, reason: "block header Merkle root does not match walked proof root" };
292
+ }
293
+ return { ok: true, merkleRoot: hexEncode(matchesBE ? headerMerkleBE : headerMerkleLE) };
294
+ };
295
+ }
296
+ function bytesEqual(a, b) {
297
+ if (a.byteLength !== b.byteLength) return false;
298
+ for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
299
+ return true;
300
+ }
301
+ function adaptAnchorVerifier(verifier, digestLookup) {
302
+ return async (proofB64, blockHeight, blockHash) => {
303
+ const r = await verifier({
304
+ proofBytes: base64Decode(proofB64),
305
+ digest: digestLookup(blockHash),
306
+ blockHeight,
307
+ blockHash
308
+ });
309
+ return r.ok;
310
+ };
311
+ }
312
+
313
+ exports.DEFAULT_CALENDARS = DEFAULT_CALENDARS;
314
+ exports.adaptAnchorVerifier = adaptAnchorVerifier;
315
+ exports.base64Decode = base64Decode;
316
+ exports.base64Encode = base64Encode;
317
+ exports.createCalendarClient = createCalendarClient;
318
+ exports.fromStampOts = fromStampOts;
319
+ exports.hexDecode = hexDecode;
320
+ exports.hexEncode = hexEncode;
321
+ exports.makeAnchorVerifier = makeAnchorVerifier;
322
+ exports.submitToCalendars = submitToCalendars;
323
+ exports.toStampOts = toStampOts;
324
+ exports.upgradeProof = upgradeProof;
325
+ //# sourceMappingURL=index.js.map
326
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/base64.ts","../src/calendar.ts","../src/submit.ts","../src/upgrade.ts","../src/anchor.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Types for @orangecheck/stamp-ots.\n\nimport type { StampOts } from '@orangecheck/stamp-core';\n\nexport type OtsProofStatus = 'pending' | 'confirmed';\n\nexport interface OtsProof {\n status: OtsProofStatus;\n /** Raw proof bytes as base64. Opaque at this layer; parsers plug in. */\n proof: string;\n /** Calendar URLs that contain this proof, in submission order. */\n calendars: string[];\n /** Bitcoin block height at which this proof is anchored (null if pending). */\n blockHeight: number | null;\n /** Bitcoin block hash (hex, lowercase) at the anchor block (null if pending). */\n blockHash: string | null;\n /** ISO 8601 UTC of the upgrade (null if pending). */\n upgradedAt: string | null;\n}\n\nexport interface CalendarClient {\n /** Base URL of the calendar, e.g. \"https://alice.btc.calendar.opentimestamps.org\". */\n readonly url: string;\n\n /**\n * Submit a 32-byte digest to the calendar for aggregation. Returns the raw\n * pending proof bytes.\n *\n * OTS calendar HTTP API: POST <url>/digest with the raw bytes as the body,\n * Content-Type: application/vnd.opentimestamps.v1, returns the proof bytes\n * as the response body.\n */\n submit(digest: Uint8Array, signal?: AbortSignal): Promise<Uint8Array>;\n\n /**\n * Fetch the current (possibly upgraded) proof for a previously-submitted\n * digest. Returns null if the calendar has no proof for this digest yet.\n *\n * OTS calendar HTTP API: GET <url>/timestamp/<hex(digest)>, returns the\n * proof bytes or 404.\n */\n fetchProof(digest: Uint8Array, signal?: AbortSignal): Promise<Uint8Array | null>;\n}\n\nexport interface AnchorVerificationInput {\n /** Raw OTS proof bytes (base64-decoded by the caller). */\n proofBytes: Uint8Array;\n /** 32-byte digest that the proof commits to. */\n digest: Uint8Array;\n /** Declared block height from the envelope. */\n blockHeight: number;\n /** Declared block hash (hex) from the envelope. */\n blockHash: string;\n /**\n * Optional block header source. If provided, the verifier uses this to\n * fetch the header at `blockHeight` and compare its Merkle root to the\n * one derived from walking the proof.\n *\n * If omitted, the verifier MAY still parse the proof and report\n * structural validity but CANNOT report a bound anchor.\n */\n headerSource?: BlockHeaderSource;\n}\n\nexport interface BlockHeaderSource {\n /** Return the 80-byte raw block header at the given height, or null if unknown. */\n getHeaderAt(height: number): Promise<Uint8Array | null>;\n}\n\nexport type AnchorVerifier = (input: AnchorVerificationInput) => Promise<AnchorVerificationResult>;\n\nexport type AnchorVerificationResult =\n | { ok: true; merkleRoot: string }\n | { ok: false; reason: string };\n\n/**\n * Convert an OtsProof (library type) to the StampOts field shape that the\n * envelope carries. The two are isomorphic; this function exists to keep\n * stamp-core's type surface narrow.\n */\nexport function toStampOts(p: OtsProof): StampOts {\n return {\n status: p.status,\n proof: p.proof,\n calendars: p.calendars,\n block_height: p.blockHeight,\n block_hash: p.blockHash,\n upgraded_at: p.upgradedAt,\n };\n}\n\nexport function fromStampOts(s: StampOts): OtsProof {\n return {\n status: s.status,\n proof: s.proof,\n calendars: s.calendars,\n blockHeight: s.block_height,\n blockHash: s.block_hash,\n upgradedAt: s.upgraded_at,\n };\n}\n","// Minimal, dependency-free base64 <-> Uint8Array helpers. OTS proof bytes\n// are carried as base64 inside OC Stamp envelopes so they survive any text\n// transport (URL fragment, JSON, Nostr event content).\n\nconst B64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\nconst B64_DECODE_TABLE = (() => {\n const t = new Int16Array(128).fill(-1);\n for (let i = 0; i < B64_CHARS.length; i++) t[B64_CHARS.charCodeAt(i)] = i;\n t['='.charCodeAt(0)] = -2;\n return t;\n})();\n\nexport function base64Encode(bytes: Uint8Array): string {\n let out = '';\n let i = 0;\n for (; i + 2 < bytes.length; i += 3) {\n const a = bytes[i]!;\n const b = bytes[i + 1]!;\n const c = bytes[i + 2]!;\n out +=\n B64_CHARS[a >> 2]! +\n B64_CHARS[((a & 0x03) << 4) | (b >> 4)]! +\n B64_CHARS[((b & 0x0f) << 2) | (c >> 6)]! +\n B64_CHARS[c & 0x3f]!;\n }\n if (i < bytes.length) {\n const a = bytes[i]!;\n const b = i + 1 < bytes.length ? bytes[i + 1]! : 0;\n out += B64_CHARS[a >> 2]!;\n out += B64_CHARS[((a & 0x03) << 4) | (b >> 4)]!;\n out += i + 1 < bytes.length ? B64_CHARS[(b & 0x0f) << 2]! : '=';\n out += '=';\n }\n return out;\n}\n\nexport function base64Decode(s: string): Uint8Array {\n const str = s.replace(/\\s+/g, '');\n const len = str.length;\n if (len === 0) return new Uint8Array(0);\n if (len % 4 !== 0) throw new Error('base64: bad length');\n let padding = 0;\n if (str[len - 1] === '=') padding++;\n if (str[len - 2] === '=') padding++;\n const outLen = (len / 4) * 3 - padding;\n const out = new Uint8Array(outLen);\n let o = 0;\n for (let i = 0; i < len; i += 4) {\n const c0 = B64_DECODE_TABLE[str.charCodeAt(i)]!;\n const c1 = B64_DECODE_TABLE[str.charCodeAt(i + 1)]!;\n const c2 = B64_DECODE_TABLE[str.charCodeAt(i + 2)]!;\n const c3 = B64_DECODE_TABLE[str.charCodeAt(i + 3)]!;\n if (c0 < 0 || c1 < 0 || c2 < -2 || c3 < -2) throw new Error('base64: bad char');\n out[o++] = (c0 << 2) | (c1 >> 4);\n if (c2 >= 0) out[o++] = ((c1 & 0x0f) << 4) | (c2 >> 2);\n if (c3 >= 0) out[o++] = ((c2 & 0x03) << 6) | c3;\n }\n return out;\n}\n\nexport function hexDecode(s: string): Uint8Array {\n if (s.length % 2 !== 0) throw new Error('hex: odd length');\n const out = new Uint8Array(s.length / 2);\n for (let i = 0; i < out.length; i++) {\n const hi = parseInt(s[i * 2]!, 16);\n const lo = parseInt(s[i * 2 + 1]!, 16);\n if (Number.isNaN(hi) || Number.isNaN(lo)) throw new Error('hex: bad char');\n out[i] = (hi << 4) | lo;\n }\n return out;\n}\n\nexport function hexEncode(bytes: Uint8Array): string {\n const HEX = '0123456789abcdef';\n let out = '';\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i]!;\n out += HEX[(b >> 4) & 0x0f]! + HEX[b & 0x0f]!;\n }\n return out;\n}\n","// HTTP client for an OpenTimestamps calendar. Implements the minimal subset\n// of the calendar protocol that an OC Stamp implementation needs:\n//\n// POST <url>/digest body = raw digest bytes -> pending proof bytes\n// GET <url>/timestamp/<hex> -> upgraded proof bytes, or 404\n//\n// The client is thin by design. It does not parse proof bytes; that is a\n// separate concern addressed by whichever OTS proof library the consumer\n// plugs in (see verifyOtsAnchor in ./anchor.ts).\n\nimport type { CalendarClient } from './types.js';\nimport { hexEncode } from './base64.js';\n\nexport const DEFAULT_CALENDARS: readonly string[] = [\n 'https://alice.btc.calendar.opentimestamps.org',\n 'https://bob.btc.calendar.opentimestamps.org',\n 'https://finney.calendar.eternitywall.com',\n];\n\nexport interface HttpCalendarOptions {\n /**\n * Custom fetch implementation. Defaults to global `fetch`. Useful in\n * environments that need a specific adapter (Node < 18, Cloudflare\n * Workers, proxies).\n */\n fetch?: typeof fetch;\n /** Request timeout in milliseconds. Default 30_000. */\n timeoutMs?: number;\n}\n\nexport function createCalendarClient(url: string, opts: HttpCalendarOptions = {}): CalendarClient {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new Error(\n '@orangecheck/stamp-ots: no fetch implementation available. Pass one via { fetch }.'\n );\n }\n const timeoutMs = opts.timeoutMs ?? 30_000;\n const base = url.replace(/\\/$/, '');\n\n return {\n url: base,\n async submit(digest, signal) {\n if (digest.byteLength !== 32) {\n throw new Error('OTS: digest must be exactly 32 bytes');\n }\n const ac = withTimeout(signal, timeoutMs);\n // Uint8Array is a valid fetch body at runtime in Node 18+ and all\n // modern browsers, but TypeScript's DOM typings don't accept it\n // directly under `strict` + `noUncheckedIndexedAccess`. The cast is\n // narrow and intentional.\n const resp = await fetchImpl(`${base}/digest`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/vnd.opentimestamps.v1' },\n body: digest as unknown as BodyInit,\n signal: ac.signal,\n });\n ac.clear();\n if (!resp.ok) {\n throw new Error(`OTS calendar ${base} submit failed: HTTP ${resp.status}`);\n }\n const buf = await resp.arrayBuffer();\n return new Uint8Array(buf);\n },\n async fetchProof(digest, signal) {\n if (digest.byteLength !== 32) {\n throw new Error('OTS: digest must be exactly 32 bytes');\n }\n const ac = withTimeout(signal, timeoutMs);\n const resp = await fetchImpl(`${base}/timestamp/${hexEncode(digest)}`, {\n method: 'GET',\n headers: { Accept: 'application/vnd.opentimestamps.v1' },\n signal: ac.signal,\n });\n ac.clear();\n if (resp.status === 404) return null;\n if (!resp.ok) {\n throw new Error(`OTS calendar ${base} fetchProof failed: HTTP ${resp.status}`);\n }\n const buf = await resp.arrayBuffer();\n return new Uint8Array(buf);\n },\n };\n}\n\nfunction withTimeout(parent: AbortSignal | undefined, ms: number): { signal: AbortSignal; clear: () => void } {\n const ac = new AbortController();\n const onParent = () => ac.abort((parent as AbortSignal & { reason?: unknown }).reason);\n if (parent) {\n if (parent.aborted) ac.abort((parent as AbortSignal & { reason?: unknown }).reason);\n else parent.addEventListener('abort', onParent, { once: true });\n }\n const t = setTimeout(() => ac.abort(new Error(`OTS request timed out after ${ms}ms`)), ms);\n return {\n signal: ac.signal,\n clear: () => {\n clearTimeout(t);\n if (parent) parent.removeEventListener('abort', onParent);\n },\n };\n}\n","// Submit an envelope id to one or more OTS calendars. Returns a pending proof.\n// See SPEC.md §6.1.\n//\n// The submission strategy: try each calendar in parallel, take the first\n// success, report which calendar produced it. A stamp that only reaches one\n// calendar is a single point of failure for upgrade; the reference app\n// submits to at least two calendars for redundancy.\n\nimport { base64Encode, hexDecode } from './base64.js';\nimport { createCalendarClient, DEFAULT_CALENDARS } from './calendar.js';\nimport type { CalendarClient, OtsProof } from './types.js';\n\nexport interface SubmitOptions {\n /**\n * Calendars to submit to. Defaults to DEFAULT_CALENDARS. Can be strings\n * (URLs) or pre-built CalendarClient instances.\n */\n calendars?: readonly (string | CalendarClient)[];\n /**\n * Minimum number of calendars that must succeed. Defaults to 1.\n * If fewer than `minSuccesses` calendars respond with a proof,\n * submit() throws an aggregate error.\n */\n minSuccesses?: number;\n /** Optional custom fetch passed through to calendar clients. */\n fetch?: typeof fetch;\n /** Request timeout per calendar. Default 30_000ms. */\n timeoutMs?: number;\n signal?: AbortSignal;\n}\n\n/**\n * Submit the envelope `id` (64-hex) to OTS calendars and return an OtsProof in\n * pending state. The caller merges this into the envelope's `ots` field.\n *\n * Implementation note: the raw proof bytes returned by OTS calendars are not\n * a superset of the digest alone — they encode the calendar's commitment.\n * Because different calendars return different pending-proof bytes for the\n * same digest, we carry the FIRST successful calendar's bytes in the\n * envelope. Upgrade (see upgrade.ts) can fetch richer proofs from any of the\n * submitted-to calendars later.\n */\nexport async function submitToCalendars(idHex: string, opts: SubmitOptions = {}): Promise<OtsProof> {\n const digest = hexDecode(idHex);\n if (digest.byteLength !== 32) {\n throw new Error('submitToCalendars: idHex must be 32 bytes (64 hex chars)');\n }\n\n const clients = (opts.calendars ?? DEFAULT_CALENDARS).map((c) =>\n typeof c === 'string'\n ? createCalendarClient(c, { fetch: opts.fetch, timeoutMs: opts.timeoutMs })\n : c\n );\n if (clients.length === 0) {\n throw new Error('submitToCalendars: at least one calendar is required');\n }\n const minSuccesses = Math.max(1, opts.minSuccesses ?? 1);\n\n const settled = await Promise.allSettled(\n clients.map(async (c) => {\n const proof = await c.submit(digest, opts.signal);\n return { url: c.url, proof };\n })\n );\n const successes = settled\n .filter((r): r is PromiseFulfilledResult<{ url: string; proof: Uint8Array }> => r.status === 'fulfilled')\n .map((r) => r.value);\n const failures = settled\n .filter((r): r is PromiseRejectedResult => r.status === 'rejected')\n .map((r) => (r.reason instanceof Error ? r.reason.message : String(r.reason)));\n\n if (successes.length < minSuccesses) {\n // AggregateError would be nicer but targets ES2021; Error keeps us at ES2020.\n const msg = `OTS submission did not reach ${minSuccesses} calendar(s). Errors: ${failures.join('; ')}`;\n const err = new Error(msg) as Error & { errors: string[] };\n err.errors = failures;\n throw err;\n }\n\n const first = successes[0]!;\n return {\n status: 'pending',\n proof: base64Encode(first.proof),\n calendars: successes.map((s) => s.url),\n blockHeight: null,\n blockHash: null,\n upgradedAt: null,\n };\n}\n","// Upgrade a pending OTS proof to a confirmed one. See SPEC.md §6.2.\n//\n// Upgrade strategy: for each listed calendar, ask whether it has an upgraded\n// proof for this digest. If at least one calendar returns a proof longer than\n// the current pending one (heuristic for \"more commitments included\"),\n// return an upgraded OtsProof. The caller is responsible for verifying the\n// proof chains to a real Bitcoin block header (see anchor.ts).\n//\n// This package does NOT parse the proof bytes to extract block_height /\n// block_hash — that requires a full OTS proof parser. We provide an anchor\n// verifier callback pattern instead (see AnchorVerifier in ./types.ts).\n\nimport { base64Decode, base64Encode, hexDecode } from './base64.js';\nimport { createCalendarClient } from './calendar.js';\nimport type { CalendarClient, OtsProof } from './types.js';\n\nexport interface UpgradeOptions {\n /**\n * A parser that, given proof bytes, returns `{blockHeight, blockHash}` if\n * the proof is fully anchored, or null if it's still pending.\n *\n * Callers plug in their own parser (e.g., via the `opentimestamps` npm\n * package). This keeps @orangecheck/stamp-ots free of heavy parser deps.\n */\n parseAnchor: (proofBytes: Uint8Array) => Promise<{ blockHeight: number; blockHash: string } | null>;\n /** Optional custom fetch passed through to calendar clients. */\n fetch?: typeof fetch;\n /** Request timeout per calendar. Default 30_000ms. */\n timeoutMs?: number;\n signal?: AbortSignal;\n}\n\nexport async function upgradeProof(\n current: OtsProof,\n idHex: string,\n opts: UpgradeOptions\n): Promise<OtsProof> {\n if (current.status === 'confirmed') return current; // idempotent\n\n const digest = hexDecode(idHex);\n const clients: CalendarClient[] = current.calendars.map((url) =>\n createCalendarClient(url, { fetch: opts.fetch, timeoutMs: opts.timeoutMs })\n );\n if (clients.length === 0) {\n throw new Error('upgradeProof: no calendars listed in current proof');\n }\n\n const attempts = await Promise.allSettled(\n clients.map(async (c) => {\n const proof = await c.fetchProof(digest, opts.signal);\n if (!proof) return null;\n const anchor = await opts.parseAnchor(proof);\n return { url: c.url, proof, anchor };\n })\n );\n\n // Prefer a fully-confirmed proof from any calendar.\n for (const r of attempts) {\n if (r.status !== 'fulfilled' || r.value === null) continue;\n const { url, proof, anchor } = r.value;\n if (anchor) {\n return {\n status: 'confirmed',\n proof: base64Encode(proof),\n calendars: uniqueFirst(current.calendars, url),\n blockHeight: anchor.blockHeight,\n blockHash: anchor.blockHash,\n upgradedAt: new Date().toISOString().replace(/\\.\\d+Z$/, 'Z'),\n };\n }\n }\n\n // No confirmation yet. Some calendars may still have returned updated\n // pending bytes (more commitments included). Prefer the longest.\n let longest: { url: string; proof: Uint8Array } | null = null;\n for (const r of attempts) {\n if (r.status !== 'fulfilled' || r.value === null) continue;\n if (!longest || r.value.proof.byteLength > longest.proof.byteLength) {\n longest = { url: r.value.url, proof: r.value.proof };\n }\n }\n if (longest) {\n const currentBytes = base64Decode(current.proof);\n if (longest.proof.byteLength > currentBytes.byteLength) {\n return {\n status: 'pending',\n proof: base64Encode(longest.proof),\n calendars: uniqueFirst(current.calendars, longest.url),\n blockHeight: null,\n blockHash: null,\n upgradedAt: null,\n };\n }\n }\n return current;\n}\n\nfunction uniqueFirst(arr: readonly string[], first: string): string[] {\n const out = [first];\n for (const x of arr) if (x !== first && !out.includes(x)) out.push(x);\n return out;\n}\n","// Anchor-verification helper. See SPEC.md §6.3.\n//\n// Full OTS proof verification requires:\n// 1. Parsing the proof's Merkle path from digest to calendar root.\n// 2. Walking that path to the declared Bitcoin Merkle root.\n// 3. Fetching the Bitcoin block header at the declared height and comparing\n// its Merkle root.\n//\n// Step 1 needs a maintained OTS proof parser. We do not ship one here; this\n// file exposes a small helper that composes a parsed-anchor result with a\n// block-header source into the `verifyOtsAnchor` callback expected by\n// @orangecheck/stamp-core's verify().\n\nimport { base64Decode, hexEncode } from './base64.js';\nimport type {\n AnchorVerificationInput,\n AnchorVerificationResult,\n AnchorVerifier,\n BlockHeaderSource,\n} from './types.js';\n\nexport interface AnchorVerifierConfig {\n /**\n * Parse the proof bytes and return the Merkle root the proof walks to,\n * plus the declared block height. Returns null if parsing failed or the\n * proof isn't anchored.\n */\n walkProof: (input: {\n proofBytes: Uint8Array;\n digest: Uint8Array;\n }) => Promise<{ merkleRoot: Uint8Array; blockHeight: number } | null>;\n /**\n * Source of Bitcoin block headers. Can be a full node, SPV, or a\n * pre-computed headers snapshot.\n */\n headerSource: BlockHeaderSource;\n}\n\n/**\n * Build a concrete anchor verifier from a proof walker plus a header source.\n *\n * Callers wire this into `verify({ verifyOtsAnchor: makeAnchorVerifier(...) })`.\n * If either the proof walker returns null or the header's Merkle root does\n * not match, the verifier returns `{ ok: false, ... }`.\n */\nexport function makeAnchorVerifier(cfg: AnchorVerifierConfig): AnchorVerifier {\n return async (input: AnchorVerificationInput): Promise<AnchorVerificationResult> => {\n const walked = await cfg.walkProof({ proofBytes: input.proofBytes, digest: input.digest });\n if (!walked) {\n return { ok: false, reason: 'proof could not be walked to a Bitcoin commitment' };\n }\n if (walked.blockHeight !== input.blockHeight) {\n return {\n ok: false,\n reason: `proof anchors at block ${walked.blockHeight}, envelope declares ${input.blockHeight}`,\n };\n }\n const header = await cfg.headerSource.getHeaderAt(input.blockHeight);\n if (!header) {\n return { ok: false, reason: `no block header available at height ${input.blockHeight}` };\n }\n // Bitcoin block header: 80 bytes. Merkle root is at offset 36, 32 bytes,\n // stored little-endian. Compare to the proof-walked root, which OTS\n // reports in big-endian conventionally.\n if (header.byteLength < 80) {\n return { ok: false, reason: 'malformed Bitcoin header (too short)' };\n }\n const headerMerkleLE = header.subarray(36, 68);\n const headerMerkleBE = new Uint8Array(headerMerkleLE).reverse();\n const matchesLE = bytesEqual(headerMerkleLE, walked.merkleRoot);\n const matchesBE = bytesEqual(headerMerkleBE, walked.merkleRoot);\n if (!matchesLE && !matchesBE) {\n return { ok: false, reason: 'block header Merkle root does not match walked proof root' };\n }\n return { ok: true, merkleRoot: hexEncode(matchesBE ? headerMerkleBE : headerMerkleLE) };\n };\n}\n\nfunction bytesEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.byteLength !== b.byteLength) return false;\n for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;\n return true;\n}\n\n/**\n * Adapt an AnchorVerifier into the signature that\n * @orangecheck/stamp-core#verify expects: a function taking (proofB64,\n * blockHeight, blockHash) that returns boolean.\n */\nexport function adaptAnchorVerifier(\n verifier: AnchorVerifier,\n digestLookup: (blockHash: string) => Uint8Array\n): (proofB64: string, blockHeight: number, blockHash: string) => Promise<boolean> {\n return async (proofB64, blockHeight, blockHash) => {\n const r = await verifier({\n proofBytes: base64Decode(proofB64),\n digest: digestLookup(blockHash),\n blockHeight,\n blockHash,\n });\n return r.ok;\n };\n}\n"]}