@orangecheck/stamp-ots 0.1.0 → 0.1.1

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/dist/index.d.mts CHANGED
@@ -77,7 +77,9 @@ interface AnchorVerifierConfig {
77
77
  } | null>;
78
78
  headerSource: BlockHeaderSource;
79
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>;
80
+ declare function makeAnchorVerifier(cfg: AnchorVerifierConfig): (proofB64: string, blockHeight: number, blockHash: string, envelopeId: string) => Promise<boolean>;
81
+ declare function makeDefaultAnchorVerifier(opts: {
82
+ headerSource: BlockHeaderSource;
83
+ }): Promise<(proofB64: string, blockHeight: number, blockHash: string, envelopeId: string) => Promise<boolean>>;
82
84
 
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 };
85
+ 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, base64Decode, base64Encode, createCalendarClient, fromStampOts, hexDecode, hexEncode, makeAnchorVerifier, makeDefaultAnchorVerifier, submitToCalendars, toStampOts, upgradeProof };
package/dist/index.d.ts CHANGED
@@ -77,7 +77,9 @@ interface AnchorVerifierConfig {
77
77
  } | null>;
78
78
  headerSource: BlockHeaderSource;
79
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>;
80
+ declare function makeAnchorVerifier(cfg: AnchorVerifierConfig): (proofB64: string, blockHeight: number, blockHash: string, envelopeId: string) => Promise<boolean>;
81
+ declare function makeDefaultAnchorVerifier(opts: {
82
+ headerSource: BlockHeaderSource;
83
+ }): Promise<(proofB64: string, blockHeight: number, blockHash: string, envelopeId: string) => Promise<boolean>>;
82
84
 
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 };
85
+ 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, base64Decode, base64Encode, createCalendarClient, fromStampOts, hexDecode, hexEncode, makeAnchorVerifier, makeDefaultAnchorVerifier, submitToCalendars, toStampOts, upgradeProof };
package/dist/index.js CHANGED
@@ -265,7 +265,7 @@ function uniqueFirst(arr, first) {
265
265
 
266
266
  // src/anchor.ts
267
267
  function makeAnchorVerifier(cfg) {
268
- return async (input) => {
268
+ const verifier = async (input) => {
269
269
  const walked = await cfg.walkProof({ proofBytes: input.proofBytes, digest: input.digest });
270
270
  if (!walked) {
271
271
  return { ok: false, reason: "proof could not be walked to a Bitcoin commitment" };
@@ -292,26 +292,62 @@ function makeAnchorVerifier(cfg) {
292
292
  }
293
293
  return { ok: true, merkleRoot: hexEncode(matchesBE ? headerMerkleBE : headerMerkleLE) };
294
294
  };
295
+ return async (proofB64, blockHeight, blockHash, envelopeId) => {
296
+ const r = await verifier({
297
+ proofBytes: base64Decode(proofB64),
298
+ digest: hexDecode(envelopeId),
299
+ blockHeight});
300
+ return r.ok;
301
+ };
302
+ }
303
+ async function makeDefaultAnchorVerifier(opts) {
304
+ const walkProof = await loadOpenTimestampsWalker();
305
+ return makeAnchorVerifier({ walkProof, headerSource: opts.headerSource });
306
+ }
307
+ async function loadOpenTimestampsWalker() {
308
+ let ots;
309
+ try {
310
+ ots = await import(
311
+ /* webpackIgnore: true */
312
+ 'opentimestamps'
313
+ );
314
+ } catch (e) {
315
+ throw new Error(
316
+ `@orangecheck/stamp-ots: makeDefaultAnchorVerifier requires the optional peer dependency \`opentimestamps\`. Install it with \`npm i opentimestamps\` or supply your own walkProof via makeAnchorVerifier. Underlying error: ${e instanceof Error ? e.message : String(e)}`
317
+ );
318
+ }
319
+ const mod = ots.default ?? ots;
320
+ const modAny = mod;
321
+ const DetachedCtor = modAny.DetachedTimestampFile ?? modAny.default?.DetachedTimestampFile;
322
+ if (!DetachedCtor || typeof DetachedCtor.deserialize !== "function") {
323
+ throw new Error(
324
+ "@orangecheck/stamp-ots: unexpected shape from `opentimestamps` package \u2014 expected DetachedTimestampFile.deserialize. If you are using a non-standard build, pass your own walkProof via makeAnchorVerifier instead."
325
+ );
326
+ }
327
+ return async ({ proofBytes }) => {
328
+ try {
329
+ const detached = DetachedCtor.deserialize(proofBytes);
330
+ const ts = detached.timestamp;
331
+ const attestations = await ts.verify();
332
+ const first = attestations.values().next();
333
+ if (first.done) return null;
334
+ const { height, timestamp } = first.value;
335
+ if (typeof height !== "number" || !timestamp) return null;
336
+ const merkleRoot = timestamp.msg;
337
+ if (!merkleRoot) return null;
338
+ return { merkleRoot, blockHeight: height };
339
+ } catch {
340
+ return null;
341
+ }
342
+ };
295
343
  }
296
344
  function bytesEqual(a, b) {
297
345
  if (a.byteLength !== b.byteLength) return false;
298
346
  for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
299
347
  return true;
300
348
  }
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
349
 
313
350
  exports.DEFAULT_CALENDARS = DEFAULT_CALENDARS;
314
- exports.adaptAnchorVerifier = adaptAnchorVerifier;
315
351
  exports.base64Decode = base64Decode;
316
352
  exports.base64Encode = base64Encode;
317
353
  exports.createCalendarClient = createCalendarClient;
@@ -319,6 +355,7 @@ exports.fromStampOts = fromStampOts;
319
355
  exports.hexDecode = hexDecode;
320
356
  exports.hexEncode = hexEncode;
321
357
  exports.makeAnchorVerifier = makeAnchorVerifier;
358
+ exports.makeDefaultAnchorVerifier = makeDefaultAnchorVerifier;
322
359
  exports.submitToCalendars = submitToCalendars;
323
360
  exports.toStampOts = toStampOts;
324
361
  exports.upgradeProof = upgradeProof;
package/dist/index.js.map CHANGED
@@ -1 +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"]}
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 real OTS proof parser. We expose two composition points:\n//\n// A) `makeAnchorVerifier({ walkProof, headerSource })` — you bring the\n// parser, we provide the glue that compares Merkle roots against the\n// block header.\n//\n// B) `makeDefaultAnchorVerifier({ headerSource })` — dynamic-imports the\n// `opentimestamps` npm package (optional peer dependency) and wires\n// it into (A). One import, full verification.\n//\n// In both cases the resulting function is what `@orangecheck/stamp-core`\n// expects at the `verifyOtsAnchor` input of `verify()` — no further adaptation\n// needed. The envelope id is passed directly into the hook by stamp-core; we\n// drop the old broken `adaptAnchorVerifier` that tried to derive the digest\n// from the block hash.\n\nimport { base64Decode, hexDecode, 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 * The returned function matches the `verifyOtsAnchor` shape expected by\n * `@orangecheck/stamp-core`'s `verify()`:\n *\n * (proofB64, blockHeight, blockHash, envelopeId) => Promise<boolean>\n *\n * Wire it in as:\n *\n * verify({ envelope, verifyOtsAnchor: makeAnchorVerifier({...}) })\n */\nexport function makeAnchorVerifier(\n cfg: AnchorVerifierConfig\n): (proofB64: string, blockHeight: number, blockHash: string, envelopeId: string) => Promise<boolean> {\n const verifier: AnchorVerifier = async (\n input: AnchorVerificationInput\n ): 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 is 80 bytes; merkle root at offset 36, 32 bytes,\n // little-endian. Proof walkers vary in convention — compare both\n // endiannesses to stay compatible across implementations.\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 return async (proofB64, blockHeight, blockHash, envelopeId) => {\n const r = await verifier({\n proofBytes: base64Decode(proofB64),\n digest: hexDecode(envelopeId),\n blockHeight,\n blockHash,\n });\n return r.ok;\n };\n}\n\n/**\n * Default anchor verifier that dynamically imports the `opentimestamps` npm\n * package and wires it into `makeAnchorVerifier`. The import is lazy so\n * consumers who only need calendar submission don't pay the dep cost.\n *\n * Install `opentimestamps` alongside `@orangecheck/stamp-ots` to use this:\n *\n * npm i @orangecheck/stamp-ots opentimestamps\n *\n * Then:\n *\n * const verifyOtsAnchor = await makeDefaultAnchorVerifier({ headerSource });\n * await verify({ envelope, verifyOtsAnchor });\n *\n * Throws if `opentimestamps` isn't resolvable. That's intentional — callers\n * who haven't installed it should use `makeAnchorVerifier` with their own\n * walker instead, so the failure surfaces immediately rather than silently\n * reporting every stamp as unverified.\n */\nexport async function makeDefaultAnchorVerifier(opts: {\n headerSource: BlockHeaderSource;\n}): Promise<\n (proofB64: string, blockHeight: number, blockHash: string, envelopeId: string) => Promise<boolean>\n> {\n const walkProof = await loadOpenTimestampsWalker();\n return makeAnchorVerifier({ walkProof, headerSource: opts.headerSource });\n}\n\ntype OtsWalker = AnchorVerifierConfig['walkProof'];\n\nasync function loadOpenTimestampsWalker(): Promise<OtsWalker> {\n // Dynamic import so the dep is optional. If the package isn't installed,\n // the error is explicit and actionable.\n let ots: unknown;\n try {\n ots = await import(/* webpackIgnore: true */ 'opentimestamps' as string);\n } catch (e) {\n throw new Error(\n '@orangecheck/stamp-ots: makeDefaultAnchorVerifier requires the optional peer dependency `opentimestamps`. ' +\n `Install it with \\`npm i opentimestamps\\` or supply your own walkProof via makeAnchorVerifier. ` +\n `Underlying error: ${e instanceof Error ? e.message : String(e)}`\n );\n }\n\n // Normalize the module shape. `opentimestamps` publishes both CJS-default\n // and named exports across its versions.\n const mod = (ots as { default?: unknown }).default ?? ots;\n const modAny = mod as Record<string, unknown>;\n\n type DetachedLike = { deserialize(bytes: Uint8Array): unknown; timestamp: unknown };\n type TimestampLike = {\n verify(): Promise<Map<string, { height: number; timestamp?: unknown }>>;\n msg: Uint8Array;\n };\n\n const DetachedCtor =\n (modAny.DetachedTimestampFile as { deserialize(b: Uint8Array): DetachedLike } | undefined) ??\n (modAny.default as Record<string, unknown> | undefined)?.DetachedTimestampFile as\n | { deserialize(b: Uint8Array): DetachedLike }\n | undefined;\n\n if (!DetachedCtor || typeof DetachedCtor.deserialize !== 'function') {\n throw new Error(\n '@orangecheck/stamp-ots: unexpected shape from `opentimestamps` package — ' +\n 'expected DetachedTimestampFile.deserialize. If you are using a non-standard build, ' +\n 'pass your own walkProof via makeAnchorVerifier instead.'\n );\n }\n\n return async ({ proofBytes }) => {\n try {\n const detached = DetachedCtor.deserialize(proofBytes);\n const ts = detached.timestamp as TimestampLike;\n const attestations = await ts.verify();\n const first = attestations.values().next();\n if (first.done) return null;\n const { height, timestamp } = first.value;\n if (typeof height !== 'number' || !timestamp) return null;\n // The attestation provides the block height; the merkle root the\n // walker produces is the timestamp.msg bytes the walker committed\n // against. opentimestamps resolves that through its internal\n // proof chain; reading ts.msg gives the final digest it committed.\n const merkleRoot = (timestamp as { msg?: Uint8Array }).msg;\n if (!merkleRoot) return null;\n return { merkleRoot, blockHeight: height };\n } catch {\n return null;\n }\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"]}
package/dist/index.mjs CHANGED
@@ -263,7 +263,7 @@ function uniqueFirst(arr, first) {
263
263
 
264
264
  // src/anchor.ts
265
265
  function makeAnchorVerifier(cfg) {
266
- return async (input) => {
266
+ const verifier = async (input) => {
267
267
  const walked = await cfg.walkProof({ proofBytes: input.proofBytes, digest: input.digest });
268
268
  if (!walked) {
269
269
  return { ok: false, reason: "proof could not be walked to a Bitcoin commitment" };
@@ -290,24 +290,61 @@ function makeAnchorVerifier(cfg) {
290
290
  }
291
291
  return { ok: true, merkleRoot: hexEncode(matchesBE ? headerMerkleBE : headerMerkleLE) };
292
292
  };
293
+ return async (proofB64, blockHeight, blockHash, envelopeId) => {
294
+ const r = await verifier({
295
+ proofBytes: base64Decode(proofB64),
296
+ digest: hexDecode(envelopeId),
297
+ blockHeight});
298
+ return r.ok;
299
+ };
300
+ }
301
+ async function makeDefaultAnchorVerifier(opts) {
302
+ const walkProof = await loadOpenTimestampsWalker();
303
+ return makeAnchorVerifier({ walkProof, headerSource: opts.headerSource });
304
+ }
305
+ async function loadOpenTimestampsWalker() {
306
+ let ots;
307
+ try {
308
+ ots = await import(
309
+ /* webpackIgnore: true */
310
+ 'opentimestamps'
311
+ );
312
+ } catch (e) {
313
+ throw new Error(
314
+ `@orangecheck/stamp-ots: makeDefaultAnchorVerifier requires the optional peer dependency \`opentimestamps\`. Install it with \`npm i opentimestamps\` or supply your own walkProof via makeAnchorVerifier. Underlying error: ${e instanceof Error ? e.message : String(e)}`
315
+ );
316
+ }
317
+ const mod = ots.default ?? ots;
318
+ const modAny = mod;
319
+ const DetachedCtor = modAny.DetachedTimestampFile ?? modAny.default?.DetachedTimestampFile;
320
+ if (!DetachedCtor || typeof DetachedCtor.deserialize !== "function") {
321
+ throw new Error(
322
+ "@orangecheck/stamp-ots: unexpected shape from `opentimestamps` package \u2014 expected DetachedTimestampFile.deserialize. If you are using a non-standard build, pass your own walkProof via makeAnchorVerifier instead."
323
+ );
324
+ }
325
+ return async ({ proofBytes }) => {
326
+ try {
327
+ const detached = DetachedCtor.deserialize(proofBytes);
328
+ const ts = detached.timestamp;
329
+ const attestations = await ts.verify();
330
+ const first = attestations.values().next();
331
+ if (first.done) return null;
332
+ const { height, timestamp } = first.value;
333
+ if (typeof height !== "number" || !timestamp) return null;
334
+ const merkleRoot = timestamp.msg;
335
+ if (!merkleRoot) return null;
336
+ return { merkleRoot, blockHeight: height };
337
+ } catch {
338
+ return null;
339
+ }
340
+ };
293
341
  }
294
342
  function bytesEqual(a, b) {
295
343
  if (a.byteLength !== b.byteLength) return false;
296
344
  for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
297
345
  return true;
298
346
  }
299
- function adaptAnchorVerifier(verifier, digestLookup) {
300
- return async (proofB64, blockHeight, blockHash) => {
301
- const r = await verifier({
302
- proofBytes: base64Decode(proofB64),
303
- digest: digestLookup(blockHash),
304
- blockHeight,
305
- blockHash
306
- });
307
- return r.ok;
308
- };
309
- }
310
347
 
311
- export { DEFAULT_CALENDARS, adaptAnchorVerifier, base64Decode, base64Encode, createCalendarClient, fromStampOts, hexDecode, hexEncode, makeAnchorVerifier, submitToCalendars, toStampOts, upgradeProof };
348
+ export { DEFAULT_CALENDARS, base64Decode, base64Encode, createCalendarClient, fromStampOts, hexDecode, hexEncode, makeAnchorVerifier, makeDefaultAnchorVerifier, submitToCalendars, toStampOts, upgradeProof };
312
349
  //# sourceMappingURL=index.mjs.map
313
350
  //# sourceMappingURL=index.mjs.map
@@ -1 +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.mjs","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"]}
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.mjs","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 real OTS proof parser. We expose two composition points:\n//\n// A) `makeAnchorVerifier({ walkProof, headerSource })` — you bring the\n// parser, we provide the glue that compares Merkle roots against the\n// block header.\n//\n// B) `makeDefaultAnchorVerifier({ headerSource })` — dynamic-imports the\n// `opentimestamps` npm package (optional peer dependency) and wires\n// it into (A). One import, full verification.\n//\n// In both cases the resulting function is what `@orangecheck/stamp-core`\n// expects at the `verifyOtsAnchor` input of `verify()` — no further adaptation\n// needed. The envelope id is passed directly into the hook by stamp-core; we\n// drop the old broken `adaptAnchorVerifier` that tried to derive the digest\n// from the block hash.\n\nimport { base64Decode, hexDecode, 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 * The returned function matches the `verifyOtsAnchor` shape expected by\n * `@orangecheck/stamp-core`'s `verify()`:\n *\n * (proofB64, blockHeight, blockHash, envelopeId) => Promise<boolean>\n *\n * Wire it in as:\n *\n * verify({ envelope, verifyOtsAnchor: makeAnchorVerifier({...}) })\n */\nexport function makeAnchorVerifier(\n cfg: AnchorVerifierConfig\n): (proofB64: string, blockHeight: number, blockHash: string, envelopeId: string) => Promise<boolean> {\n const verifier: AnchorVerifier = async (\n input: AnchorVerificationInput\n ): 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 is 80 bytes; merkle root at offset 36, 32 bytes,\n // little-endian. Proof walkers vary in convention — compare both\n // endiannesses to stay compatible across implementations.\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 return async (proofB64, blockHeight, blockHash, envelopeId) => {\n const r = await verifier({\n proofBytes: base64Decode(proofB64),\n digest: hexDecode(envelopeId),\n blockHeight,\n blockHash,\n });\n return r.ok;\n };\n}\n\n/**\n * Default anchor verifier that dynamically imports the `opentimestamps` npm\n * package and wires it into `makeAnchorVerifier`. The import is lazy so\n * consumers who only need calendar submission don't pay the dep cost.\n *\n * Install `opentimestamps` alongside `@orangecheck/stamp-ots` to use this:\n *\n * npm i @orangecheck/stamp-ots opentimestamps\n *\n * Then:\n *\n * const verifyOtsAnchor = await makeDefaultAnchorVerifier({ headerSource });\n * await verify({ envelope, verifyOtsAnchor });\n *\n * Throws if `opentimestamps` isn't resolvable. That's intentional — callers\n * who haven't installed it should use `makeAnchorVerifier` with their own\n * walker instead, so the failure surfaces immediately rather than silently\n * reporting every stamp as unverified.\n */\nexport async function makeDefaultAnchorVerifier(opts: {\n headerSource: BlockHeaderSource;\n}): Promise<\n (proofB64: string, blockHeight: number, blockHash: string, envelopeId: string) => Promise<boolean>\n> {\n const walkProof = await loadOpenTimestampsWalker();\n return makeAnchorVerifier({ walkProof, headerSource: opts.headerSource });\n}\n\ntype OtsWalker = AnchorVerifierConfig['walkProof'];\n\nasync function loadOpenTimestampsWalker(): Promise<OtsWalker> {\n // Dynamic import so the dep is optional. If the package isn't installed,\n // the error is explicit and actionable.\n let ots: unknown;\n try {\n ots = await import(/* webpackIgnore: true */ 'opentimestamps' as string);\n } catch (e) {\n throw new Error(\n '@orangecheck/stamp-ots: makeDefaultAnchorVerifier requires the optional peer dependency `opentimestamps`. ' +\n `Install it with \\`npm i opentimestamps\\` or supply your own walkProof via makeAnchorVerifier. ` +\n `Underlying error: ${e instanceof Error ? e.message : String(e)}`\n );\n }\n\n // Normalize the module shape. `opentimestamps` publishes both CJS-default\n // and named exports across its versions.\n const mod = (ots as { default?: unknown }).default ?? ots;\n const modAny = mod as Record<string, unknown>;\n\n type DetachedLike = { deserialize(bytes: Uint8Array): unknown; timestamp: unknown };\n type TimestampLike = {\n verify(): Promise<Map<string, { height: number; timestamp?: unknown }>>;\n msg: Uint8Array;\n };\n\n const DetachedCtor =\n (modAny.DetachedTimestampFile as { deserialize(b: Uint8Array): DetachedLike } | undefined) ??\n (modAny.default as Record<string, unknown> | undefined)?.DetachedTimestampFile as\n | { deserialize(b: Uint8Array): DetachedLike }\n | undefined;\n\n if (!DetachedCtor || typeof DetachedCtor.deserialize !== 'function') {\n throw new Error(\n '@orangecheck/stamp-ots: unexpected shape from `opentimestamps` package — ' +\n 'expected DetachedTimestampFile.deserialize. If you are using a non-standard build, ' +\n 'pass your own walkProof via makeAnchorVerifier instead.'\n );\n }\n\n return async ({ proofBytes }) => {\n try {\n const detached = DetachedCtor.deserialize(proofBytes);\n const ts = detached.timestamp as TimestampLike;\n const attestations = await ts.verify();\n const first = attestations.values().next();\n if (first.done) return null;\n const { height, timestamp } = first.value;\n if (typeof height !== 'number' || !timestamp) return null;\n // The attestation provides the block height; the merkle root the\n // walker produces is the timestamp.msg bytes the walker committed\n // against. opentimestamps resolves that through its internal\n // proof chain; reading ts.msg gives the final digest it committed.\n const merkleRoot = (timestamp as { msg?: Uint8Array }).msg;\n if (!merkleRoot) return null;\n return { merkleRoot, blockHeight: height };\n } catch {\n return null;\n }\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orangecheck/stamp-ots",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "OpenTimestamps calendar client, proof helpers, and anchor-verification hooks for OC Stamp envelopes. See https://github.com/orangecheck/oc-stamp-protocol.",
5
5
  "keywords": [
6
6
  "bitcoin",
@@ -48,7 +48,15 @@
48
48
  "prepublishOnly": "npm run clean && npm run build"
49
49
  },
50
50
  "dependencies": {
51
- "@orangecheck/stamp-core": "^0.1.0"
51
+ "@orangecheck/stamp-core": "^0.1.2"
52
+ },
53
+ "peerDependencies": {
54
+ "opentimestamps": "^0.4.9"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "opentimestamps": {
58
+ "optional": true
59
+ }
52
60
  },
53
61
  "devDependencies": {
54
62
  "@types/node": "^22.10.2",
package/src/anchor.ts CHANGED
@@ -6,12 +6,23 @@
6
6
  // 3. Fetching the Bitcoin block header at the declared height and comparing
7
7
  // its Merkle root.
8
8
  //
9
- // Step 1 needs a maintained OTS proof parser. We do not ship one here; this
10
- // file exposes a small helper that composes a parsed-anchor result with a
11
- // block-header source into the `verifyOtsAnchor` callback expected by
12
- // @orangecheck/stamp-core's verify().
9
+ // Step 1 needs a real OTS proof parser. We expose two composition points:
10
+ //
11
+ // A) `makeAnchorVerifier({ walkProof, headerSource })` — you bring the
12
+ // parser, we provide the glue that compares Merkle roots against the
13
+ // block header.
14
+ //
15
+ // B) `makeDefaultAnchorVerifier({ headerSource })` — dynamic-imports the
16
+ // `opentimestamps` npm package (optional peer dependency) and wires
17
+ // it into (A). One import, full verification.
18
+ //
19
+ // In both cases the resulting function is what `@orangecheck/stamp-core`
20
+ // expects at the `verifyOtsAnchor` input of `verify()` — no further adaptation
21
+ // needed. The envelope id is passed directly into the hook by stamp-core; we
22
+ // drop the old broken `adaptAnchorVerifier` that tried to derive the digest
23
+ // from the block hash.
13
24
 
14
- import { base64Decode, hexEncode } from './base64.js';
25
+ import { base64Decode, hexDecode, hexEncode } from './base64.js';
15
26
  import type {
16
27
  AnchorVerificationInput,
17
28
  AnchorVerificationResult,
@@ -39,12 +50,21 @@ export interface AnchorVerifierConfig {
39
50
  /**
40
51
  * Build a concrete anchor verifier from a proof walker plus a header source.
41
52
  *
42
- * Callers wire this into `verify({ verifyOtsAnchor: makeAnchorVerifier(...) })`.
43
- * If either the proof walker returns null or the header's Merkle root does
44
- * not match, the verifier returns `{ ok: false, ... }`.
53
+ * The returned function matches the `verifyOtsAnchor` shape expected by
54
+ * `@orangecheck/stamp-core`'s `verify()`:
55
+ *
56
+ * (proofB64, blockHeight, blockHash, envelopeId) => Promise<boolean>
57
+ *
58
+ * Wire it in as:
59
+ *
60
+ * verify({ envelope, verifyOtsAnchor: makeAnchorVerifier({...}) })
45
61
  */
46
- export function makeAnchorVerifier(cfg: AnchorVerifierConfig): AnchorVerifier {
47
- return async (input: AnchorVerificationInput): Promise<AnchorVerificationResult> => {
62
+ export function makeAnchorVerifier(
63
+ cfg: AnchorVerifierConfig
64
+ ): (proofB64: string, blockHeight: number, blockHash: string, envelopeId: string) => Promise<boolean> {
65
+ const verifier: AnchorVerifier = async (
66
+ input: AnchorVerificationInput
67
+ ): Promise<AnchorVerificationResult> => {
48
68
  const walked = await cfg.walkProof({ proofBytes: input.proofBytes, digest: input.digest });
49
69
  if (!walked) {
50
70
  return { ok: false, reason: 'proof could not be walked to a Bitcoin commitment' };
@@ -59,9 +79,9 @@ export function makeAnchorVerifier(cfg: AnchorVerifierConfig): AnchorVerifier {
59
79
  if (!header) {
60
80
  return { ok: false, reason: `no block header available at height ${input.blockHeight}` };
61
81
  }
62
- // Bitcoin block header: 80 bytes. Merkle root is at offset 36, 32 bytes,
63
- // stored little-endian. Compare to the proof-walked root, which OTS
64
- // reports in big-endian conventionally.
82
+ // Bitcoin block header is 80 bytes; merkle root at offset 36, 32 bytes,
83
+ // little-endian. Proof walkers vary in convention — compare both
84
+ // endiannesses to stay compatible across implementations.
65
85
  if (header.byteLength < 80) {
66
86
  return { ok: false, reason: 'malformed Bitcoin header (too short)' };
67
87
  }
@@ -74,30 +94,111 @@ export function makeAnchorVerifier(cfg: AnchorVerifierConfig): AnchorVerifier {
74
94
  }
75
95
  return { ok: true, merkleRoot: hexEncode(matchesBE ? headerMerkleBE : headerMerkleLE) };
76
96
  };
77
- }
78
97
 
79
- function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
80
- if (a.byteLength !== b.byteLength) return false;
81
- for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
82
- return true;
83
- }
84
-
85
- /**
86
- * Adapt an AnchorVerifier into the signature that
87
- * @orangecheck/stamp-core#verify expects: a function taking (proofB64,
88
- * blockHeight, blockHash) that returns boolean.
89
- */
90
- export function adaptAnchorVerifier(
91
- verifier: AnchorVerifier,
92
- digestLookup: (blockHash: string) => Uint8Array
93
- ): (proofB64: string, blockHeight: number, blockHash: string) => Promise<boolean> {
94
- return async (proofB64, blockHeight, blockHash) => {
98
+ return async (proofB64, blockHeight, blockHash, envelopeId) => {
95
99
  const r = await verifier({
96
100
  proofBytes: base64Decode(proofB64),
97
- digest: digestLookup(blockHash),
101
+ digest: hexDecode(envelopeId),
98
102
  blockHeight,
99
103
  blockHash,
100
104
  });
101
105
  return r.ok;
102
106
  };
103
107
  }
108
+
109
+ /**
110
+ * Default anchor verifier that dynamically imports the `opentimestamps` npm
111
+ * package and wires it into `makeAnchorVerifier`. The import is lazy so
112
+ * consumers who only need calendar submission don't pay the dep cost.
113
+ *
114
+ * Install `opentimestamps` alongside `@orangecheck/stamp-ots` to use this:
115
+ *
116
+ * npm i @orangecheck/stamp-ots opentimestamps
117
+ *
118
+ * Then:
119
+ *
120
+ * const verifyOtsAnchor = await makeDefaultAnchorVerifier({ headerSource });
121
+ * await verify({ envelope, verifyOtsAnchor });
122
+ *
123
+ * Throws if `opentimestamps` isn't resolvable. That's intentional — callers
124
+ * who haven't installed it should use `makeAnchorVerifier` with their own
125
+ * walker instead, so the failure surfaces immediately rather than silently
126
+ * reporting every stamp as unverified.
127
+ */
128
+ export async function makeDefaultAnchorVerifier(opts: {
129
+ headerSource: BlockHeaderSource;
130
+ }): Promise<
131
+ (proofB64: string, blockHeight: number, blockHash: string, envelopeId: string) => Promise<boolean>
132
+ > {
133
+ const walkProof = await loadOpenTimestampsWalker();
134
+ return makeAnchorVerifier({ walkProof, headerSource: opts.headerSource });
135
+ }
136
+
137
+ type OtsWalker = AnchorVerifierConfig['walkProof'];
138
+
139
+ async function loadOpenTimestampsWalker(): Promise<OtsWalker> {
140
+ // Dynamic import so the dep is optional. If the package isn't installed,
141
+ // the error is explicit and actionable.
142
+ let ots: unknown;
143
+ try {
144
+ ots = await import(/* webpackIgnore: true */ 'opentimestamps' as string);
145
+ } catch (e) {
146
+ throw new Error(
147
+ '@orangecheck/stamp-ots: makeDefaultAnchorVerifier requires the optional peer dependency `opentimestamps`. ' +
148
+ `Install it with \`npm i opentimestamps\` or supply your own walkProof via makeAnchorVerifier. ` +
149
+ `Underlying error: ${e instanceof Error ? e.message : String(e)}`
150
+ );
151
+ }
152
+
153
+ // Normalize the module shape. `opentimestamps` publishes both CJS-default
154
+ // and named exports across its versions.
155
+ const mod = (ots as { default?: unknown }).default ?? ots;
156
+ const modAny = mod as Record<string, unknown>;
157
+
158
+ type DetachedLike = { deserialize(bytes: Uint8Array): unknown; timestamp: unknown };
159
+ type TimestampLike = {
160
+ verify(): Promise<Map<string, { height: number; timestamp?: unknown }>>;
161
+ msg: Uint8Array;
162
+ };
163
+
164
+ const DetachedCtor =
165
+ (modAny.DetachedTimestampFile as { deserialize(b: Uint8Array): DetachedLike } | undefined) ??
166
+ (modAny.default as Record<string, unknown> | undefined)?.DetachedTimestampFile as
167
+ | { deserialize(b: Uint8Array): DetachedLike }
168
+ | undefined;
169
+
170
+ if (!DetachedCtor || typeof DetachedCtor.deserialize !== 'function') {
171
+ throw new Error(
172
+ '@orangecheck/stamp-ots: unexpected shape from `opentimestamps` package — ' +
173
+ 'expected DetachedTimestampFile.deserialize. If you are using a non-standard build, ' +
174
+ 'pass your own walkProof via makeAnchorVerifier instead.'
175
+ );
176
+ }
177
+
178
+ return async ({ proofBytes }) => {
179
+ try {
180
+ const detached = DetachedCtor.deserialize(proofBytes);
181
+ const ts = detached.timestamp as TimestampLike;
182
+ const attestations = await ts.verify();
183
+ const first = attestations.values().next();
184
+ if (first.done) return null;
185
+ const { height, timestamp } = first.value;
186
+ if (typeof height !== 'number' || !timestamp) return null;
187
+ // The attestation provides the block height; the merkle root the
188
+ // walker produces is the timestamp.msg bytes the walker committed
189
+ // against. opentimestamps resolves that through its internal
190
+ // proof chain; reading ts.msg gives the final digest it committed.
191
+ const merkleRoot = (timestamp as { msg?: Uint8Array }).msg;
192
+ if (!merkleRoot) return null;
193
+ return { merkleRoot, blockHeight: height };
194
+ } catch {
195
+ return null;
196
+ }
197
+ };
198
+ }
199
+
200
+ function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
201
+ if (a.byteLength !== b.byteLength) return false;
202
+ for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
203
+ return true;
204
+ }
package/src/index.ts CHANGED
@@ -6,5 +6,5 @@ export { submitToCalendars } from './submit.js';
6
6
  export type { SubmitOptions } from './submit.js';
7
7
  export { upgradeProof } from './upgrade.js';
8
8
  export type { UpgradeOptions } from './upgrade.js';
9
- export { makeAnchorVerifier, adaptAnchorVerifier } from './anchor.js';
9
+ export { makeAnchorVerifier, makeDefaultAnchorVerifier } from './anchor.js';
10
10
  export type { AnchorVerifierConfig } from './anchor.js';