🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@orangecheck/stamp-core

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@orangecheck/stamp-core - npm Package Compare versions

Comparing version
0.1.0
to
0.1.1
+7
-1
dist/canonical.d.mts
import { StampEnvelope, CanonicalMessageInput } from './types.mjs';
declare function canonicalMessage(input: CanonicalMessageInput): string;
declare function validateCanonicalInput(input: CanonicalMessageInput): {
ok: true;
} | {
ok: false;
reason: string;
};
declare function canonicalMessageBytes(input: CanonicalMessageInput): Uint8Array;

@@ -15,2 +21,2 @@ declare function computeEnvelopeId(input: CanonicalMessageInput): string;

export { type JsonValue, canonicalEnvelopeBytes, canonicalMessage, canonicalMessageBytes, canonicalize, canonicalizeEnvelope, computeEnvelopeId, hexEncode, sha256Hex };
export { type JsonValue, canonicalEnvelopeBytes, canonicalMessage, canonicalMessageBytes, canonicalize, canonicalizeEnvelope, computeEnvelopeId, hexEncode, sha256Hex, validateCanonicalInput };
import { StampEnvelope, CanonicalMessageInput } from './types.js';
declare function canonicalMessage(input: CanonicalMessageInput): string;
declare function validateCanonicalInput(input: CanonicalMessageInput): {
ok: true;
} | {
ok: false;
reason: string;
};
declare function canonicalMessageBytes(input: CanonicalMessageInput): Uint8Array;

@@ -15,2 +21,2 @@ declare function computeEnvelopeId(input: CanonicalMessageInput): string;

export { type JsonValue, canonicalEnvelopeBytes, canonicalMessage, canonicalMessageBytes, canonicalize, canonicalizeEnvelope, computeEnvelopeId, hexEncode, sha256Hex };
export { type JsonValue, canonicalEnvelopeBytes, canonicalMessage, canonicalMessageBytes, canonicalize, canonicalizeEnvelope, computeEnvelopeId, hexEncode, sha256Hex, validateCanonicalInput };

@@ -16,2 +16,20 @@ 'use strict';

}
function validateCanonicalInput(input) {
if (!input.address || /[\s\x00-\x1f]/.test(input.address)) {
return { ok: false, reason: "address must be non-empty with no whitespace or control chars" };
}
if (!/^sha256:[0-9a-f]{64}$/.test(input.content_hash)) {
return { ok: false, reason: "content_hash must match /^sha256:[0-9a-f]{64}$/" };
}
if (!Number.isInteger(input.content_length) || input.content_length < 0) {
return { ok: false, reason: "content_length must be a non-negative integer" };
}
if (!input.content_mime || /[\x00-\x1f]/.test(input.content_mime)) {
return { ok: false, reason: "content_mime must be non-empty with no control chars" };
}
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/.test(input.signed_at)) {
return { ok: false, reason: "signed_at must be ISO 8601 UTC ending in Z" };
}
return { ok: true };
}
function canonicalMessageBytes(input) {

@@ -109,3 +127,4 @@ return new TextEncoder().encode(canonicalMessage(input));

exports.sha256Hex = sha256Hex;
exports.validateCanonicalInput = validateCanonicalInput;
//# sourceMappingURL=canonical.js.map
//# sourceMappingURL=canonical.js.map
+1
-1

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/canonical.ts"],"names":["sha256"],"mappings":"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","file":"canonical.js","sourcesContent":["// Canonical message + RFC 8785 JSON canonicalization for OC Stamp.\n//\n// Two distinct canonicalizers live here:\n//\n// 1. canonicalMessage(input) — the exact byte sequence the signer signs via\n// BIP-322. LF-separated, no trailing LF after signed_at. SPEC §3.1.\n//\n// 2. canonicalizeEnvelope(env) — RFC 8785 JSON canonicalization of the wire\n// envelope, used for stable serialization / equality checks. SPEC §5.\n//\n// Only (1) participates in the id derivation. (2) is for test-vector byte\n// equality and any future operation that needs deterministic envelope bytes.\n\nimport { sha256 } from '@noble/hashes/sha256';\n\nimport type { CanonicalMessageInput, StampEnvelope } from './types.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonical message (SPEC §3.1)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function canonicalMessage(input: CanonicalMessageInput): string {\n // The domain separator `oc-stamp:v1` commits the whole signing ceremony to\n // this spec version. Future incompatible changes MUST bump the version\n // number here so a v1 signature can never be replayed against a v2 domain.\n return [\n 'oc-stamp:v1',\n `address: ${input.address}`,\n `content_hash: ${input.content_hash}`,\n `content_length: ${input.content_length}`,\n `content_mime: ${input.content_mime}`,\n `signed_at: ${input.signed_at}`,\n ].join('\\n');\n}\n\nexport function canonicalMessageBytes(input: CanonicalMessageInput): Uint8Array {\n return new TextEncoder().encode(canonicalMessage(input));\n}\n\n/**\n * Compute the envelope id as lowercase hex of sha256(canonical_message_bytes).\n * Pure function — the single source of truth for identity derivation.\n */\nexport function computeEnvelopeId(input: CanonicalMessageInput): string {\n const bytes = canonicalMessageBytes(input);\n return hexEncode(sha256(bytes));\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Envelope canonicalization (SPEC §5; RFC 8785)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nexport function canonicalizeEnvelope(env: StampEnvelope): string {\n return canonicalize(env as unknown as JsonValue);\n}\n\nexport function canonicalEnvelopeBytes(env: StampEnvelope): Uint8Array {\n return new TextEncoder().encode(canonicalize(env as unknown as JsonValue) + '\\n');\n}\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value);\n}\n\nfunction encode(v: JsonValue): string {\n if (v === null) return 'null';\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (typeof v === 'number') return encodeNumber(v);\n if (typeof v === 'string') return encodeString(v);\n if (Array.isArray(v)) {\n const parts: string[] = [];\n for (const item of v) parts.push(encode(item));\n return '[' + parts.join(',') + ']';\n }\n if (typeof v === 'object') {\n const keys = Object.keys(v).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const inner = v[k];\n if (inner === undefined) continue;\n parts.push(encodeString(k) + ':' + encode(inner as JsonValue));\n }\n return '{' + parts.join(',') + '}';\n }\n throw new Error('cannot canonicalize value of type ' + typeof v);\n}\n\nfunction encodeNumber(n: number): string {\n if (!Number.isFinite(n)) throw new Error('non-finite number not JSON-canonicalizable');\n if (Object.is(n, -0)) return '0';\n if (Number.isInteger(n) && Math.abs(n) < 1e21) {\n return String(n);\n }\n return JSON.stringify(n);\n}\n\nconst ESCAPE_TABLE: Record<string, string> = {\n '\\\\': '\\\\\\\\',\n '\"': '\\\\\"',\n '\\b': '\\\\b',\n '\\f': '\\\\f',\n '\\n': '\\\\n',\n '\\r': '\\\\r',\n '\\t': '\\\\t',\n};\n\nfunction encodeString(s: string): string {\n let out = '\"';\n for (let i = 0; i < s.length; i++) {\n const ch = s[i]!;\n const code = ch.charCodeAt(0);\n if (ESCAPE_TABLE[ch]) {\n out += ESCAPE_TABLE[ch];\n } else if (code < 0x20) {\n out += '\\\\u' + code.toString(16).padStart(4, '0');\n } else {\n out += ch;\n }\n }\n out += '\"';\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Small utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst HEX_TABLE = '0123456789abcdef';\n\nexport function hexEncode(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i]!;\n out += HEX_TABLE[(b >> 4) & 0x0f]! + HEX_TABLE[b & 0x0f]!;\n }\n return out;\n}\n\nexport function sha256Hex(bytes: Uint8Array): string {\n return hexEncode(sha256(bytes));\n}\n"]}
{"version":3,"sources":["../src/canonical.ts"],"names":["sha256"],"mappings":"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","file":"canonical.js","sourcesContent":["// Canonical message + RFC 8785 JSON canonicalization for OC Stamp.\n//\n// Two distinct canonicalizers live here:\n//\n// 1. canonicalMessage(input) — the exact byte sequence the signer signs via\n// BIP-322. LF-separated, no trailing LF after signed_at. SPEC §3.1.\n//\n// 2. canonicalizeEnvelope(env) — RFC 8785 JSON canonicalization of the wire\n// envelope, used for stable serialization / equality checks. SPEC §5.\n//\n// Only (1) participates in the id derivation. (2) is for test-vector byte\n// equality and any future operation that needs deterministic envelope bytes.\n\nimport { sha256 } from '@noble/hashes/sha256';\n\nimport type { CanonicalMessageInput, StampEnvelope } from './types.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonical message (SPEC §3.1)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function canonicalMessage(input: CanonicalMessageInput): string {\n // The domain separator `oc-stamp:v1` commits the whole signing ceremony to\n // this spec version. Future incompatible changes MUST bump the version\n // number here so a v1 signature can never be replayed against a v2 domain.\n return [\n 'oc-stamp:v1',\n `address: ${input.address}`,\n `content_hash: ${input.content_hash}`,\n `content_length: ${input.content_length}`,\n `content_mime: ${input.content_mime}`,\n `signed_at: ${input.signed_at}`,\n ].join('\\n');\n}\n\n/**\n * Check whether a CanonicalMessageInput is well-formed per the spec's field\n * rules (§3, §4). Returns `{ok: true}` if every field is valid, or\n * `{ok: false, reason}` pointing at the first violation.\n *\n * Called by `stamp()` before producing the canonical bytes. Exposed for\n * UIs that want to preview validity without throwing.\n */\nexport function validateCanonicalInput(input: CanonicalMessageInput):\n | { ok: true }\n | { ok: false, reason: string } {\n // No control chars or whitespace breaks in fields — any of those would\n // invalidate the 6-line structure and produce an id nobody else can\n // reconstruct. The whole canonical message is ASCII-safe by design.\n if (!input.address || /[\\s\\x00-\\x1f]/.test(input.address)) {\n return { ok: false, reason: 'address must be non-empty with no whitespace or control chars' };\n }\n if (!/^sha256:[0-9a-f]{64}$/.test(input.content_hash)) {\n return { ok: false, reason: 'content_hash must match /^sha256:[0-9a-f]{64}$/' };\n }\n if (!Number.isInteger(input.content_length) || input.content_length < 0) {\n return { ok: false, reason: 'content_length must be a non-negative integer' };\n }\n // MIME per RFC 6838: \"type/subtype[+suffix][;params]\". We accept anything\n // that's non-empty and control-char-free; fine-grained parsing is out of\n // scope and would reject legitimate media types.\n if (!input.content_mime || /[\\x00-\\x1f]/.test(input.content_mime)) {\n return { ok: false, reason: 'content_mime must be non-empty with no control chars' };\n }\n if (!/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$/.test(input.signed_at)) {\n return { ok: false, reason: 'signed_at must be ISO 8601 UTC ending in Z' };\n }\n return { ok: true };\n}\n\nexport function canonicalMessageBytes(input: CanonicalMessageInput): Uint8Array {\n return new TextEncoder().encode(canonicalMessage(input));\n}\n\n/**\n * Compute the envelope id as lowercase hex of sha256(canonical_message_bytes).\n * Pure function — the single source of truth for identity derivation.\n */\nexport function computeEnvelopeId(input: CanonicalMessageInput): string {\n const bytes = canonicalMessageBytes(input);\n return hexEncode(sha256(bytes));\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Envelope canonicalization (SPEC §5; RFC 8785)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nexport function canonicalizeEnvelope(env: StampEnvelope): string {\n return canonicalize(env as unknown as JsonValue);\n}\n\nexport function canonicalEnvelopeBytes(env: StampEnvelope): Uint8Array {\n return new TextEncoder().encode(canonicalize(env as unknown as JsonValue) + '\\n');\n}\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value);\n}\n\nfunction encode(v: JsonValue): string {\n if (v === null) return 'null';\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (typeof v === 'number') return encodeNumber(v);\n if (typeof v === 'string') return encodeString(v);\n if (Array.isArray(v)) {\n const parts: string[] = [];\n for (const item of v) parts.push(encode(item));\n return '[' + parts.join(',') + ']';\n }\n if (typeof v === 'object') {\n const keys = Object.keys(v).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const inner = v[k];\n if (inner === undefined) continue;\n parts.push(encodeString(k) + ':' + encode(inner as JsonValue));\n }\n return '{' + parts.join(',') + '}';\n }\n throw new Error('cannot canonicalize value of type ' + typeof v);\n}\n\nfunction encodeNumber(n: number): string {\n if (!Number.isFinite(n)) throw new Error('non-finite number not JSON-canonicalizable');\n if (Object.is(n, -0)) return '0';\n if (Number.isInteger(n) && Math.abs(n) < 1e21) {\n return String(n);\n }\n return JSON.stringify(n);\n}\n\nconst ESCAPE_TABLE: Record<string, string> = {\n '\\\\': '\\\\\\\\',\n '\"': '\\\\\"',\n '\\b': '\\\\b',\n '\\f': '\\\\f',\n '\\n': '\\\\n',\n '\\r': '\\\\r',\n '\\t': '\\\\t',\n};\n\nfunction encodeString(s: string): string {\n let out = '\"';\n for (let i = 0; i < s.length; i++) {\n const ch = s[i]!;\n const code = ch.charCodeAt(0);\n if (ESCAPE_TABLE[ch]) {\n out += ESCAPE_TABLE[ch];\n } else if (code < 0x20) {\n out += '\\\\u' + code.toString(16).padStart(4, '0');\n } else {\n out += ch;\n }\n }\n out += '\"';\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Small utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst HEX_TABLE = '0123456789abcdef';\n\nexport function hexEncode(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i]!;\n out += HEX_TABLE[(b >> 4) & 0x0f]! + HEX_TABLE[b & 0x0f]!;\n }\n return out;\n}\n\nexport function sha256Hex(bytes: Uint8Array): string {\n return hexEncode(sha256(bytes));\n}\n"]}

@@ -14,2 +14,20 @@ import { sha256 } from '@noble/hashes/sha256';

}
function validateCanonicalInput(input) {
if (!input.address || /[\s\x00-\x1f]/.test(input.address)) {
return { ok: false, reason: "address must be non-empty with no whitespace or control chars" };
}
if (!/^sha256:[0-9a-f]{64}$/.test(input.content_hash)) {
return { ok: false, reason: "content_hash must match /^sha256:[0-9a-f]{64}$/" };
}
if (!Number.isInteger(input.content_length) || input.content_length < 0) {
return { ok: false, reason: "content_length must be a non-negative integer" };
}
if (!input.content_mime || /[\x00-\x1f]/.test(input.content_mime)) {
return { ok: false, reason: "content_mime must be non-empty with no control chars" };
}
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/.test(input.signed_at)) {
return { ok: false, reason: "signed_at must be ISO 8601 UTC ending in Z" };
}
return { ok: true };
}
function canonicalMessageBytes(input) {

@@ -99,4 +117,4 @@ return new TextEncoder().encode(canonicalMessage(input));

export { canonicalEnvelopeBytes, canonicalMessage, canonicalMessageBytes, canonicalize, canonicalizeEnvelope, computeEnvelopeId, hexEncode, sha256Hex };
export { canonicalEnvelopeBytes, canonicalMessage, canonicalMessageBytes, canonicalize, canonicalizeEnvelope, computeEnvelopeId, hexEncode, sha256Hex, validateCanonicalInput };
//# sourceMappingURL=canonical.mjs.map
//# sourceMappingURL=canonical.mjs.map

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/canonical.ts"],"names":[],"mappings":"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","file":"canonical.mjs","sourcesContent":["// Canonical message + RFC 8785 JSON canonicalization for OC Stamp.\n//\n// Two distinct canonicalizers live here:\n//\n// 1. canonicalMessage(input) — the exact byte sequence the signer signs via\n// BIP-322. LF-separated, no trailing LF after signed_at. SPEC §3.1.\n//\n// 2. canonicalizeEnvelope(env) — RFC 8785 JSON canonicalization of the wire\n// envelope, used for stable serialization / equality checks. SPEC §5.\n//\n// Only (1) participates in the id derivation. (2) is for test-vector byte\n// equality and any future operation that needs deterministic envelope bytes.\n\nimport { sha256 } from '@noble/hashes/sha256';\n\nimport type { CanonicalMessageInput, StampEnvelope } from './types.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonical message (SPEC §3.1)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function canonicalMessage(input: CanonicalMessageInput): string {\n // The domain separator `oc-stamp:v1` commits the whole signing ceremony to\n // this spec version. Future incompatible changes MUST bump the version\n // number here so a v1 signature can never be replayed against a v2 domain.\n return [\n 'oc-stamp:v1',\n `address: ${input.address}`,\n `content_hash: ${input.content_hash}`,\n `content_length: ${input.content_length}`,\n `content_mime: ${input.content_mime}`,\n `signed_at: ${input.signed_at}`,\n ].join('\\n');\n}\n\nexport function canonicalMessageBytes(input: CanonicalMessageInput): Uint8Array {\n return new TextEncoder().encode(canonicalMessage(input));\n}\n\n/**\n * Compute the envelope id as lowercase hex of sha256(canonical_message_bytes).\n * Pure function — the single source of truth for identity derivation.\n */\nexport function computeEnvelopeId(input: CanonicalMessageInput): string {\n const bytes = canonicalMessageBytes(input);\n return hexEncode(sha256(bytes));\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Envelope canonicalization (SPEC §5; RFC 8785)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nexport function canonicalizeEnvelope(env: StampEnvelope): string {\n return canonicalize(env as unknown as JsonValue);\n}\n\nexport function canonicalEnvelopeBytes(env: StampEnvelope): Uint8Array {\n return new TextEncoder().encode(canonicalize(env as unknown as JsonValue) + '\\n');\n}\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value);\n}\n\nfunction encode(v: JsonValue): string {\n if (v === null) return 'null';\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (typeof v === 'number') return encodeNumber(v);\n if (typeof v === 'string') return encodeString(v);\n if (Array.isArray(v)) {\n const parts: string[] = [];\n for (const item of v) parts.push(encode(item));\n return '[' + parts.join(',') + ']';\n }\n if (typeof v === 'object') {\n const keys = Object.keys(v).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const inner = v[k];\n if (inner === undefined) continue;\n parts.push(encodeString(k) + ':' + encode(inner as JsonValue));\n }\n return '{' + parts.join(',') + '}';\n }\n throw new Error('cannot canonicalize value of type ' + typeof v);\n}\n\nfunction encodeNumber(n: number): string {\n if (!Number.isFinite(n)) throw new Error('non-finite number not JSON-canonicalizable');\n if (Object.is(n, -0)) return '0';\n if (Number.isInteger(n) && Math.abs(n) < 1e21) {\n return String(n);\n }\n return JSON.stringify(n);\n}\n\nconst ESCAPE_TABLE: Record<string, string> = {\n '\\\\': '\\\\\\\\',\n '\"': '\\\\\"',\n '\\b': '\\\\b',\n '\\f': '\\\\f',\n '\\n': '\\\\n',\n '\\r': '\\\\r',\n '\\t': '\\\\t',\n};\n\nfunction encodeString(s: string): string {\n let out = '\"';\n for (let i = 0; i < s.length; i++) {\n const ch = s[i]!;\n const code = ch.charCodeAt(0);\n if (ESCAPE_TABLE[ch]) {\n out += ESCAPE_TABLE[ch];\n } else if (code < 0x20) {\n out += '\\\\u' + code.toString(16).padStart(4, '0');\n } else {\n out += ch;\n }\n }\n out += '\"';\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Small utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst HEX_TABLE = '0123456789abcdef';\n\nexport function hexEncode(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i]!;\n out += HEX_TABLE[(b >> 4) & 0x0f]! + HEX_TABLE[b & 0x0f]!;\n }\n return out;\n}\n\nexport function sha256Hex(bytes: Uint8Array): string {\n return hexEncode(sha256(bytes));\n}\n"]}
{"version":3,"sources":["../src/canonical.ts"],"names":[],"mappings":"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","file":"canonical.mjs","sourcesContent":["// Canonical message + RFC 8785 JSON canonicalization for OC Stamp.\n//\n// Two distinct canonicalizers live here:\n//\n// 1. canonicalMessage(input) — the exact byte sequence the signer signs via\n// BIP-322. LF-separated, no trailing LF after signed_at. SPEC §3.1.\n//\n// 2. canonicalizeEnvelope(env) — RFC 8785 JSON canonicalization of the wire\n// envelope, used for stable serialization / equality checks. SPEC §5.\n//\n// Only (1) participates in the id derivation. (2) is for test-vector byte\n// equality and any future operation that needs deterministic envelope bytes.\n\nimport { sha256 } from '@noble/hashes/sha256';\n\nimport type { CanonicalMessageInput, StampEnvelope } from './types.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonical message (SPEC §3.1)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function canonicalMessage(input: CanonicalMessageInput): string {\n // The domain separator `oc-stamp:v1` commits the whole signing ceremony to\n // this spec version. Future incompatible changes MUST bump the version\n // number here so a v1 signature can never be replayed against a v2 domain.\n return [\n 'oc-stamp:v1',\n `address: ${input.address}`,\n `content_hash: ${input.content_hash}`,\n `content_length: ${input.content_length}`,\n `content_mime: ${input.content_mime}`,\n `signed_at: ${input.signed_at}`,\n ].join('\\n');\n}\n\n/**\n * Check whether a CanonicalMessageInput is well-formed per the spec's field\n * rules (§3, §4). Returns `{ok: true}` if every field is valid, or\n * `{ok: false, reason}` pointing at the first violation.\n *\n * Called by `stamp()` before producing the canonical bytes. Exposed for\n * UIs that want to preview validity without throwing.\n */\nexport function validateCanonicalInput(input: CanonicalMessageInput):\n | { ok: true }\n | { ok: false, reason: string } {\n // No control chars or whitespace breaks in fields — any of those would\n // invalidate the 6-line structure and produce an id nobody else can\n // reconstruct. The whole canonical message is ASCII-safe by design.\n if (!input.address || /[\\s\\x00-\\x1f]/.test(input.address)) {\n return { ok: false, reason: 'address must be non-empty with no whitespace or control chars' };\n }\n if (!/^sha256:[0-9a-f]{64}$/.test(input.content_hash)) {\n return { ok: false, reason: 'content_hash must match /^sha256:[0-9a-f]{64}$/' };\n }\n if (!Number.isInteger(input.content_length) || input.content_length < 0) {\n return { ok: false, reason: 'content_length must be a non-negative integer' };\n }\n // MIME per RFC 6838: \"type/subtype[+suffix][;params]\". We accept anything\n // that's non-empty and control-char-free; fine-grained parsing is out of\n // scope and would reject legitimate media types.\n if (!input.content_mime || /[\\x00-\\x1f]/.test(input.content_mime)) {\n return { ok: false, reason: 'content_mime must be non-empty with no control chars' };\n }\n if (!/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$/.test(input.signed_at)) {\n return { ok: false, reason: 'signed_at must be ISO 8601 UTC ending in Z' };\n }\n return { ok: true };\n}\n\nexport function canonicalMessageBytes(input: CanonicalMessageInput): Uint8Array {\n return new TextEncoder().encode(canonicalMessage(input));\n}\n\n/**\n * Compute the envelope id as lowercase hex of sha256(canonical_message_bytes).\n * Pure function — the single source of truth for identity derivation.\n */\nexport function computeEnvelopeId(input: CanonicalMessageInput): string {\n const bytes = canonicalMessageBytes(input);\n return hexEncode(sha256(bytes));\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Envelope canonicalization (SPEC §5; RFC 8785)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nexport function canonicalizeEnvelope(env: StampEnvelope): string {\n return canonicalize(env as unknown as JsonValue);\n}\n\nexport function canonicalEnvelopeBytes(env: StampEnvelope): Uint8Array {\n return new TextEncoder().encode(canonicalize(env as unknown as JsonValue) + '\\n');\n}\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value);\n}\n\nfunction encode(v: JsonValue): string {\n if (v === null) return 'null';\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (typeof v === 'number') return encodeNumber(v);\n if (typeof v === 'string') return encodeString(v);\n if (Array.isArray(v)) {\n const parts: string[] = [];\n for (const item of v) parts.push(encode(item));\n return '[' + parts.join(',') + ']';\n }\n if (typeof v === 'object') {\n const keys = Object.keys(v).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const inner = v[k];\n if (inner === undefined) continue;\n parts.push(encodeString(k) + ':' + encode(inner as JsonValue));\n }\n return '{' + parts.join(',') + '}';\n }\n throw new Error('cannot canonicalize value of type ' + typeof v);\n}\n\nfunction encodeNumber(n: number): string {\n if (!Number.isFinite(n)) throw new Error('non-finite number not JSON-canonicalizable');\n if (Object.is(n, -0)) return '0';\n if (Number.isInteger(n) && Math.abs(n) < 1e21) {\n return String(n);\n }\n return JSON.stringify(n);\n}\n\nconst ESCAPE_TABLE: Record<string, string> = {\n '\\\\': '\\\\\\\\',\n '\"': '\\\\\"',\n '\\b': '\\\\b',\n '\\f': '\\\\f',\n '\\n': '\\\\n',\n '\\r': '\\\\r',\n '\\t': '\\\\t',\n};\n\nfunction encodeString(s: string): string {\n let out = '\"';\n for (let i = 0; i < s.length; i++) {\n const ch = s[i]!;\n const code = ch.charCodeAt(0);\n if (ESCAPE_TABLE[ch]) {\n out += ESCAPE_TABLE[ch];\n } else if (code < 0x20) {\n out += '\\\\u' + code.toString(16).padStart(4, '0');\n } else {\n out += ch;\n }\n }\n out += '\"';\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Small utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst HEX_TABLE = '0123456789abcdef';\n\nexport function hexEncode(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i]!;\n out += HEX_TABLE[(b >> 4) & 0x0f]! + HEX_TABLE[b & 0x0f]!;\n }\n return out;\n}\n\nexport function sha256Hex(bytes: Uint8Array): string {\n return hexEncode(sha256(bytes));\n}\n"]}
import { VerifyErrorCode, StampInput, StampEnvelope, VerifyInput, VerifyResult } from './types.mjs';
export { CanonicalMessageInput, ENVELOPE_VERSION, EnvelopeKind, OtsStatus, StampContent, StampOts, StampSignature, StampSigner, StampStake, VerifyErr, VerifyOk } from './types.mjs';
export { JsonValue, canonicalEnvelopeBytes, canonicalMessage, canonicalMessageBytes, canonicalize, canonicalizeEnvelope, computeEnvelopeId, hexEncode, sha256Hex } from './canonical.mjs';
export { JsonValue, canonicalEnvelopeBytes, canonicalMessage, canonicalMessageBytes, canonicalize, canonicalizeEnvelope, computeEnvelopeId, hexEncode, sha256Hex, validateCanonicalInput } from './canonical.mjs';
declare function hashContent(bytes: Uint8Array): {
hash: string;
length: number;
};
declare class StampError extends Error {

@@ -12,2 +16,2 @@ code: VerifyErrorCode;

export { StampEnvelope, StampError, StampInput, VerifyErrorCode, VerifyInput, VerifyResult, stamp, verify };
export { StampEnvelope, StampError, StampInput, VerifyErrorCode, VerifyInput, VerifyResult, hashContent, stamp, verify };
import { VerifyErrorCode, StampInput, StampEnvelope, VerifyInput, VerifyResult } from './types.js';
export { CanonicalMessageInput, ENVELOPE_VERSION, EnvelopeKind, OtsStatus, StampContent, StampOts, StampSignature, StampSigner, StampStake, VerifyErr, VerifyOk } from './types.js';
export { JsonValue, canonicalEnvelopeBytes, canonicalMessage, canonicalMessageBytes, canonicalize, canonicalizeEnvelope, computeEnvelopeId, hexEncode, sha256Hex } from './canonical.js';
export { JsonValue, canonicalEnvelopeBytes, canonicalMessage, canonicalMessageBytes, canonicalize, canonicalizeEnvelope, computeEnvelopeId, hexEncode, sha256Hex, validateCanonicalInput } from './canonical.js';
declare function hashContent(bytes: Uint8Array): {
hash: string;
length: number;
};
declare class StampError extends Error {

@@ -12,2 +16,2 @@ code: VerifyErrorCode;

export { StampEnvelope, StampError, StampInput, VerifyErrorCode, VerifyInput, VerifyResult, stamp, verify };
export { StampEnvelope, StampError, StampInput, VerifyErrorCode, VerifyInput, VerifyResult, hashContent, stamp, verify };

@@ -17,2 +17,20 @@ 'use strict';

}
function validateCanonicalInput(input) {
if (!input.address || /[\s\x00-\x1f]/.test(input.address)) {
return { ok: false, reason: "address must be non-empty with no whitespace or control chars" };
}
if (!/^sha256:[0-9a-f]{64}$/.test(input.content_hash)) {
return { ok: false, reason: "content_hash must match /^sha256:[0-9a-f]{64}$/" };
}
if (!Number.isInteger(input.content_length) || input.content_length < 0) {
return { ok: false, reason: "content_length must be a non-negative integer" };
}
if (!input.content_mime || /[\x00-\x1f]/.test(input.content_mime)) {
return { ok: false, reason: "content_mime must be non-empty with no control chars" };
}
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/.test(input.signed_at)) {
return { ok: false, reason: "signed_at must be ISO 8601 UTC ending in Z" };
}
return { ok: true };
}
function canonicalMessageBytes(input) {

@@ -101,2 +119,8 @@ return new TextEncoder().encode(canonicalMessage(input));

}
function hashContent(bytes) {
return {
hash: "sha256:" + hexEncode(sha256.sha256(bytes)),
length: bytes.byteLength
};
}
var StampError = class extends Error {

@@ -137,2 +161,6 @@ constructor(code, message) {

};
const v = validateCanonicalInput(canon);
if (!v.ok) {
throw new StampError("E_MALFORMED", v.reason);
}
const id = computeEnvelopeId(canon);

@@ -270,7 +298,9 @@ const sigValue = await input.signer.signMessage(id);

exports.computeEnvelopeId = computeEnvelopeId;
exports.hashContent = hashContent;
exports.hexEncode = hexEncode;
exports.sha256Hex = sha256Hex;
exports.stamp = stamp;
exports.validateCanonicalInput = validateCanonicalInput;
exports.verify = verify;
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/types.ts","../src/canonical.ts","../src/stamp.ts"],"names":["sha256"],"mappings":"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","file":"index.js","sourcesContent":["// Wire types for OC Stamp v1 envelopes. See SPEC.md §4.\n\nexport const ENVELOPE_VERSION = 1 as const;\n\nexport type EnvelopeKind = 'stamp';\n\nexport interface StampContent {\n /** \"sha256:<64-hex>\" */\n hash: string;\n length: number;\n /** RFC 6838 media type. \"application/octet-stream\" if unknown. */\n mime: string;\n /** Optional pointer — not cryptographic. */\n ref: string | null;\n}\n\nexport interface StampSigner {\n address: string;\n alg: 'bip322';\n}\n\nexport interface StampStake {\n /** SHA-256 hex of an OrangeCheck canonical message. */\n attestation_id: string;\n sats_bonded: number;\n days_unspent: number;\n}\n\nexport type OtsStatus = 'pending' | 'confirmed';\n\nexport interface StampOts {\n status: OtsStatus;\n /** Base64-encoded OpenTimestamps proof. */\n proof: string;\n calendars: string[];\n block_height: number | null;\n block_hash: string | null;\n /** ISO 8601 UTC of the upgrade; null while pending. */\n upgraded_at: string | null;\n}\n\nexport interface StampSignature {\n alg: 'bip322';\n pubkey: string; // MUST equal signer.address\n value: string; // base64 BIP-322 signature over hex(id)\n}\n\nexport interface StampEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: EnvelopeKind;\n id: string; // 64-hex of sha256(canonical_message)\n content: StampContent;\n signer: StampSigner;\n signed_at: string; // ISO 8601 UTC\n stake: StampStake | null;\n ots: StampOts | null;\n sig: StampSignature;\n}\n\n// Inputs to stamp() / verify().\n\nexport interface CanonicalMessageInput {\n address: string;\n /** \"sha256:<64-hex>\" */\n content_hash: string;\n content_length: number;\n /** \"application/octet-stream\" if unknown. */\n content_mime: string;\n /** ISO 8601 UTC. */\n signed_at: string;\n}\n\nexport interface StampInput {\n /** The bytes being stamped, or their pre-computed hash. Exactly one is required. */\n content: Uint8Array | { hash: string; length: number };\n mime: string;\n ref?: string | null;\n signer: {\n address: string;\n /**\n * Returns a BIP-322 base64 signature of the given message bytes as UTF-8.\n * The caller supplies the wallet adapter. Called once per stamp() invocation.\n */\n signMessage: (msg: string) => Promise<string>;\n };\n stake?: StampStake | null;\n /** Defaults to now. */\n signedAt?: Date;\n}\n\nexport interface VerifyInput {\n envelope: StampEnvelope;\n /**\n * Optional: the content bytes. If present, verify() additionally checks that\n * sha256(content) matches envelope.content.hash.\n */\n content?: Uint8Array;\n /**\n * Verifier for BIP-322. Plugged in by the caller because signature verification\n * libraries differ across Node, browser, and CLI environments.\n */\n verifyBip322?: (msg: string, signatureB64: string, address: string) => Promise<boolean>;\n /**\n * Optional hook for OTS anchor verification. If present and ots.status === \"confirmed\",\n * the caller-supplied function walks the proof and returns true iff it chains to a\n * real Bitcoin block header at the declared height.\n *\n * The default behavior (anchor hook omitted) is to ACCEPT a confirmed envelope on\n * shape alone — useful for preview UIs. Callers that attach legal or economic\n * weight MUST supply this hook.\n */\n verifyOtsAnchor?: (proofB64: string, blockHeight: number, blockHash: string) => Promise<boolean>;\n /**\n * Skip the BIP-322 signature verification. Default false. Useful for test vectors\n * where signatures are placeholders, or for preview UIs that re-verify separately.\n */\n skipSignatureVerification?: boolean;\n}\n\nexport type VerifyErrorCode =\n | 'E_UNSUPPORTED_VERSION'\n | 'E_MALFORMED'\n | 'E_BAD_ID'\n | 'E_BAD_SIG'\n | 'E_BAD_ANCHOR'\n | 'E_NO_ANCHOR'\n | 'E_BAD_CONTENT'\n | 'E_STAKE_UNMET'\n | 'E_CALENDAR_UNREACHABLE';\n\nexport interface VerifyOk {\n ok: true;\n envelope: StampEnvelope;\n canonicalMessage: string;\n id: string;\n anchor:\n | { status: 'none' }\n | { status: 'pending' }\n | { status: 'confirmed'; blockHeight: number; blockHash: string; verified: boolean };\n}\n\nexport interface VerifyErr {\n ok: false;\n code: VerifyErrorCode;\n message: string;\n}\n\nexport type VerifyResult = VerifyOk | VerifyErr;\n","// Canonical message + RFC 8785 JSON canonicalization for OC Stamp.\n//\n// Two distinct canonicalizers live here:\n//\n// 1. canonicalMessage(input) — the exact byte sequence the signer signs via\n// BIP-322. LF-separated, no trailing LF after signed_at. SPEC §3.1.\n//\n// 2. canonicalizeEnvelope(env) — RFC 8785 JSON canonicalization of the wire\n// envelope, used for stable serialization / equality checks. SPEC §5.\n//\n// Only (1) participates in the id derivation. (2) is for test-vector byte\n// equality and any future operation that needs deterministic envelope bytes.\n\nimport { sha256 } from '@noble/hashes/sha256';\n\nimport type { CanonicalMessageInput, StampEnvelope } from './types.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonical message (SPEC §3.1)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function canonicalMessage(input: CanonicalMessageInput): string {\n // The domain separator `oc-stamp:v1` commits the whole signing ceremony to\n // this spec version. Future incompatible changes MUST bump the version\n // number here so a v1 signature can never be replayed against a v2 domain.\n return [\n 'oc-stamp:v1',\n `address: ${input.address}`,\n `content_hash: ${input.content_hash}`,\n `content_length: ${input.content_length}`,\n `content_mime: ${input.content_mime}`,\n `signed_at: ${input.signed_at}`,\n ].join('\\n');\n}\n\nexport function canonicalMessageBytes(input: CanonicalMessageInput): Uint8Array {\n return new TextEncoder().encode(canonicalMessage(input));\n}\n\n/**\n * Compute the envelope id as lowercase hex of sha256(canonical_message_bytes).\n * Pure function — the single source of truth for identity derivation.\n */\nexport function computeEnvelopeId(input: CanonicalMessageInput): string {\n const bytes = canonicalMessageBytes(input);\n return hexEncode(sha256(bytes));\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Envelope canonicalization (SPEC §5; RFC 8785)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nexport function canonicalizeEnvelope(env: StampEnvelope): string {\n return canonicalize(env as unknown as JsonValue);\n}\n\nexport function canonicalEnvelopeBytes(env: StampEnvelope): Uint8Array {\n return new TextEncoder().encode(canonicalize(env as unknown as JsonValue) + '\\n');\n}\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value);\n}\n\nfunction encode(v: JsonValue): string {\n if (v === null) return 'null';\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (typeof v === 'number') return encodeNumber(v);\n if (typeof v === 'string') return encodeString(v);\n if (Array.isArray(v)) {\n const parts: string[] = [];\n for (const item of v) parts.push(encode(item));\n return '[' + parts.join(',') + ']';\n }\n if (typeof v === 'object') {\n const keys = Object.keys(v).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const inner = v[k];\n if (inner === undefined) continue;\n parts.push(encodeString(k) + ':' + encode(inner as JsonValue));\n }\n return '{' + parts.join(',') + '}';\n }\n throw new Error('cannot canonicalize value of type ' + typeof v);\n}\n\nfunction encodeNumber(n: number): string {\n if (!Number.isFinite(n)) throw new Error('non-finite number not JSON-canonicalizable');\n if (Object.is(n, -0)) return '0';\n if (Number.isInteger(n) && Math.abs(n) < 1e21) {\n return String(n);\n }\n return JSON.stringify(n);\n}\n\nconst ESCAPE_TABLE: Record<string, string> = {\n '\\\\': '\\\\\\\\',\n '\"': '\\\\\"',\n '\\b': '\\\\b',\n '\\f': '\\\\f',\n '\\n': '\\\\n',\n '\\r': '\\\\r',\n '\\t': '\\\\t',\n};\n\nfunction encodeString(s: string): string {\n let out = '\"';\n for (let i = 0; i < s.length; i++) {\n const ch = s[i]!;\n const code = ch.charCodeAt(0);\n if (ESCAPE_TABLE[ch]) {\n out += ESCAPE_TABLE[ch];\n } else if (code < 0x20) {\n out += '\\\\u' + code.toString(16).padStart(4, '0');\n } else {\n out += ch;\n }\n }\n out += '\"';\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Small utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst HEX_TABLE = '0123456789abcdef';\n\nexport function hexEncode(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i]!;\n out += HEX_TABLE[(b >> 4) & 0x0f]! + HEX_TABLE[b & 0x0f]!;\n }\n return out;\n}\n\nexport function sha256Hex(bytes: Uint8Array): string {\n return hexEncode(sha256(bytes));\n}\n","// stamp() and verify() — reference implementation of OC Stamp v1. SPEC §4, §8.\n\nimport { sha256 } from '@noble/hashes/sha256';\n\nimport {\n canonicalMessage,\n canonicalMessageBytes,\n computeEnvelopeId,\n hexEncode,\n} from './canonical.js';\nimport {\n ENVELOPE_VERSION,\n type StampEnvelope,\n type StampInput,\n type VerifyErrorCode,\n type VerifyInput,\n type VerifyOk,\n type VerifyResult,\n} from './types.js';\n\nexport class StampError extends Error {\n code: VerifyErrorCode;\n constructor(code: VerifyErrorCode, message: string) {\n super(message);\n this.code = code;\n this.name = 'StampError';\n }\n}\n\n/**\n * Build and sign a stamp envelope. Returns the envelope with sig populated but\n * ots left null — OTS calendar submission is a separate step (see\n * @orangecheck/stamp-ots).\n */\nexport async function stamp(input: StampInput): Promise<StampEnvelope> {\n // Resolve content hash + length. Two modes: raw bytes (we hash) or\n // pre-computed {hash, length} (we trust the caller).\n let contentHash: string;\n let contentLength: number;\n if (input.content instanceof Uint8Array) {\n contentHash = 'sha256:' + hexEncode(sha256(input.content));\n contentLength = input.content.byteLength;\n } else {\n // Pre-computed. Caller must have used SHA-256 and declared length correctly.\n // Guard against the easy typos.\n const h = input.content.hash;\n if (!h.startsWith('sha256:') || h.length !== 'sha256:'.length + 64) {\n throw new StampError(\n 'E_MALFORMED',\n 'content.hash must be \"sha256:\" + 64 lowercase hex chars'\n );\n }\n contentHash = h;\n contentLength = input.content.length;\n if (!Number.isInteger(contentLength) || contentLength < 0) {\n throw new StampError('E_MALFORMED', 'content.length must be a non-negative integer');\n }\n }\n\n const signedAt = isoUtcSeconds(input.signedAt ?? new Date());\n\n const canon = {\n address: input.signer.address,\n content_hash: contentHash,\n content_length: contentLength,\n content_mime: input.mime,\n signed_at: signedAt,\n };\n\n const id = computeEnvelopeId(canon);\n\n // BIP-322 signs the ASCII hex representation of id. Hex is chosen (not\n // raw bytes) so wallets that render the signed message to the user show\n // something legible. The id itself commits to every field in the canonical\n // message so this is a transitive commitment to the full signing ceremony.\n const sigValue = await input.signer.signMessage(id);\n\n const envelope: StampEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'stamp',\n id,\n content: {\n hash: contentHash,\n length: contentLength,\n mime: input.mime,\n ref: input.ref ?? null,\n },\n signer: {\n address: input.signer.address,\n alg: 'bip322',\n },\n signed_at: signedAt,\n stake: input.stake ?? null,\n ots: null,\n sig: {\n alg: 'bip322',\n pubkey: input.signer.address,\n value: sigValue,\n },\n };\n\n return envelope;\n}\n\n/**\n * Verify a stamp envelope. See SPEC §8 for the full algorithm. Returns a\n * structured result; callers can match on `result.ok` and drill into\n * `result.code` for the specific failure.\n */\nexport async function verify(input: VerifyInput): Promise<VerifyResult> {\n const env = input.envelope;\n\n // 1. Version check.\n if (env.v !== ENVELOPE_VERSION) {\n return err('E_UNSUPPORTED_VERSION', `envelope version ${env.v} not supported by this implementation`);\n }\n\n // 2. Shape check.\n const shape = checkShape(env);\n if (shape) return shape;\n\n // 3. Canonical message reconstruction + id check.\n const canon = {\n address: env.signer.address,\n content_hash: env.content.hash,\n content_length: env.content.length,\n content_mime: env.content.mime,\n signed_at: env.signed_at,\n };\n const reconstructedMessage = canonicalMessage(canon);\n const reconstructedId = hexEncode(sha256(canonicalMessageBytes(canon)));\n if (reconstructedId !== env.id) {\n return err('E_BAD_ID', `reconstructed id (${reconstructedId}) does not match envelope.id (${env.id})`);\n }\n\n // 4. Signature verify.\n if (!input.skipSignatureVerification) {\n if (!input.verifyBip322) {\n return err('E_BAD_SIG', 'no BIP-322 verifier supplied');\n }\n const ok = await input.verifyBip322(env.id, env.sig.value, env.signer.address);\n if (!ok) return err('E_BAD_SIG', 'BIP-322 signature did not verify');\n }\n\n // 5. Anchor verify.\n let anchor: VerifyOk['anchor'];\n if (env.ots === null) {\n anchor = { status: 'none' };\n } else if (env.ots.status === 'pending') {\n anchor = { status: 'pending' };\n } else {\n // confirmed\n const h = env.ots.block_height;\n const hash = env.ots.block_hash;\n if (h === null || hash === null) {\n return err('E_BAD_ANCHOR', 'confirmed OTS proof missing block_height or block_hash');\n }\n let verified = false;\n if (input.verifyOtsAnchor) {\n try {\n verified = await input.verifyOtsAnchor(env.ots.proof, h, hash);\n } catch (e) {\n return err('E_BAD_ANCHOR', `anchor verifier threw: ${(e as Error).message}`);\n }\n if (!verified) return err('E_BAD_ANCHOR', 'OTS proof did not chain to declared Bitcoin block header');\n }\n // If no anchor verifier was supplied, we accept on shape. The result\n // reports verified: false so callers can decide what to do.\n anchor = { status: 'confirmed', blockHeight: h, blockHash: hash, verified };\n }\n\n // 6. Content bytes check (if supplied).\n if (input.content) {\n const actualHash = 'sha256:' + hexEncode(sha256(input.content));\n if (actualHash !== env.content.hash) {\n return err('E_BAD_CONTENT', `content hash (${actualHash}) does not match envelope.content.hash (${env.content.hash})`);\n }\n }\n\n return {\n ok: true,\n envelope: env,\n canonicalMessage: reconstructedMessage,\n id: env.id,\n anchor,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction checkShape(env: StampEnvelope): VerifyResult | null {\n if (env.kind !== 'stamp') return err('E_MALFORMED', 'envelope.kind must be \"stamp\"');\n if (!env.id || typeof env.id !== 'string' || !/^[0-9a-f]{64}$/.test(env.id)) {\n return err('E_MALFORMED', 'envelope.id must be 64 lowercase hex chars');\n }\n if (!env.content || typeof env.content.hash !== 'string' || !env.content.hash.startsWith('sha256:')) {\n return err('E_MALFORMED', 'envelope.content.hash must start with \"sha256:\"');\n }\n if (!Number.isInteger(env.content.length) || env.content.length < 0) {\n return err('E_MALFORMED', 'envelope.content.length must be a non-negative integer');\n }\n if (typeof env.content.mime !== 'string' || env.content.mime.length === 0) {\n return err('E_MALFORMED', 'envelope.content.mime must be a non-empty string');\n }\n if (!env.signer || typeof env.signer.address !== 'string' || env.signer.alg !== 'bip322') {\n return err('E_MALFORMED', 'envelope.signer invalid');\n }\n if (env.signer.address !== env.sig.pubkey) {\n return err('E_MALFORMED', 'envelope.signer.address must equal envelope.sig.pubkey');\n }\n if (typeof env.signed_at !== 'string' || !/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$/.test(env.signed_at)) {\n return err('E_MALFORMED', 'envelope.signed_at must be ISO 8601 UTC ending in Z');\n }\n if (!env.sig || env.sig.alg !== 'bip322' || typeof env.sig.value !== 'string') {\n return err('E_MALFORMED', 'envelope.sig invalid');\n }\n return null;\n}\n\nfunction err(code: VerifyErrorCode, message: string): VerifyResult {\n return { ok: false, code, message };\n}\n\nfunction isoUtcSeconds(d: Date): string {\n // Drop milliseconds; SPEC allows ms-precision but we default to seconds\n // for shorter wallet-signing prompts. Callers needing millisecond\n // precision can pass a pre-formatted ISO string via a future field.\n return d.toISOString().replace(/\\.\\d+Z$/, 'Z');\n}\n"]}
{"version":3,"sources":["../src/types.ts","../src/canonical.ts","../src/stamp.ts"],"names":["sha256"],"mappings":"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","file":"index.js","sourcesContent":["// Wire types for OC Stamp v1 envelopes. See SPEC.md §4.\n\nexport const ENVELOPE_VERSION = 1 as const;\n\nexport type EnvelopeKind = 'stamp';\n\nexport interface StampContent {\n /** \"sha256:<64-hex>\" */\n hash: string;\n length: number;\n /** RFC 6838 media type. \"application/octet-stream\" if unknown. */\n mime: string;\n /** Optional pointer — not cryptographic. */\n ref: string | null;\n}\n\nexport interface StampSigner {\n address: string;\n alg: 'bip322';\n}\n\nexport interface StampStake {\n /** SHA-256 hex of an OrangeCheck canonical message. */\n attestation_id: string;\n sats_bonded: number;\n days_unspent: number;\n}\n\nexport type OtsStatus = 'pending' | 'confirmed';\n\nexport interface StampOts {\n status: OtsStatus;\n /** Base64-encoded OpenTimestamps proof. */\n proof: string;\n calendars: string[];\n block_height: number | null;\n block_hash: string | null;\n /** ISO 8601 UTC of the upgrade; null while pending. */\n upgraded_at: string | null;\n}\n\nexport interface StampSignature {\n alg: 'bip322';\n pubkey: string; // MUST equal signer.address\n value: string; // base64 BIP-322 signature over hex(id)\n}\n\nexport interface StampEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: EnvelopeKind;\n id: string; // 64-hex of sha256(canonical_message)\n content: StampContent;\n signer: StampSigner;\n signed_at: string; // ISO 8601 UTC\n stake: StampStake | null;\n ots: StampOts | null;\n sig: StampSignature;\n}\n\n// Inputs to stamp() / verify().\n\nexport interface CanonicalMessageInput {\n address: string;\n /** \"sha256:<64-hex>\" */\n content_hash: string;\n content_length: number;\n /** \"application/octet-stream\" if unknown. */\n content_mime: string;\n /** ISO 8601 UTC. */\n signed_at: string;\n}\n\nexport interface StampInput {\n /** The bytes being stamped, or their pre-computed hash. Exactly one is required. */\n content: Uint8Array | { hash: string; length: number };\n mime: string;\n ref?: string | null;\n signer: {\n address: string;\n /**\n * Returns a BIP-322 base64 signature of the given message bytes as UTF-8.\n * The caller supplies the wallet adapter. Called once per stamp() invocation.\n */\n signMessage: (msg: string) => Promise<string>;\n };\n stake?: StampStake | null;\n /** Defaults to now. */\n signedAt?: Date;\n}\n\nexport interface VerifyInput {\n envelope: StampEnvelope;\n /**\n * Optional: the content bytes. If present, verify() additionally checks that\n * sha256(content) matches envelope.content.hash.\n */\n content?: Uint8Array;\n /**\n * Verifier for BIP-322. Plugged in by the caller because signature verification\n * libraries differ across Node, browser, and CLI environments.\n */\n verifyBip322?: (msg: string, signatureB64: string, address: string) => Promise<boolean>;\n /**\n * Optional hook for OTS anchor verification. If present and ots.status === \"confirmed\",\n * the caller-supplied function walks the proof and returns true iff it chains to a\n * real Bitcoin block header at the declared height.\n *\n * The default behavior (anchor hook omitted) is to ACCEPT a confirmed envelope on\n * shape alone — useful for preview UIs. Callers that attach legal or economic\n * weight MUST supply this hook.\n */\n verifyOtsAnchor?: (proofB64: string, blockHeight: number, blockHash: string) => Promise<boolean>;\n /**\n * Skip the BIP-322 signature verification. Default false. Useful for test vectors\n * where signatures are placeholders, or for preview UIs that re-verify separately.\n */\n skipSignatureVerification?: boolean;\n}\n\nexport type VerifyErrorCode =\n | 'E_UNSUPPORTED_VERSION'\n | 'E_MALFORMED'\n | 'E_BAD_ID'\n | 'E_BAD_SIG'\n | 'E_BAD_ANCHOR'\n | 'E_NO_ANCHOR'\n | 'E_BAD_CONTENT'\n | 'E_STAKE_UNMET'\n | 'E_CALENDAR_UNREACHABLE';\n\nexport interface VerifyOk {\n ok: true;\n envelope: StampEnvelope;\n canonicalMessage: string;\n id: string;\n anchor:\n | { status: 'none' }\n | { status: 'pending' }\n | { status: 'confirmed'; blockHeight: number; blockHash: string; verified: boolean };\n}\n\nexport interface VerifyErr {\n ok: false;\n code: VerifyErrorCode;\n message: string;\n}\n\nexport type VerifyResult = VerifyOk | VerifyErr;\n","// Canonical message + RFC 8785 JSON canonicalization for OC Stamp.\n//\n// Two distinct canonicalizers live here:\n//\n// 1. canonicalMessage(input) — the exact byte sequence the signer signs via\n// BIP-322. LF-separated, no trailing LF after signed_at. SPEC §3.1.\n//\n// 2. canonicalizeEnvelope(env) — RFC 8785 JSON canonicalization of the wire\n// envelope, used for stable serialization / equality checks. SPEC §5.\n//\n// Only (1) participates in the id derivation. (2) is for test-vector byte\n// equality and any future operation that needs deterministic envelope bytes.\n\nimport { sha256 } from '@noble/hashes/sha256';\n\nimport type { CanonicalMessageInput, StampEnvelope } from './types.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonical message (SPEC §3.1)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function canonicalMessage(input: CanonicalMessageInput): string {\n // The domain separator `oc-stamp:v1` commits the whole signing ceremony to\n // this spec version. Future incompatible changes MUST bump the version\n // number here so a v1 signature can never be replayed against a v2 domain.\n return [\n 'oc-stamp:v1',\n `address: ${input.address}`,\n `content_hash: ${input.content_hash}`,\n `content_length: ${input.content_length}`,\n `content_mime: ${input.content_mime}`,\n `signed_at: ${input.signed_at}`,\n ].join('\\n');\n}\n\n/**\n * Check whether a CanonicalMessageInput is well-formed per the spec's field\n * rules (§3, §4). Returns `{ok: true}` if every field is valid, or\n * `{ok: false, reason}` pointing at the first violation.\n *\n * Called by `stamp()` before producing the canonical bytes. Exposed for\n * UIs that want to preview validity without throwing.\n */\nexport function validateCanonicalInput(input: CanonicalMessageInput):\n | { ok: true }\n | { ok: false, reason: string } {\n // No control chars or whitespace breaks in fields — any of those would\n // invalidate the 6-line structure and produce an id nobody else can\n // reconstruct. The whole canonical message is ASCII-safe by design.\n if (!input.address || /[\\s\\x00-\\x1f]/.test(input.address)) {\n return { ok: false, reason: 'address must be non-empty with no whitespace or control chars' };\n }\n if (!/^sha256:[0-9a-f]{64}$/.test(input.content_hash)) {\n return { ok: false, reason: 'content_hash must match /^sha256:[0-9a-f]{64}$/' };\n }\n if (!Number.isInteger(input.content_length) || input.content_length < 0) {\n return { ok: false, reason: 'content_length must be a non-negative integer' };\n }\n // MIME per RFC 6838: \"type/subtype[+suffix][;params]\". We accept anything\n // that's non-empty and control-char-free; fine-grained parsing is out of\n // scope and would reject legitimate media types.\n if (!input.content_mime || /[\\x00-\\x1f]/.test(input.content_mime)) {\n return { ok: false, reason: 'content_mime must be non-empty with no control chars' };\n }\n if (!/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$/.test(input.signed_at)) {\n return { ok: false, reason: 'signed_at must be ISO 8601 UTC ending in Z' };\n }\n return { ok: true };\n}\n\nexport function canonicalMessageBytes(input: CanonicalMessageInput): Uint8Array {\n return new TextEncoder().encode(canonicalMessage(input));\n}\n\n/**\n * Compute the envelope id as lowercase hex of sha256(canonical_message_bytes).\n * Pure function — the single source of truth for identity derivation.\n */\nexport function computeEnvelopeId(input: CanonicalMessageInput): string {\n const bytes = canonicalMessageBytes(input);\n return hexEncode(sha256(bytes));\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Envelope canonicalization (SPEC §5; RFC 8785)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nexport function canonicalizeEnvelope(env: StampEnvelope): string {\n return canonicalize(env as unknown as JsonValue);\n}\n\nexport function canonicalEnvelopeBytes(env: StampEnvelope): Uint8Array {\n return new TextEncoder().encode(canonicalize(env as unknown as JsonValue) + '\\n');\n}\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value);\n}\n\nfunction encode(v: JsonValue): string {\n if (v === null) return 'null';\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (typeof v === 'number') return encodeNumber(v);\n if (typeof v === 'string') return encodeString(v);\n if (Array.isArray(v)) {\n const parts: string[] = [];\n for (const item of v) parts.push(encode(item));\n return '[' + parts.join(',') + ']';\n }\n if (typeof v === 'object') {\n const keys = Object.keys(v).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const inner = v[k];\n if (inner === undefined) continue;\n parts.push(encodeString(k) + ':' + encode(inner as JsonValue));\n }\n return '{' + parts.join(',') + '}';\n }\n throw new Error('cannot canonicalize value of type ' + typeof v);\n}\n\nfunction encodeNumber(n: number): string {\n if (!Number.isFinite(n)) throw new Error('non-finite number not JSON-canonicalizable');\n if (Object.is(n, -0)) return '0';\n if (Number.isInteger(n) && Math.abs(n) < 1e21) {\n return String(n);\n }\n return JSON.stringify(n);\n}\n\nconst ESCAPE_TABLE: Record<string, string> = {\n '\\\\': '\\\\\\\\',\n '\"': '\\\\\"',\n '\\b': '\\\\b',\n '\\f': '\\\\f',\n '\\n': '\\\\n',\n '\\r': '\\\\r',\n '\\t': '\\\\t',\n};\n\nfunction encodeString(s: string): string {\n let out = '\"';\n for (let i = 0; i < s.length; i++) {\n const ch = s[i]!;\n const code = ch.charCodeAt(0);\n if (ESCAPE_TABLE[ch]) {\n out += ESCAPE_TABLE[ch];\n } else if (code < 0x20) {\n out += '\\\\u' + code.toString(16).padStart(4, '0');\n } else {\n out += ch;\n }\n }\n out += '\"';\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Small utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst HEX_TABLE = '0123456789abcdef';\n\nexport function hexEncode(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i]!;\n out += HEX_TABLE[(b >> 4) & 0x0f]! + HEX_TABLE[b & 0x0f]!;\n }\n return out;\n}\n\nexport function sha256Hex(bytes: Uint8Array): string {\n return hexEncode(sha256(bytes));\n}\n","// stamp() and verify() — reference implementation of OC Stamp v1. SPEC §4, §8.\n\nimport { sha256 } from '@noble/hashes/sha256';\n\nimport {\n canonicalMessage,\n canonicalMessageBytes,\n computeEnvelopeId,\n hexEncode,\n validateCanonicalInput,\n} from './canonical.js';\nimport {\n ENVELOPE_VERSION,\n type StampEnvelope,\n type StampInput,\n type VerifyErrorCode,\n type VerifyInput,\n type VerifyOk,\n type VerifyResult,\n} from './types.js';\n\n/**\n * Compute `{hash, length}` for raw bytes. Convenience over the common\n * `sha256(bytes)` pattern that prefixes with `sha256:` and returns lowercase\n * hex. Pass the result to `stamp({ content: hashContent(bytes), mime })` when\n * you don't want stamp() to hash the bytes itself.\n */\nexport function hashContent(bytes: Uint8Array): { hash: string; length: number } {\n return {\n hash: 'sha256:' + hexEncode(sha256(bytes)),\n length: bytes.byteLength,\n };\n}\n\nexport class StampError extends Error {\n code: VerifyErrorCode;\n constructor(code: VerifyErrorCode, message: string) {\n super(message);\n this.code = code;\n this.name = 'StampError';\n }\n}\n\n/**\n * Build and sign a stamp envelope. Returns the envelope with sig populated but\n * ots left null — OTS calendar submission is a separate step (see\n * @orangecheck/stamp-ots).\n */\nexport async function stamp(input: StampInput): Promise<StampEnvelope> {\n // Resolve content hash + length. Two modes: raw bytes (we hash) or\n // pre-computed {hash, length} (we trust the caller).\n let contentHash: string;\n let contentLength: number;\n if (input.content instanceof Uint8Array) {\n contentHash = 'sha256:' + hexEncode(sha256(input.content));\n contentLength = input.content.byteLength;\n } else {\n // Pre-computed. Caller must have used SHA-256 and declared length correctly.\n // Guard against the easy typos.\n const h = input.content.hash;\n if (!h.startsWith('sha256:') || h.length !== 'sha256:'.length + 64) {\n throw new StampError(\n 'E_MALFORMED',\n 'content.hash must be \"sha256:\" + 64 lowercase hex chars'\n );\n }\n contentHash = h;\n contentLength = input.content.length;\n if (!Number.isInteger(contentLength) || contentLength < 0) {\n throw new StampError('E_MALFORMED', 'content.length must be a non-negative integer');\n }\n }\n\n const signedAt = isoUtcSeconds(input.signedAt ?? new Date());\n\n const canon = {\n address: input.signer.address,\n content_hash: contentHash,\n content_length: contentLength,\n content_mime: input.mime,\n signed_at: signedAt,\n };\n\n const v = validateCanonicalInput(canon);\n if (!v.ok) {\n // Validation rejects inputs that would silently break canonical-message\n // reconstructability downstream (whitespace in address, wrong hash\n // prefix, floats in length, etc.). Catching them here prevents a\n // signed envelope that no verifier can accept.\n throw new StampError('E_MALFORMED', v.reason);\n }\n\n const id = computeEnvelopeId(canon);\n\n // BIP-322 signs the ASCII hex representation of id. Hex is chosen (not\n // raw bytes) so wallets that render the signed message to the user show\n // something legible. The id itself commits to every field in the canonical\n // message so this is a transitive commitment to the full signing ceremony.\n const sigValue = await input.signer.signMessage(id);\n\n const envelope: StampEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'stamp',\n id,\n content: {\n hash: contentHash,\n length: contentLength,\n mime: input.mime,\n ref: input.ref ?? null,\n },\n signer: {\n address: input.signer.address,\n alg: 'bip322',\n },\n signed_at: signedAt,\n stake: input.stake ?? null,\n ots: null,\n sig: {\n alg: 'bip322',\n pubkey: input.signer.address,\n value: sigValue,\n },\n };\n\n return envelope;\n}\n\n/**\n * Verify a stamp envelope. See SPEC §8 for the full algorithm. Returns a\n * structured result; callers can match on `result.ok` and drill into\n * `result.code` for the specific failure.\n */\nexport async function verify(input: VerifyInput): Promise<VerifyResult> {\n const env = input.envelope;\n\n // 1. Version check.\n if (env.v !== ENVELOPE_VERSION) {\n return err('E_UNSUPPORTED_VERSION', `envelope version ${env.v} not supported by this implementation`);\n }\n\n // 2. Shape check.\n const shape = checkShape(env);\n if (shape) return shape;\n\n // 3. Canonical message reconstruction + id check.\n const canon = {\n address: env.signer.address,\n content_hash: env.content.hash,\n content_length: env.content.length,\n content_mime: env.content.mime,\n signed_at: env.signed_at,\n };\n const reconstructedMessage = canonicalMessage(canon);\n const reconstructedId = hexEncode(sha256(canonicalMessageBytes(canon)));\n if (reconstructedId !== env.id) {\n return err('E_BAD_ID', `reconstructed id (${reconstructedId}) does not match envelope.id (${env.id})`);\n }\n\n // 4. Signature verify.\n if (!input.skipSignatureVerification) {\n if (!input.verifyBip322) {\n return err('E_BAD_SIG', 'no BIP-322 verifier supplied');\n }\n const ok = await input.verifyBip322(env.id, env.sig.value, env.signer.address);\n if (!ok) return err('E_BAD_SIG', 'BIP-322 signature did not verify');\n }\n\n // 5. Anchor verify.\n let anchor: VerifyOk['anchor'];\n if (env.ots === null) {\n anchor = { status: 'none' };\n } else if (env.ots.status === 'pending') {\n anchor = { status: 'pending' };\n } else {\n // confirmed\n const h = env.ots.block_height;\n const hash = env.ots.block_hash;\n if (h === null || hash === null) {\n return err('E_BAD_ANCHOR', 'confirmed OTS proof missing block_height or block_hash');\n }\n let verified = false;\n if (input.verifyOtsAnchor) {\n try {\n verified = await input.verifyOtsAnchor(env.ots.proof, h, hash);\n } catch (e) {\n return err('E_BAD_ANCHOR', `anchor verifier threw: ${(e as Error).message}`);\n }\n if (!verified) return err('E_BAD_ANCHOR', 'OTS proof did not chain to declared Bitcoin block header');\n }\n // If no anchor verifier was supplied, we accept on shape. The result\n // reports verified: false so callers can decide what to do.\n anchor = { status: 'confirmed', blockHeight: h, blockHash: hash, verified };\n }\n\n // 6. Content bytes check (if supplied).\n if (input.content) {\n const actualHash = 'sha256:' + hexEncode(sha256(input.content));\n if (actualHash !== env.content.hash) {\n return err('E_BAD_CONTENT', `content hash (${actualHash}) does not match envelope.content.hash (${env.content.hash})`);\n }\n }\n\n return {\n ok: true,\n envelope: env,\n canonicalMessage: reconstructedMessage,\n id: env.id,\n anchor,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction checkShape(env: StampEnvelope): VerifyResult | null {\n if (env.kind !== 'stamp') return err('E_MALFORMED', 'envelope.kind must be \"stamp\"');\n if (!env.id || typeof env.id !== 'string' || !/^[0-9a-f]{64}$/.test(env.id)) {\n return err('E_MALFORMED', 'envelope.id must be 64 lowercase hex chars');\n }\n if (!env.content || typeof env.content.hash !== 'string' || !env.content.hash.startsWith('sha256:')) {\n return err('E_MALFORMED', 'envelope.content.hash must start with \"sha256:\"');\n }\n if (!Number.isInteger(env.content.length) || env.content.length < 0) {\n return err('E_MALFORMED', 'envelope.content.length must be a non-negative integer');\n }\n if (typeof env.content.mime !== 'string' || env.content.mime.length === 0) {\n return err('E_MALFORMED', 'envelope.content.mime must be a non-empty string');\n }\n if (!env.signer || typeof env.signer.address !== 'string' || env.signer.alg !== 'bip322') {\n return err('E_MALFORMED', 'envelope.signer invalid');\n }\n if (env.signer.address !== env.sig.pubkey) {\n return err('E_MALFORMED', 'envelope.signer.address must equal envelope.sig.pubkey');\n }\n if (typeof env.signed_at !== 'string' || !/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$/.test(env.signed_at)) {\n return err('E_MALFORMED', 'envelope.signed_at must be ISO 8601 UTC ending in Z');\n }\n if (!env.sig || env.sig.alg !== 'bip322' || typeof env.sig.value !== 'string') {\n return err('E_MALFORMED', 'envelope.sig invalid');\n }\n return null;\n}\n\nfunction err(code: VerifyErrorCode, message: string): VerifyResult {\n return { ok: false, code, message };\n}\n\nfunction isoUtcSeconds(d: Date): string {\n // Drop milliseconds; SPEC allows ms-precision but we default to seconds\n // for shorter wallet-signing prompts. Callers needing millisecond\n // precision can pass a pre-formatted ISO string via a future field.\n return d.toISOString().replace(/\\.\\d+Z$/, 'Z');\n}\n"]}

@@ -15,2 +15,20 @@ import { sha256 } from '@noble/hashes/sha256';

}
function validateCanonicalInput(input) {
if (!input.address || /[\s\x00-\x1f]/.test(input.address)) {
return { ok: false, reason: "address must be non-empty with no whitespace or control chars" };
}
if (!/^sha256:[0-9a-f]{64}$/.test(input.content_hash)) {
return { ok: false, reason: "content_hash must match /^sha256:[0-9a-f]{64}$/" };
}
if (!Number.isInteger(input.content_length) || input.content_length < 0) {
return { ok: false, reason: "content_length must be a non-negative integer" };
}
if (!input.content_mime || /[\x00-\x1f]/.test(input.content_mime)) {
return { ok: false, reason: "content_mime must be non-empty with no control chars" };
}
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/.test(input.signed_at)) {
return { ok: false, reason: "signed_at must be ISO 8601 UTC ending in Z" };
}
return { ok: true };
}
function canonicalMessageBytes(input) {

@@ -99,2 +117,8 @@ return new TextEncoder().encode(canonicalMessage(input));

}
function hashContent(bytes) {
return {
hash: "sha256:" + hexEncode(sha256(bytes)),
length: bytes.byteLength
};
}
var StampError = class extends Error {

@@ -135,2 +159,6 @@ constructor(code, message) {

};
const v = validateCanonicalInput(canon);
if (!v.ok) {
throw new StampError("E_MALFORMED", v.reason);
}
const id = computeEnvelopeId(canon);

@@ -260,4 +288,4 @@ const sigValue = await input.signer.signMessage(id);

export { ENVELOPE_VERSION, StampError, canonicalEnvelopeBytes, canonicalMessage, canonicalMessageBytes, canonicalize, canonicalizeEnvelope, computeEnvelopeId, hexEncode, sha256Hex, stamp, verify };
export { ENVELOPE_VERSION, StampError, canonicalEnvelopeBytes, canonicalMessage, canonicalMessageBytes, canonicalize, canonicalizeEnvelope, computeEnvelopeId, hashContent, hexEncode, sha256Hex, stamp, validateCanonicalInput, verify };
//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/types.ts","../src/canonical.ts","../src/stamp.ts"],"names":["sha256"],"mappings":"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","file":"index.mjs","sourcesContent":["// Wire types for OC Stamp v1 envelopes. See SPEC.md §4.\n\nexport const ENVELOPE_VERSION = 1 as const;\n\nexport type EnvelopeKind = 'stamp';\n\nexport interface StampContent {\n /** \"sha256:<64-hex>\" */\n hash: string;\n length: number;\n /** RFC 6838 media type. \"application/octet-stream\" if unknown. */\n mime: string;\n /** Optional pointer — not cryptographic. */\n ref: string | null;\n}\n\nexport interface StampSigner {\n address: string;\n alg: 'bip322';\n}\n\nexport interface StampStake {\n /** SHA-256 hex of an OrangeCheck canonical message. */\n attestation_id: string;\n sats_bonded: number;\n days_unspent: number;\n}\n\nexport type OtsStatus = 'pending' | 'confirmed';\n\nexport interface StampOts {\n status: OtsStatus;\n /** Base64-encoded OpenTimestamps proof. */\n proof: string;\n calendars: string[];\n block_height: number | null;\n block_hash: string | null;\n /** ISO 8601 UTC of the upgrade; null while pending. */\n upgraded_at: string | null;\n}\n\nexport interface StampSignature {\n alg: 'bip322';\n pubkey: string; // MUST equal signer.address\n value: string; // base64 BIP-322 signature over hex(id)\n}\n\nexport interface StampEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: EnvelopeKind;\n id: string; // 64-hex of sha256(canonical_message)\n content: StampContent;\n signer: StampSigner;\n signed_at: string; // ISO 8601 UTC\n stake: StampStake | null;\n ots: StampOts | null;\n sig: StampSignature;\n}\n\n// Inputs to stamp() / verify().\n\nexport interface CanonicalMessageInput {\n address: string;\n /** \"sha256:<64-hex>\" */\n content_hash: string;\n content_length: number;\n /** \"application/octet-stream\" if unknown. */\n content_mime: string;\n /** ISO 8601 UTC. */\n signed_at: string;\n}\n\nexport interface StampInput {\n /** The bytes being stamped, or their pre-computed hash. Exactly one is required. */\n content: Uint8Array | { hash: string; length: number };\n mime: string;\n ref?: string | null;\n signer: {\n address: string;\n /**\n * Returns a BIP-322 base64 signature of the given message bytes as UTF-8.\n * The caller supplies the wallet adapter. Called once per stamp() invocation.\n */\n signMessage: (msg: string) => Promise<string>;\n };\n stake?: StampStake | null;\n /** Defaults to now. */\n signedAt?: Date;\n}\n\nexport interface VerifyInput {\n envelope: StampEnvelope;\n /**\n * Optional: the content bytes. If present, verify() additionally checks that\n * sha256(content) matches envelope.content.hash.\n */\n content?: Uint8Array;\n /**\n * Verifier for BIP-322. Plugged in by the caller because signature verification\n * libraries differ across Node, browser, and CLI environments.\n */\n verifyBip322?: (msg: string, signatureB64: string, address: string) => Promise<boolean>;\n /**\n * Optional hook for OTS anchor verification. If present and ots.status === \"confirmed\",\n * the caller-supplied function walks the proof and returns true iff it chains to a\n * real Bitcoin block header at the declared height.\n *\n * The default behavior (anchor hook omitted) is to ACCEPT a confirmed envelope on\n * shape alone — useful for preview UIs. Callers that attach legal or economic\n * weight MUST supply this hook.\n */\n verifyOtsAnchor?: (proofB64: string, blockHeight: number, blockHash: string) => Promise<boolean>;\n /**\n * Skip the BIP-322 signature verification. Default false. Useful for test vectors\n * where signatures are placeholders, or for preview UIs that re-verify separately.\n */\n skipSignatureVerification?: boolean;\n}\n\nexport type VerifyErrorCode =\n | 'E_UNSUPPORTED_VERSION'\n | 'E_MALFORMED'\n | 'E_BAD_ID'\n | 'E_BAD_SIG'\n | 'E_BAD_ANCHOR'\n | 'E_NO_ANCHOR'\n | 'E_BAD_CONTENT'\n | 'E_STAKE_UNMET'\n | 'E_CALENDAR_UNREACHABLE';\n\nexport interface VerifyOk {\n ok: true;\n envelope: StampEnvelope;\n canonicalMessage: string;\n id: string;\n anchor:\n | { status: 'none' }\n | { status: 'pending' }\n | { status: 'confirmed'; blockHeight: number; blockHash: string; verified: boolean };\n}\n\nexport interface VerifyErr {\n ok: false;\n code: VerifyErrorCode;\n message: string;\n}\n\nexport type VerifyResult = VerifyOk | VerifyErr;\n","// Canonical message + RFC 8785 JSON canonicalization for OC Stamp.\n//\n// Two distinct canonicalizers live here:\n//\n// 1. canonicalMessage(input) — the exact byte sequence the signer signs via\n// BIP-322. LF-separated, no trailing LF after signed_at. SPEC §3.1.\n//\n// 2. canonicalizeEnvelope(env) — RFC 8785 JSON canonicalization of the wire\n// envelope, used for stable serialization / equality checks. SPEC §5.\n//\n// Only (1) participates in the id derivation. (2) is for test-vector byte\n// equality and any future operation that needs deterministic envelope bytes.\n\nimport { sha256 } from '@noble/hashes/sha256';\n\nimport type { CanonicalMessageInput, StampEnvelope } from './types.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonical message (SPEC §3.1)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function canonicalMessage(input: CanonicalMessageInput): string {\n // The domain separator `oc-stamp:v1` commits the whole signing ceremony to\n // this spec version. Future incompatible changes MUST bump the version\n // number here so a v1 signature can never be replayed against a v2 domain.\n return [\n 'oc-stamp:v1',\n `address: ${input.address}`,\n `content_hash: ${input.content_hash}`,\n `content_length: ${input.content_length}`,\n `content_mime: ${input.content_mime}`,\n `signed_at: ${input.signed_at}`,\n ].join('\\n');\n}\n\nexport function canonicalMessageBytes(input: CanonicalMessageInput): Uint8Array {\n return new TextEncoder().encode(canonicalMessage(input));\n}\n\n/**\n * Compute the envelope id as lowercase hex of sha256(canonical_message_bytes).\n * Pure function — the single source of truth for identity derivation.\n */\nexport function computeEnvelopeId(input: CanonicalMessageInput): string {\n const bytes = canonicalMessageBytes(input);\n return hexEncode(sha256(bytes));\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Envelope canonicalization (SPEC §5; RFC 8785)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nexport function canonicalizeEnvelope(env: StampEnvelope): string {\n return canonicalize(env as unknown as JsonValue);\n}\n\nexport function canonicalEnvelopeBytes(env: StampEnvelope): Uint8Array {\n return new TextEncoder().encode(canonicalize(env as unknown as JsonValue) + '\\n');\n}\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value);\n}\n\nfunction encode(v: JsonValue): string {\n if (v === null) return 'null';\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (typeof v === 'number') return encodeNumber(v);\n if (typeof v === 'string') return encodeString(v);\n if (Array.isArray(v)) {\n const parts: string[] = [];\n for (const item of v) parts.push(encode(item));\n return '[' + parts.join(',') + ']';\n }\n if (typeof v === 'object') {\n const keys = Object.keys(v).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const inner = v[k];\n if (inner === undefined) continue;\n parts.push(encodeString(k) + ':' + encode(inner as JsonValue));\n }\n return '{' + parts.join(',') + '}';\n }\n throw new Error('cannot canonicalize value of type ' + typeof v);\n}\n\nfunction encodeNumber(n: number): string {\n if (!Number.isFinite(n)) throw new Error('non-finite number not JSON-canonicalizable');\n if (Object.is(n, -0)) return '0';\n if (Number.isInteger(n) && Math.abs(n) < 1e21) {\n return String(n);\n }\n return JSON.stringify(n);\n}\n\nconst ESCAPE_TABLE: Record<string, string> = {\n '\\\\': '\\\\\\\\',\n '\"': '\\\\\"',\n '\\b': '\\\\b',\n '\\f': '\\\\f',\n '\\n': '\\\\n',\n '\\r': '\\\\r',\n '\\t': '\\\\t',\n};\n\nfunction encodeString(s: string): string {\n let out = '\"';\n for (let i = 0; i < s.length; i++) {\n const ch = s[i]!;\n const code = ch.charCodeAt(0);\n if (ESCAPE_TABLE[ch]) {\n out += ESCAPE_TABLE[ch];\n } else if (code < 0x20) {\n out += '\\\\u' + code.toString(16).padStart(4, '0');\n } else {\n out += ch;\n }\n }\n out += '\"';\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Small utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst HEX_TABLE = '0123456789abcdef';\n\nexport function hexEncode(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i]!;\n out += HEX_TABLE[(b >> 4) & 0x0f]! + HEX_TABLE[b & 0x0f]!;\n }\n return out;\n}\n\nexport function sha256Hex(bytes: Uint8Array): string {\n return hexEncode(sha256(bytes));\n}\n","// stamp() and verify() — reference implementation of OC Stamp v1. SPEC §4, §8.\n\nimport { sha256 } from '@noble/hashes/sha256';\n\nimport {\n canonicalMessage,\n canonicalMessageBytes,\n computeEnvelopeId,\n hexEncode,\n} from './canonical.js';\nimport {\n ENVELOPE_VERSION,\n type StampEnvelope,\n type StampInput,\n type VerifyErrorCode,\n type VerifyInput,\n type VerifyOk,\n type VerifyResult,\n} from './types.js';\n\nexport class StampError extends Error {\n code: VerifyErrorCode;\n constructor(code: VerifyErrorCode, message: string) {\n super(message);\n this.code = code;\n this.name = 'StampError';\n }\n}\n\n/**\n * Build and sign a stamp envelope. Returns the envelope with sig populated but\n * ots left null — OTS calendar submission is a separate step (see\n * @orangecheck/stamp-ots).\n */\nexport async function stamp(input: StampInput): Promise<StampEnvelope> {\n // Resolve content hash + length. Two modes: raw bytes (we hash) or\n // pre-computed {hash, length} (we trust the caller).\n let contentHash: string;\n let contentLength: number;\n if (input.content instanceof Uint8Array) {\n contentHash = 'sha256:' + hexEncode(sha256(input.content));\n contentLength = input.content.byteLength;\n } else {\n // Pre-computed. Caller must have used SHA-256 and declared length correctly.\n // Guard against the easy typos.\n const h = input.content.hash;\n if (!h.startsWith('sha256:') || h.length !== 'sha256:'.length + 64) {\n throw new StampError(\n 'E_MALFORMED',\n 'content.hash must be \"sha256:\" + 64 lowercase hex chars'\n );\n }\n contentHash = h;\n contentLength = input.content.length;\n if (!Number.isInteger(contentLength) || contentLength < 0) {\n throw new StampError('E_MALFORMED', 'content.length must be a non-negative integer');\n }\n }\n\n const signedAt = isoUtcSeconds(input.signedAt ?? new Date());\n\n const canon = {\n address: input.signer.address,\n content_hash: contentHash,\n content_length: contentLength,\n content_mime: input.mime,\n signed_at: signedAt,\n };\n\n const id = computeEnvelopeId(canon);\n\n // BIP-322 signs the ASCII hex representation of id. Hex is chosen (not\n // raw bytes) so wallets that render the signed message to the user show\n // something legible. The id itself commits to every field in the canonical\n // message so this is a transitive commitment to the full signing ceremony.\n const sigValue = await input.signer.signMessage(id);\n\n const envelope: StampEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'stamp',\n id,\n content: {\n hash: contentHash,\n length: contentLength,\n mime: input.mime,\n ref: input.ref ?? null,\n },\n signer: {\n address: input.signer.address,\n alg: 'bip322',\n },\n signed_at: signedAt,\n stake: input.stake ?? null,\n ots: null,\n sig: {\n alg: 'bip322',\n pubkey: input.signer.address,\n value: sigValue,\n },\n };\n\n return envelope;\n}\n\n/**\n * Verify a stamp envelope. See SPEC §8 for the full algorithm. Returns a\n * structured result; callers can match on `result.ok` and drill into\n * `result.code` for the specific failure.\n */\nexport async function verify(input: VerifyInput): Promise<VerifyResult> {\n const env = input.envelope;\n\n // 1. Version check.\n if (env.v !== ENVELOPE_VERSION) {\n return err('E_UNSUPPORTED_VERSION', `envelope version ${env.v} not supported by this implementation`);\n }\n\n // 2. Shape check.\n const shape = checkShape(env);\n if (shape) return shape;\n\n // 3. Canonical message reconstruction + id check.\n const canon = {\n address: env.signer.address,\n content_hash: env.content.hash,\n content_length: env.content.length,\n content_mime: env.content.mime,\n signed_at: env.signed_at,\n };\n const reconstructedMessage = canonicalMessage(canon);\n const reconstructedId = hexEncode(sha256(canonicalMessageBytes(canon)));\n if (reconstructedId !== env.id) {\n return err('E_BAD_ID', `reconstructed id (${reconstructedId}) does not match envelope.id (${env.id})`);\n }\n\n // 4. Signature verify.\n if (!input.skipSignatureVerification) {\n if (!input.verifyBip322) {\n return err('E_BAD_SIG', 'no BIP-322 verifier supplied');\n }\n const ok = await input.verifyBip322(env.id, env.sig.value, env.signer.address);\n if (!ok) return err('E_BAD_SIG', 'BIP-322 signature did not verify');\n }\n\n // 5. Anchor verify.\n let anchor: VerifyOk['anchor'];\n if (env.ots === null) {\n anchor = { status: 'none' };\n } else if (env.ots.status === 'pending') {\n anchor = { status: 'pending' };\n } else {\n // confirmed\n const h = env.ots.block_height;\n const hash = env.ots.block_hash;\n if (h === null || hash === null) {\n return err('E_BAD_ANCHOR', 'confirmed OTS proof missing block_height or block_hash');\n }\n let verified = false;\n if (input.verifyOtsAnchor) {\n try {\n verified = await input.verifyOtsAnchor(env.ots.proof, h, hash);\n } catch (e) {\n return err('E_BAD_ANCHOR', `anchor verifier threw: ${(e as Error).message}`);\n }\n if (!verified) return err('E_BAD_ANCHOR', 'OTS proof did not chain to declared Bitcoin block header');\n }\n // If no anchor verifier was supplied, we accept on shape. The result\n // reports verified: false so callers can decide what to do.\n anchor = { status: 'confirmed', blockHeight: h, blockHash: hash, verified };\n }\n\n // 6. Content bytes check (if supplied).\n if (input.content) {\n const actualHash = 'sha256:' + hexEncode(sha256(input.content));\n if (actualHash !== env.content.hash) {\n return err('E_BAD_CONTENT', `content hash (${actualHash}) does not match envelope.content.hash (${env.content.hash})`);\n }\n }\n\n return {\n ok: true,\n envelope: env,\n canonicalMessage: reconstructedMessage,\n id: env.id,\n anchor,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction checkShape(env: StampEnvelope): VerifyResult | null {\n if (env.kind !== 'stamp') return err('E_MALFORMED', 'envelope.kind must be \"stamp\"');\n if (!env.id || typeof env.id !== 'string' || !/^[0-9a-f]{64}$/.test(env.id)) {\n return err('E_MALFORMED', 'envelope.id must be 64 lowercase hex chars');\n }\n if (!env.content || typeof env.content.hash !== 'string' || !env.content.hash.startsWith('sha256:')) {\n return err('E_MALFORMED', 'envelope.content.hash must start with \"sha256:\"');\n }\n if (!Number.isInteger(env.content.length) || env.content.length < 0) {\n return err('E_MALFORMED', 'envelope.content.length must be a non-negative integer');\n }\n if (typeof env.content.mime !== 'string' || env.content.mime.length === 0) {\n return err('E_MALFORMED', 'envelope.content.mime must be a non-empty string');\n }\n if (!env.signer || typeof env.signer.address !== 'string' || env.signer.alg !== 'bip322') {\n return err('E_MALFORMED', 'envelope.signer invalid');\n }\n if (env.signer.address !== env.sig.pubkey) {\n return err('E_MALFORMED', 'envelope.signer.address must equal envelope.sig.pubkey');\n }\n if (typeof env.signed_at !== 'string' || !/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$/.test(env.signed_at)) {\n return err('E_MALFORMED', 'envelope.signed_at must be ISO 8601 UTC ending in Z');\n }\n if (!env.sig || env.sig.alg !== 'bip322' || typeof env.sig.value !== 'string') {\n return err('E_MALFORMED', 'envelope.sig invalid');\n }\n return null;\n}\n\nfunction err(code: VerifyErrorCode, message: string): VerifyResult {\n return { ok: false, code, message };\n}\n\nfunction isoUtcSeconds(d: Date): string {\n // Drop milliseconds; SPEC allows ms-precision but we default to seconds\n // for shorter wallet-signing prompts. Callers needing millisecond\n // precision can pass a pre-formatted ISO string via a future field.\n return d.toISOString().replace(/\\.\\d+Z$/, 'Z');\n}\n"]}
{"version":3,"sources":["../src/types.ts","../src/canonical.ts","../src/stamp.ts"],"names":["sha256"],"mappings":"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","file":"index.mjs","sourcesContent":["// Wire types for OC Stamp v1 envelopes. See SPEC.md §4.\n\nexport const ENVELOPE_VERSION = 1 as const;\n\nexport type EnvelopeKind = 'stamp';\n\nexport interface StampContent {\n /** \"sha256:<64-hex>\" */\n hash: string;\n length: number;\n /** RFC 6838 media type. \"application/octet-stream\" if unknown. */\n mime: string;\n /** Optional pointer — not cryptographic. */\n ref: string | null;\n}\n\nexport interface StampSigner {\n address: string;\n alg: 'bip322';\n}\n\nexport interface StampStake {\n /** SHA-256 hex of an OrangeCheck canonical message. */\n attestation_id: string;\n sats_bonded: number;\n days_unspent: number;\n}\n\nexport type OtsStatus = 'pending' | 'confirmed';\n\nexport interface StampOts {\n status: OtsStatus;\n /** Base64-encoded OpenTimestamps proof. */\n proof: string;\n calendars: string[];\n block_height: number | null;\n block_hash: string | null;\n /** ISO 8601 UTC of the upgrade; null while pending. */\n upgraded_at: string | null;\n}\n\nexport interface StampSignature {\n alg: 'bip322';\n pubkey: string; // MUST equal signer.address\n value: string; // base64 BIP-322 signature over hex(id)\n}\n\nexport interface StampEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: EnvelopeKind;\n id: string; // 64-hex of sha256(canonical_message)\n content: StampContent;\n signer: StampSigner;\n signed_at: string; // ISO 8601 UTC\n stake: StampStake | null;\n ots: StampOts | null;\n sig: StampSignature;\n}\n\n// Inputs to stamp() / verify().\n\nexport interface CanonicalMessageInput {\n address: string;\n /** \"sha256:<64-hex>\" */\n content_hash: string;\n content_length: number;\n /** \"application/octet-stream\" if unknown. */\n content_mime: string;\n /** ISO 8601 UTC. */\n signed_at: string;\n}\n\nexport interface StampInput {\n /** The bytes being stamped, or their pre-computed hash. Exactly one is required. */\n content: Uint8Array | { hash: string; length: number };\n mime: string;\n ref?: string | null;\n signer: {\n address: string;\n /**\n * Returns a BIP-322 base64 signature of the given message bytes as UTF-8.\n * The caller supplies the wallet adapter. Called once per stamp() invocation.\n */\n signMessage: (msg: string) => Promise<string>;\n };\n stake?: StampStake | null;\n /** Defaults to now. */\n signedAt?: Date;\n}\n\nexport interface VerifyInput {\n envelope: StampEnvelope;\n /**\n * Optional: the content bytes. If present, verify() additionally checks that\n * sha256(content) matches envelope.content.hash.\n */\n content?: Uint8Array;\n /**\n * Verifier for BIP-322. Plugged in by the caller because signature verification\n * libraries differ across Node, browser, and CLI environments.\n */\n verifyBip322?: (msg: string, signatureB64: string, address: string) => Promise<boolean>;\n /**\n * Optional hook for OTS anchor verification. If present and ots.status === \"confirmed\",\n * the caller-supplied function walks the proof and returns true iff it chains to a\n * real Bitcoin block header at the declared height.\n *\n * The default behavior (anchor hook omitted) is to ACCEPT a confirmed envelope on\n * shape alone — useful for preview UIs. Callers that attach legal or economic\n * weight MUST supply this hook.\n */\n verifyOtsAnchor?: (proofB64: string, blockHeight: number, blockHash: string) => Promise<boolean>;\n /**\n * Skip the BIP-322 signature verification. Default false. Useful for test vectors\n * where signatures are placeholders, or for preview UIs that re-verify separately.\n */\n skipSignatureVerification?: boolean;\n}\n\nexport type VerifyErrorCode =\n | 'E_UNSUPPORTED_VERSION'\n | 'E_MALFORMED'\n | 'E_BAD_ID'\n | 'E_BAD_SIG'\n | 'E_BAD_ANCHOR'\n | 'E_NO_ANCHOR'\n | 'E_BAD_CONTENT'\n | 'E_STAKE_UNMET'\n | 'E_CALENDAR_UNREACHABLE';\n\nexport interface VerifyOk {\n ok: true;\n envelope: StampEnvelope;\n canonicalMessage: string;\n id: string;\n anchor:\n | { status: 'none' }\n | { status: 'pending' }\n | { status: 'confirmed'; blockHeight: number; blockHash: string; verified: boolean };\n}\n\nexport interface VerifyErr {\n ok: false;\n code: VerifyErrorCode;\n message: string;\n}\n\nexport type VerifyResult = VerifyOk | VerifyErr;\n","// Canonical message + RFC 8785 JSON canonicalization for OC Stamp.\n//\n// Two distinct canonicalizers live here:\n//\n// 1. canonicalMessage(input) — the exact byte sequence the signer signs via\n// BIP-322. LF-separated, no trailing LF after signed_at. SPEC §3.1.\n//\n// 2. canonicalizeEnvelope(env) — RFC 8785 JSON canonicalization of the wire\n// envelope, used for stable serialization / equality checks. SPEC §5.\n//\n// Only (1) participates in the id derivation. (2) is for test-vector byte\n// equality and any future operation that needs deterministic envelope bytes.\n\nimport { sha256 } from '@noble/hashes/sha256';\n\nimport type { CanonicalMessageInput, StampEnvelope } from './types.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonical message (SPEC §3.1)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function canonicalMessage(input: CanonicalMessageInput): string {\n // The domain separator `oc-stamp:v1` commits the whole signing ceremony to\n // this spec version. Future incompatible changes MUST bump the version\n // number here so a v1 signature can never be replayed against a v2 domain.\n return [\n 'oc-stamp:v1',\n `address: ${input.address}`,\n `content_hash: ${input.content_hash}`,\n `content_length: ${input.content_length}`,\n `content_mime: ${input.content_mime}`,\n `signed_at: ${input.signed_at}`,\n ].join('\\n');\n}\n\n/**\n * Check whether a CanonicalMessageInput is well-formed per the spec's field\n * rules (§3, §4). Returns `{ok: true}` if every field is valid, or\n * `{ok: false, reason}` pointing at the first violation.\n *\n * Called by `stamp()` before producing the canonical bytes. Exposed for\n * UIs that want to preview validity without throwing.\n */\nexport function validateCanonicalInput(input: CanonicalMessageInput):\n | { ok: true }\n | { ok: false, reason: string } {\n // No control chars or whitespace breaks in fields — any of those would\n // invalidate the 6-line structure and produce an id nobody else can\n // reconstruct. The whole canonical message is ASCII-safe by design.\n if (!input.address || /[\\s\\x00-\\x1f]/.test(input.address)) {\n return { ok: false, reason: 'address must be non-empty with no whitespace or control chars' };\n }\n if (!/^sha256:[0-9a-f]{64}$/.test(input.content_hash)) {\n return { ok: false, reason: 'content_hash must match /^sha256:[0-9a-f]{64}$/' };\n }\n if (!Number.isInteger(input.content_length) || input.content_length < 0) {\n return { ok: false, reason: 'content_length must be a non-negative integer' };\n }\n // MIME per RFC 6838: \"type/subtype[+suffix][;params]\". We accept anything\n // that's non-empty and control-char-free; fine-grained parsing is out of\n // scope and would reject legitimate media types.\n if (!input.content_mime || /[\\x00-\\x1f]/.test(input.content_mime)) {\n return { ok: false, reason: 'content_mime must be non-empty with no control chars' };\n }\n if (!/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$/.test(input.signed_at)) {\n return { ok: false, reason: 'signed_at must be ISO 8601 UTC ending in Z' };\n }\n return { ok: true };\n}\n\nexport function canonicalMessageBytes(input: CanonicalMessageInput): Uint8Array {\n return new TextEncoder().encode(canonicalMessage(input));\n}\n\n/**\n * Compute the envelope id as lowercase hex of sha256(canonical_message_bytes).\n * Pure function — the single source of truth for identity derivation.\n */\nexport function computeEnvelopeId(input: CanonicalMessageInput): string {\n const bytes = canonicalMessageBytes(input);\n return hexEncode(sha256(bytes));\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Envelope canonicalization (SPEC §5; RFC 8785)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nexport function canonicalizeEnvelope(env: StampEnvelope): string {\n return canonicalize(env as unknown as JsonValue);\n}\n\nexport function canonicalEnvelopeBytes(env: StampEnvelope): Uint8Array {\n return new TextEncoder().encode(canonicalize(env as unknown as JsonValue) + '\\n');\n}\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value);\n}\n\nfunction encode(v: JsonValue): string {\n if (v === null) return 'null';\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (typeof v === 'number') return encodeNumber(v);\n if (typeof v === 'string') return encodeString(v);\n if (Array.isArray(v)) {\n const parts: string[] = [];\n for (const item of v) parts.push(encode(item));\n return '[' + parts.join(',') + ']';\n }\n if (typeof v === 'object') {\n const keys = Object.keys(v).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const inner = v[k];\n if (inner === undefined) continue;\n parts.push(encodeString(k) + ':' + encode(inner as JsonValue));\n }\n return '{' + parts.join(',') + '}';\n }\n throw new Error('cannot canonicalize value of type ' + typeof v);\n}\n\nfunction encodeNumber(n: number): string {\n if (!Number.isFinite(n)) throw new Error('non-finite number not JSON-canonicalizable');\n if (Object.is(n, -0)) return '0';\n if (Number.isInteger(n) && Math.abs(n) < 1e21) {\n return String(n);\n }\n return JSON.stringify(n);\n}\n\nconst ESCAPE_TABLE: Record<string, string> = {\n '\\\\': '\\\\\\\\',\n '\"': '\\\\\"',\n '\\b': '\\\\b',\n '\\f': '\\\\f',\n '\\n': '\\\\n',\n '\\r': '\\\\r',\n '\\t': '\\\\t',\n};\n\nfunction encodeString(s: string): string {\n let out = '\"';\n for (let i = 0; i < s.length; i++) {\n const ch = s[i]!;\n const code = ch.charCodeAt(0);\n if (ESCAPE_TABLE[ch]) {\n out += ESCAPE_TABLE[ch];\n } else if (code < 0x20) {\n out += '\\\\u' + code.toString(16).padStart(4, '0');\n } else {\n out += ch;\n }\n }\n out += '\"';\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Small utilities\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst HEX_TABLE = '0123456789abcdef';\n\nexport function hexEncode(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i]!;\n out += HEX_TABLE[(b >> 4) & 0x0f]! + HEX_TABLE[b & 0x0f]!;\n }\n return out;\n}\n\nexport function sha256Hex(bytes: Uint8Array): string {\n return hexEncode(sha256(bytes));\n}\n","// stamp() and verify() — reference implementation of OC Stamp v1. SPEC §4, §8.\n\nimport { sha256 } from '@noble/hashes/sha256';\n\nimport {\n canonicalMessage,\n canonicalMessageBytes,\n computeEnvelopeId,\n hexEncode,\n validateCanonicalInput,\n} from './canonical.js';\nimport {\n ENVELOPE_VERSION,\n type StampEnvelope,\n type StampInput,\n type VerifyErrorCode,\n type VerifyInput,\n type VerifyOk,\n type VerifyResult,\n} from './types.js';\n\n/**\n * Compute `{hash, length}` for raw bytes. Convenience over the common\n * `sha256(bytes)` pattern that prefixes with `sha256:` and returns lowercase\n * hex. Pass the result to `stamp({ content: hashContent(bytes), mime })` when\n * you don't want stamp() to hash the bytes itself.\n */\nexport function hashContent(bytes: Uint8Array): { hash: string; length: number } {\n return {\n hash: 'sha256:' + hexEncode(sha256(bytes)),\n length: bytes.byteLength,\n };\n}\n\nexport class StampError extends Error {\n code: VerifyErrorCode;\n constructor(code: VerifyErrorCode, message: string) {\n super(message);\n this.code = code;\n this.name = 'StampError';\n }\n}\n\n/**\n * Build and sign a stamp envelope. Returns the envelope with sig populated but\n * ots left null — OTS calendar submission is a separate step (see\n * @orangecheck/stamp-ots).\n */\nexport async function stamp(input: StampInput): Promise<StampEnvelope> {\n // Resolve content hash + length. Two modes: raw bytes (we hash) or\n // pre-computed {hash, length} (we trust the caller).\n let contentHash: string;\n let contentLength: number;\n if (input.content instanceof Uint8Array) {\n contentHash = 'sha256:' + hexEncode(sha256(input.content));\n contentLength = input.content.byteLength;\n } else {\n // Pre-computed. Caller must have used SHA-256 and declared length correctly.\n // Guard against the easy typos.\n const h = input.content.hash;\n if (!h.startsWith('sha256:') || h.length !== 'sha256:'.length + 64) {\n throw new StampError(\n 'E_MALFORMED',\n 'content.hash must be \"sha256:\" + 64 lowercase hex chars'\n );\n }\n contentHash = h;\n contentLength = input.content.length;\n if (!Number.isInteger(contentLength) || contentLength < 0) {\n throw new StampError('E_MALFORMED', 'content.length must be a non-negative integer');\n }\n }\n\n const signedAt = isoUtcSeconds(input.signedAt ?? new Date());\n\n const canon = {\n address: input.signer.address,\n content_hash: contentHash,\n content_length: contentLength,\n content_mime: input.mime,\n signed_at: signedAt,\n };\n\n const v = validateCanonicalInput(canon);\n if (!v.ok) {\n // Validation rejects inputs that would silently break canonical-message\n // reconstructability downstream (whitespace in address, wrong hash\n // prefix, floats in length, etc.). Catching them here prevents a\n // signed envelope that no verifier can accept.\n throw new StampError('E_MALFORMED', v.reason);\n }\n\n const id = computeEnvelopeId(canon);\n\n // BIP-322 signs the ASCII hex representation of id. Hex is chosen (not\n // raw bytes) so wallets that render the signed message to the user show\n // something legible. The id itself commits to every field in the canonical\n // message so this is a transitive commitment to the full signing ceremony.\n const sigValue = await input.signer.signMessage(id);\n\n const envelope: StampEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'stamp',\n id,\n content: {\n hash: contentHash,\n length: contentLength,\n mime: input.mime,\n ref: input.ref ?? null,\n },\n signer: {\n address: input.signer.address,\n alg: 'bip322',\n },\n signed_at: signedAt,\n stake: input.stake ?? null,\n ots: null,\n sig: {\n alg: 'bip322',\n pubkey: input.signer.address,\n value: sigValue,\n },\n };\n\n return envelope;\n}\n\n/**\n * Verify a stamp envelope. See SPEC §8 for the full algorithm. Returns a\n * structured result; callers can match on `result.ok` and drill into\n * `result.code` for the specific failure.\n */\nexport async function verify(input: VerifyInput): Promise<VerifyResult> {\n const env = input.envelope;\n\n // 1. Version check.\n if (env.v !== ENVELOPE_VERSION) {\n return err('E_UNSUPPORTED_VERSION', `envelope version ${env.v} not supported by this implementation`);\n }\n\n // 2. Shape check.\n const shape = checkShape(env);\n if (shape) return shape;\n\n // 3. Canonical message reconstruction + id check.\n const canon = {\n address: env.signer.address,\n content_hash: env.content.hash,\n content_length: env.content.length,\n content_mime: env.content.mime,\n signed_at: env.signed_at,\n };\n const reconstructedMessage = canonicalMessage(canon);\n const reconstructedId = hexEncode(sha256(canonicalMessageBytes(canon)));\n if (reconstructedId !== env.id) {\n return err('E_BAD_ID', `reconstructed id (${reconstructedId}) does not match envelope.id (${env.id})`);\n }\n\n // 4. Signature verify.\n if (!input.skipSignatureVerification) {\n if (!input.verifyBip322) {\n return err('E_BAD_SIG', 'no BIP-322 verifier supplied');\n }\n const ok = await input.verifyBip322(env.id, env.sig.value, env.signer.address);\n if (!ok) return err('E_BAD_SIG', 'BIP-322 signature did not verify');\n }\n\n // 5. Anchor verify.\n let anchor: VerifyOk['anchor'];\n if (env.ots === null) {\n anchor = { status: 'none' };\n } else if (env.ots.status === 'pending') {\n anchor = { status: 'pending' };\n } else {\n // confirmed\n const h = env.ots.block_height;\n const hash = env.ots.block_hash;\n if (h === null || hash === null) {\n return err('E_BAD_ANCHOR', 'confirmed OTS proof missing block_height or block_hash');\n }\n let verified = false;\n if (input.verifyOtsAnchor) {\n try {\n verified = await input.verifyOtsAnchor(env.ots.proof, h, hash);\n } catch (e) {\n return err('E_BAD_ANCHOR', `anchor verifier threw: ${(e as Error).message}`);\n }\n if (!verified) return err('E_BAD_ANCHOR', 'OTS proof did not chain to declared Bitcoin block header');\n }\n // If no anchor verifier was supplied, we accept on shape. The result\n // reports verified: false so callers can decide what to do.\n anchor = { status: 'confirmed', blockHeight: h, blockHash: hash, verified };\n }\n\n // 6. Content bytes check (if supplied).\n if (input.content) {\n const actualHash = 'sha256:' + hexEncode(sha256(input.content));\n if (actualHash !== env.content.hash) {\n return err('E_BAD_CONTENT', `content hash (${actualHash}) does not match envelope.content.hash (${env.content.hash})`);\n }\n }\n\n return {\n ok: true,\n envelope: env,\n canonicalMessage: reconstructedMessage,\n id: env.id,\n anchor,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction checkShape(env: StampEnvelope): VerifyResult | null {\n if (env.kind !== 'stamp') return err('E_MALFORMED', 'envelope.kind must be \"stamp\"');\n if (!env.id || typeof env.id !== 'string' || !/^[0-9a-f]{64}$/.test(env.id)) {\n return err('E_MALFORMED', 'envelope.id must be 64 lowercase hex chars');\n }\n if (!env.content || typeof env.content.hash !== 'string' || !env.content.hash.startsWith('sha256:')) {\n return err('E_MALFORMED', 'envelope.content.hash must start with \"sha256:\"');\n }\n if (!Number.isInteger(env.content.length) || env.content.length < 0) {\n return err('E_MALFORMED', 'envelope.content.length must be a non-negative integer');\n }\n if (typeof env.content.mime !== 'string' || env.content.mime.length === 0) {\n return err('E_MALFORMED', 'envelope.content.mime must be a non-empty string');\n }\n if (!env.signer || typeof env.signer.address !== 'string' || env.signer.alg !== 'bip322') {\n return err('E_MALFORMED', 'envelope.signer invalid');\n }\n if (env.signer.address !== env.sig.pubkey) {\n return err('E_MALFORMED', 'envelope.signer.address must equal envelope.sig.pubkey');\n }\n if (typeof env.signed_at !== 'string' || !/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$/.test(env.signed_at)) {\n return err('E_MALFORMED', 'envelope.signed_at must be ISO 8601 UTC ending in Z');\n }\n if (!env.sig || env.sig.alg !== 'bip322' || typeof env.sig.value !== 'string') {\n return err('E_MALFORMED', 'envelope.sig invalid');\n }\n return null;\n}\n\nfunction err(code: VerifyErrorCode, message: string): VerifyResult {\n return { ok: false, code, message };\n}\n\nfunction isoUtcSeconds(d: Date): string {\n // Drop milliseconds; SPEC allows ms-precision but we default to seconds\n // for shorter wallet-signing prompts. Callers needing millisecond\n // precision can pass a pre-formatted ISO string via a future field.\n return d.toISOString().replace(/\\.\\d+Z$/, 'Z');\n}\n"]}
{
"name": "@orangecheck/stamp-core",
"version": "0.1.0",
"version": "0.1.1",
"description": "OC Stamp canonical message, envelope format, stamp and verify. See https://github.com/orangecheck/oc-stamp-protocol.",

@@ -5,0 +5,0 @@ "keywords": [

@@ -10,2 +10,3 @@ import { describe, expect, it } from 'vitest';

sha256Hex,
validateCanonicalInput,
} from './canonical.js';

@@ -96,2 +97,56 @@

describe('validateCanonicalInput', () => {
const good = {
address: 'bc1qalice000000000000000000000000000000000',
content_hash: 'sha256:' + '1'.repeat(64),
content_length: 42,
content_mime: 'text/plain',
signed_at: '2026-04-24T18:30:00Z',
};
it('accepts a well-formed input', () => {
expect(validateCanonicalInput(good).ok).toBe(true);
});
it('rejects an address with whitespace', () => {
const r = validateCanonicalInput({ ...good, address: 'bc1q alice' });
expect(r.ok).toBe(false);
});
it('rejects an address with embedded newline', () => {
const r = validateCanonicalInput({ ...good, address: 'bc1q\nalice' });
expect(r.ok).toBe(false);
});
it('rejects a hash missing sha256: prefix', () => {
const r = validateCanonicalInput({ ...good, content_hash: 'a'.repeat(64) });
expect(r.ok).toBe(false);
});
it('rejects a non-hex hash body', () => {
const r = validateCanonicalInput({ ...good, content_hash: 'sha256:' + 'Z'.repeat(64) });
expect(r.ok).toBe(false);
});
it('rejects fractional content_length', () => {
const r = validateCanonicalInput({ ...good, content_length: 3.14 });
expect(r.ok).toBe(false);
});
it('rejects negative content_length', () => {
const r = validateCanonicalInput({ ...good, content_length: -1 });
expect(r.ok).toBe(false);
});
it('rejects signed_at missing Z suffix', () => {
const r = validateCanonicalInput({ ...good, signed_at: '2026-04-24T18:30:00' });
expect(r.ok).toBe(false);
});
it('rejects empty mime', () => {
const r = validateCanonicalInput({ ...good, content_mime: '' });
expect(r.ok).toBe(false);
});
});
describe('utilities', () => {

@@ -98,0 +153,0 @@ it('hexEncode produces lowercase hex', () => {

@@ -36,2 +36,37 @@ // Canonical message + RFC 8785 JSON canonicalization for OC Stamp.

/**
* Check whether a CanonicalMessageInput is well-formed per the spec's field
* rules (§3, §4). Returns `{ok: true}` if every field is valid, or
* `{ok: false, reason}` pointing at the first violation.
*
* Called by `stamp()` before producing the canonical bytes. Exposed for
* UIs that want to preview validity without throwing.
*/
export function validateCanonicalInput(input: CanonicalMessageInput):
| { ok: true }
| { ok: false, reason: string } {
// No control chars or whitespace breaks in fields — any of those would
// invalidate the 6-line structure and produce an id nobody else can
// reconstruct. The whole canonical message is ASCII-safe by design.
if (!input.address || /[\s\x00-\x1f]/.test(input.address)) {
return { ok: false, reason: 'address must be non-empty with no whitespace or control chars' };
}
if (!/^sha256:[0-9a-f]{64}$/.test(input.content_hash)) {
return { ok: false, reason: 'content_hash must match /^sha256:[0-9a-f]{64}$/' };
}
if (!Number.isInteger(input.content_length) || input.content_length < 0) {
return { ok: false, reason: 'content_length must be a non-negative integer' };
}
// MIME per RFC 6838: "type/subtype[+suffix][;params]". We accept anything
// that's non-empty and control-char-free; fine-grained parsing is out of
// scope and would reject legitimate media types.
if (!input.content_mime || /[\x00-\x1f]/.test(input.content_mime)) {
return { ok: false, reason: 'content_mime must be non-empty with no control chars' };
}
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/.test(input.signed_at)) {
return { ok: false, reason: 'signed_at must be ISO 8601 UTC ending in Z' };
}
return { ok: true };
}
export function canonicalMessageBytes(input: CanonicalMessageInput): Uint8Array {

@@ -38,0 +73,0 @@ return new TextEncoder().encode(canonicalMessage(input));

@@ -11,4 +11,5 @@ export * from './types.js';

sha256Hex,
validateCanonicalInput,
} from './canonical.js';
export type { JsonValue } from './canonical.js';
export { stamp, verify, StampError } from './stamp.js';
export { stamp, verify, StampError, hashContent } from './stamp.js';
import { describe, expect, it } from 'vitest';
import { canonicalMessage, computeEnvelopeId } from './canonical.js';
import { StampError, stamp, verify } from './stamp.js';
import { StampError, hashContent, stamp, verify } from './stamp.js';
import type { StampEnvelope } from './types.js';

@@ -256,2 +256,49 @@

describe('input validation', () => {
it('rejects malformed signer.address at stamp() time', async () => {
await expect(
stamp({
content: { hash: 'sha256:' + '0'.repeat(64), length: 1 },
mime: 'text/plain',
signer: { address: 'bc1q alice', signMessage: async () => FIXED_SIG },
signedAt: FIXED_DATE,
})
).rejects.toBeInstanceOf(StampError);
});
it('rejects an empty mime at stamp() time', async () => {
await expect(
stamp({
content: { hash: 'sha256:' + '0'.repeat(64), length: 1 },
mime: '',
signer: fakeSigner(),
signedAt: FIXED_DATE,
})
).rejects.toBeInstanceOf(StampError);
});
});
describe('hashContent helper', () => {
it('returns sha256: prefix + length', () => {
const bytes = new TextEncoder().encode('hi');
const h = hashContent(bytes);
expect(h.hash.startsWith('sha256:')).toBe(true);
expect(h.hash).toHaveLength('sha256:'.length + 64);
expect(h.length).toBe(2);
});
it('hashContent(x).hash matches what stamp() produces from raw bytes', async () => {
const bytes = new Uint8Array([1, 2, 3, 4, 5]);
const pre = hashContent(bytes);
const env = await stamp({
content: bytes,
mime: 'application/octet-stream',
signer: fakeSigner(),
signedAt: FIXED_DATE,
});
expect(env.content.hash).toBe(pre.hash);
expect(env.content.length).toBe(pre.length);
});
});
describe('canonical message consistency', () => {

@@ -258,0 +305,0 @@ it('round-trips via canonicalMessage()', () => {

@@ -10,2 +10,3 @@ // stamp() and verify() — reference implementation of OC Stamp v1. SPEC §4, §8.

hexEncode,
validateCanonicalInput,
} from './canonical.js';

@@ -22,2 +23,15 @@ import {

/**
* Compute `{hash, length}` for raw bytes. Convenience over the common
* `sha256(bytes)` pattern that prefixes with `sha256:` and returns lowercase
* hex. Pass the result to `stamp({ content: hashContent(bytes), mime })` when
* you don't want stamp() to hash the bytes itself.
*/
export function hashContent(bytes: Uint8Array): { hash: string; length: number } {
return {
hash: 'sha256:' + hexEncode(sha256(bytes)),
length: bytes.byteLength,
};
}
export class StampError extends Error {

@@ -72,2 +86,11 @@ code: VerifyErrorCode;

const v = validateCanonicalInput(canon);
if (!v.ok) {
// Validation rejects inputs that would silently break canonical-message
// reconstructability downstream (whitespace in address, wrong hash
// prefix, floats in length, etc.). Catching them here prevents a
// signed envelope that no verifier can accept.
throw new StampError('E_MALFORMED', v.reason);
}
const id = computeEnvelopeId(canon);

@@ -74,0 +97,0 @@