🚀. Socket Launch Week Day 3:Socket Firewall Now Blocks Malicious VS Code and Open VSX Extensions.Learn more
Sign In

@orangecheck/lock-core

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@orangecheck/lock-core - npm Package Compare versions

Comparing version
0.1.0
to
1.0.0
+6
-0
dist/index.js

@@ -110,2 +110,8 @@ 'use strict';

async function seal(input) {
if (!input.recipients || input.recipients.length === 0) {
throw new LockError(
"E_NO_RECIPIENTS",
"seal() requires at least one recipient"
);
}
const kind = input.kind ?? (input.payment ? "payment" : "identity");

@@ -112,0 +118,0 @@ const now = (/* @__PURE__ */ new Date()).toISOString();

+1
-1

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

{"version":3,"sources":["../src/types.ts","../src/canonical.ts","../src/seal.ts"],"names":["utf8Encode","sha256Bytes","randomBytesN","hexDecode","generateX25519KeyPair","x25519Shared","hkdfSha256","aesGcmEncrypt","zeroize","hexEncode","b64urlEncode","b64urlDecode","aesGcmDecrypt"],"mappings":"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","file":"index.js","sourcesContent":["// Wire types for OC Lock v2 envelopes. See SPEC.md §4.\n\nexport const ENVELOPE_VERSION = 2 as const;\n\nexport type EnvelopeKind = 'identity' | 'payment';\n\nexport interface EnvelopeAlg {\n kem: 'x25519';\n aead: 'aes-256-gcm';\n kdf: 'hkdf-sha256';\n}\n\nexport interface EnvelopeFrom {\n address: string;\n attestation_id?: string;\n}\n\nexport interface EnvelopeRecipient {\n address: string;\n device_id: string;\n device_pk: string; // 32-byte hex X25519 pubkey\n eph_pk: string; // 32-byte hex X25519 ephemeral pubkey\n wrapped_key: string; // base64url(aead(content_key, kek, nonce_kek))\n nonce_kek: string; // 12-byte hex\n}\n\nexport interface EnvelopePayment {\n amount_sats: number;\n address: string;\n confirmations: number;\n relay: string;\n}\n\nexport interface EnvelopeSignature {\n alg: 'bip322';\n pubkey: string; // sender btc address\n value: string; // base64 BIP-322\n}\n\nexport interface LockEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: EnvelopeKind;\n id: string; // 32-byte hex\n alg: EnvelopeAlg;\n from: EnvelopeFrom;\n recipients: EnvelopeRecipient[];\n ciphertext: string; // base64url\n nonce_ct: string; // 12-byte hex\n hint?: string;\n created_at: string; // iso8601 utc\n expires_at: string | null;\n payment: EnvelopePayment | null;\n sig: EnvelopeSignature;\n}\n\n// Inputs\n\nexport interface DeviceRecord {\n address: string;\n device_id: string;\n device_pk: string; // hex\n}\n\nexport interface SealInput {\n payload: Uint8Array;\n sender: {\n address: string;\n attestation_id?: string;\n /**\n * Returns a BIP-322 base64 signature of the given message bytes as UTF-8.\n * Exactly one call per seal (over the envelope id).\n */\n signMessage: (msg: string) => Promise<string>;\n };\n recipients: DeviceRecord[];\n kind?: EnvelopeKind;\n hint?: string;\n expiresAt?: Date | null;\n payment?: EnvelopePayment | null;\n}\n\nexport interface UnsealInput {\n envelope: LockEnvelope;\n device: {\n device_id: string;\n /** 32-byte X25519 private key for this device. */\n secretKey: Uint8Array;\n };\n /**\n * Called to verify the sender's BIP-322 signature. Returns true if valid.\n * The verifier is injected because verification libraries differ between\n * Node and browser contexts; consumers plug their own.\n */\n verifyBip322?: (msg: string, signatureB64: string, address: string) => Promise<boolean>;\n /** Skip sender signature verification. Default false; true only for self-seals. */\n skipSenderVerification?: boolean;\n /** Current time; defaults to Date.now(). Used for expiry enforcement. */\n now?: () => Date;\n}\n\nexport interface UnsealResult {\n payload: Uint8Array;\n envelopeId: string;\n sender: EnvelopeFrom;\n matchedDeviceId: string;\n}\n","// RFC 8785 JSON Canonicalization Scheme with one addition: within the\n// envelope, `recipients` entries are sorted by `device_id` ascending before\n// canonicalization. See SPEC.md §5.\n//\n// We implement a small, self-contained canonicalizer. Inputs are plain JSON\n// values (objects, arrays, strings, numbers, booleans, null). Undefined and\n// functions are rejected.\n\nimport { utf8Encode } from '@orangecheck/lock-crypto';\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nconst JSON_RECIPIENTS_SORT_PATH = ['recipients'] as const;\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value, []);\n}\n\nexport function canonicalBytes(value: JsonValue): Uint8Array {\n return utf8Encode(canonicalize(value) + '\\n');\n}\n\nfunction encode(v: JsonValue, path: string[]): 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 let items = v;\n // Sort recipients by device_id when we're at path ['recipients'].\n if (\n path.length === JSON_RECIPIENTS_SORT_PATH.length &&\n path.every((p, i) => p === JSON_RECIPIENTS_SORT_PATH[i]) &&\n items.every(\n (it) =>\n it &&\n typeof it === 'object' &&\n !Array.isArray(it) &&\n typeof (it as Record<string, JsonValue>).device_id === 'string'\n )\n ) {\n items = [...items].sort((a, b) => {\n const aid = (a as Record<string, JsonValue>).device_id as string;\n const bid = (b as Record<string, JsonValue>).device_id as string;\n return aid < bid ? -1 : aid > bid ? 1 : 0;\n });\n }\n for (let i = 0; i < items.length; i++) {\n parts.push(encode(items[i]!, path.concat(String(i))));\n }\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, path.concat(k)));\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 // Follow JSON.stringify for non-integers; it produces canonical-ish output\n // that matches IEEE 754 round-tripping for our use case (we never emit\n // floats ourselves).\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","// Seal and unseal OC Lock envelopes. See SPEC.md §4.\n\nimport {\n aesGcmDecrypt,\n aesGcmEncrypt,\n b64urlDecode,\n b64urlEncode,\n generateX25519KeyPair,\n hexDecode,\n hexEncode,\n hkdfSha256,\n randomBytesN,\n sha256Bytes,\n utf8Encode,\n x25519Shared,\n zeroize,\n} from '@orangecheck/lock-crypto';\n\nimport { canonicalBytes, canonicalize, type JsonValue } from './canonical.js';\nimport {\n ENVELOPE_VERSION,\n type EnvelopeAlg,\n type EnvelopeKind,\n type EnvelopeRecipient,\n type LockEnvelope,\n type SealInput,\n type UnsealInput,\n type UnsealResult,\n} from './types.js';\n\nconst DEFAULT_ALG: EnvelopeAlg = {\n kem: 'x25519',\n aead: 'aes-256-gcm',\n kdf: 'hkdf-sha256',\n};\n\nfunction envelopeToJson(env: LockEnvelope): JsonValue {\n return env as unknown as JsonValue;\n}\n\n/**\n * Compute the draft envelope id used as AAD during AEAD operations. This is\n * the canonical envelope with `ciphertext`, `sig`, and each\n * `recipients[*].wrapped_key` elided per SPEC §4.2 step 3.\n */\nfunction computeDraftId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n ciphertext: '',\n sig: { alg: 'bip322', pubkey: env.from.address, value: '' },\n recipients: env.recipients.map((r) => ({ ...r, wrapped_key: '' })),\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\n/**\n * Compute the final envelope id: SHA-256 of the canonical envelope with\n * `sig.value` stripped to empty. The `id` field itself is excluded from the\n * hash input (we set it to empty before hashing).\n */\nfunction computeEnvelopeId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n sig: { alg: env.sig.alg, pubkey: env.sig.pubkey, value: '' },\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\nexport async function seal(input: SealInput): Promise<LockEnvelope> {\n const kind: EnvelopeKind = input.kind ?? (input.payment ? 'payment' : 'identity');\n const now = new Date().toISOString();\n const nonce_ct = randomBytesN(12);\n\n const content_key = randomBytesN(32);\n\n // Build recipients with wrapped keys.\n const recipients: EnvelopeRecipient[] = [];\n for (const r of input.recipients) {\n const device_pk_bytes = hexDecode(r.device_pk);\n const ephemeral = generateX25519KeyPair();\n const shared = x25519Shared(ephemeral.secret, device_pk_bytes);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + r.device_id),\n 32\n );\n const nonce_kek = randomBytesN(12);\n const wrapped = aesGcmEncrypt(kek, nonce_kek, content_key, utf8Encode(r.device_id));\n zeroize(ephemeral.secret);\n zeroize(shared);\n zeroize(kek);\n recipients.push({\n address: r.address,\n device_id: r.device_id,\n device_pk: r.device_pk,\n eph_pk: hexEncode(ephemeral.public),\n wrapped_key: b64urlEncode(wrapped),\n nonce_kek: hexEncode(nonce_kek),\n });\n }\n\n // Draft envelope (id, sig, ciphertext empty) for AAD computation.\n const draftEnv: LockEnvelope = {\n v: ENVELOPE_VERSION,\n kind,\n id: '',\n alg: DEFAULT_ALG,\n from: {\n address: input.sender.address,\n ...(input.sender.attestation_id\n ? { attestation_id: input.sender.attestation_id }\n : {}),\n },\n recipients,\n ciphertext: '',\n nonce_ct: hexEncode(nonce_ct),\n ...(input.hint !== undefined ? { hint: input.hint } : {}),\n created_at: now,\n expires_at: input.expiresAt ? input.expiresAt.toISOString() : null,\n payment: input.payment ?? null,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: '' },\n };\n\n const draftId = computeDraftId(draftEnv);\n const ciphertext = aesGcmEncrypt(content_key, nonce_ct, input.payload, draftId);\n zeroize(content_key);\n\n const withCt: LockEnvelope = { ...draftEnv, ciphertext: b64urlEncode(ciphertext) };\n const envelopeIdBytes = computeEnvelopeId(withCt);\n const envelopeId = hexEncode(envelopeIdBytes);\n\n const signatureB64 = await input.sender.signMessage(envelopeId);\n\n return {\n ...withCt,\n id: envelopeId,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: signatureB64 },\n };\n}\n\nexport async function unseal(input: UnsealInput): Promise<UnsealResult> {\n const env = input.envelope;\n\n // Expiry.\n if (env.expires_at) {\n const now = input.now ? input.now() : new Date();\n if (now.getTime() > new Date(env.expires_at).getTime()) {\n throw makeError('E_EXPIRED', 'envelope is expired');\n }\n }\n\n // Recompute envelope id.\n const idBytes = computeEnvelopeId(env);\n const idHex = hexEncode(idBytes);\n if (idHex !== env.id) {\n throw makeError('E_BAD_SIG', 'envelope id mismatch');\n }\n\n // Verify sender signature unless caller opts out (self-seal).\n if (!input.skipSenderVerification) {\n if (!input.verifyBip322) {\n throw makeError('E_BAD_SIG', 'no bip322 verifier supplied');\n }\n const ok = await input.verifyBip322(env.id, env.sig.value, env.sig.pubkey);\n if (!ok) throw makeError('E_BAD_SIG', 'sender signature did not verify');\n }\n\n // Find our recipient entry.\n const mine = env.recipients.find((r) => r.device_id === input.device.device_id);\n if (!mine) throw makeError('E_NOT_ADDRESSED', 'no matching device_id in recipients');\n\n // Unwrap content key.\n const draftId = computeDraftId({ ...env, id: env.id });\n const eph_pk = hexDecode(mine.eph_pk);\n const shared = x25519Shared(input.device.secretKey, eph_pk);\n const nonce_ct = hexDecode(env.nonce_ct);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + mine.device_id),\n 32\n );\n const nonce_kek = hexDecode(mine.nonce_kek);\n const wrapped = b64urlDecode(mine.wrapped_key);\n\n let content_key: Uint8Array;\n try {\n content_key = aesGcmDecrypt(kek, nonce_kek, wrapped, utf8Encode(mine.device_id));\n } catch (e) {\n throw makeError('E_BAD_TAG', 'wrapped key failed to decrypt');\n } finally {\n zeroize(shared);\n zeroize(kek);\n }\n\n // Decrypt ciphertext.\n const ciphertext = b64urlDecode(env.ciphertext);\n let payload: Uint8Array;\n try {\n payload = aesGcmDecrypt(content_key, nonce_ct, ciphertext, draftId);\n } catch (e) {\n zeroize(content_key);\n throw makeError('E_BAD_TAG', 'ciphertext failed to decrypt');\n }\n zeroize(content_key);\n\n return {\n payload,\n envelopeId: env.id,\n sender: env.from,\n matchedDeviceId: mine.device_id,\n };\n}\n\nexport class LockError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.code = code;\n this.name = 'LockError';\n }\n}\n\nfunction makeError(code: string, message: string): LockError {\n return new LockError(code, message);\n}\n\nexport { canonicalize, canonicalBytes };\n"]}
{"version":3,"sources":["../src/types.ts","../src/canonical.ts","../src/seal.ts"],"names":["utf8Encode","sha256Bytes","randomBytesN","hexDecode","generateX25519KeyPair","x25519Shared","hkdfSha256","aesGcmEncrypt","zeroize","hexEncode","b64urlEncode","b64urlDecode","aesGcmDecrypt"],"mappings":"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","file":"index.js","sourcesContent":["// Wire types for OC Lock v2 envelopes. See SPEC.md §4.\n\nexport const ENVELOPE_VERSION = 2 as const;\n\nexport type EnvelopeKind = 'identity' | 'payment';\n\nexport interface EnvelopeAlg {\n kem: 'x25519';\n aead: 'aes-256-gcm';\n kdf: 'hkdf-sha256';\n}\n\nexport interface EnvelopeFrom {\n address: string;\n attestation_id?: string;\n}\n\nexport interface EnvelopeRecipient {\n address: string;\n device_id: string;\n device_pk: string; // 32-byte hex X25519 pubkey\n eph_pk: string; // 32-byte hex X25519 ephemeral pubkey\n wrapped_key: string; // base64url(aead(content_key, kek, nonce_kek))\n nonce_kek: string; // 12-byte hex\n}\n\nexport interface EnvelopePayment {\n amount_sats: number;\n address: string;\n confirmations: number;\n relay: string;\n}\n\nexport interface EnvelopeSignature {\n alg: 'bip322';\n pubkey: string; // sender btc address\n value: string; // base64 BIP-322\n}\n\nexport interface LockEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: EnvelopeKind;\n id: string; // 32-byte hex\n alg: EnvelopeAlg;\n from: EnvelopeFrom;\n recipients: EnvelopeRecipient[];\n ciphertext: string; // base64url\n nonce_ct: string; // 12-byte hex\n hint?: string;\n created_at: string; // iso8601 utc\n expires_at: string | null;\n payment: EnvelopePayment | null;\n sig: EnvelopeSignature;\n}\n\n// Inputs\n\nexport interface DeviceRecord {\n address: string;\n device_id: string;\n device_pk: string; // hex\n}\n\nexport interface SealInput {\n payload: Uint8Array;\n sender: {\n address: string;\n attestation_id?: string;\n /**\n * Returns a BIP-322 base64 signature of the given message bytes as UTF-8.\n * Exactly one call per seal (over the envelope id).\n */\n signMessage: (msg: string) => Promise<string>;\n };\n recipients: DeviceRecord[];\n kind?: EnvelopeKind;\n hint?: string;\n expiresAt?: Date | null;\n payment?: EnvelopePayment | null;\n}\n\nexport interface UnsealInput {\n envelope: LockEnvelope;\n device: {\n device_id: string;\n /** 32-byte X25519 private key for this device. */\n secretKey: Uint8Array;\n };\n /**\n * Called to verify the sender's BIP-322 signature. Returns true if valid.\n * The verifier is injected because verification libraries differ between\n * Node and browser contexts; consumers plug their own.\n */\n verifyBip322?: (msg: string, signatureB64: string, address: string) => Promise<boolean>;\n /** Skip sender signature verification. Default false; true only for self-seals. */\n skipSenderVerification?: boolean;\n /** Current time; defaults to Date.now(). Used for expiry enforcement. */\n now?: () => Date;\n}\n\nexport interface UnsealResult {\n payload: Uint8Array;\n envelopeId: string;\n sender: EnvelopeFrom;\n matchedDeviceId: string;\n}\n","// RFC 8785 JSON Canonicalization Scheme with one addition: within the\n// envelope, `recipients` entries are sorted by `device_id` ascending before\n// canonicalization. See SPEC.md §5.\n//\n// We implement a small, self-contained canonicalizer. Inputs are plain JSON\n// values (objects, arrays, strings, numbers, booleans, null). Undefined and\n// functions are rejected.\n\nimport { utf8Encode } from '@orangecheck/lock-crypto';\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nconst JSON_RECIPIENTS_SORT_PATH = ['recipients'] as const;\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value, []);\n}\n\nexport function canonicalBytes(value: JsonValue): Uint8Array {\n return utf8Encode(canonicalize(value) + '\\n');\n}\n\nfunction encode(v: JsonValue, path: string[]): 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 let items = v;\n // Sort recipients by device_id when we're at path ['recipients'].\n if (\n path.length === JSON_RECIPIENTS_SORT_PATH.length &&\n path.every((p, i) => p === JSON_RECIPIENTS_SORT_PATH[i]) &&\n items.every(\n (it) =>\n it &&\n typeof it === 'object' &&\n !Array.isArray(it) &&\n typeof (it as Record<string, JsonValue>).device_id === 'string'\n )\n ) {\n items = [...items].sort((a, b) => {\n const aid = (a as Record<string, JsonValue>).device_id as string;\n const bid = (b as Record<string, JsonValue>).device_id as string;\n return aid < bid ? -1 : aid > bid ? 1 : 0;\n });\n }\n for (let i = 0; i < items.length; i++) {\n parts.push(encode(items[i]!, path.concat(String(i))));\n }\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, path.concat(k)));\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 // Follow JSON.stringify for non-integers; it produces canonical-ish output\n // that matches IEEE 754 round-tripping for our use case (we never emit\n // floats ourselves).\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","// Seal and unseal OC Lock envelopes. See SPEC.md §4.\n\nimport {\n aesGcmDecrypt,\n aesGcmEncrypt,\n b64urlDecode,\n b64urlEncode,\n generateX25519KeyPair,\n hexDecode,\n hexEncode,\n hkdfSha256,\n randomBytesN,\n sha256Bytes,\n utf8Encode,\n x25519Shared,\n zeroize,\n} from '@orangecheck/lock-crypto';\n\nimport { canonicalBytes, canonicalize, type JsonValue } from './canonical.js';\nimport {\n ENVELOPE_VERSION,\n type EnvelopeAlg,\n type EnvelopeKind,\n type EnvelopeRecipient,\n type LockEnvelope,\n type SealInput,\n type UnsealInput,\n type UnsealResult,\n} from './types.js';\n\nconst DEFAULT_ALG: EnvelopeAlg = {\n kem: 'x25519',\n aead: 'aes-256-gcm',\n kdf: 'hkdf-sha256',\n};\n\nfunction envelopeToJson(env: LockEnvelope): JsonValue {\n return env as unknown as JsonValue;\n}\n\n/**\n * Compute the draft envelope id used as AAD during AEAD operations. This is\n * the canonical envelope with `ciphertext`, `sig`, and each\n * `recipients[*].wrapped_key` elided per SPEC §4.2 step 3.\n */\nfunction computeDraftId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n ciphertext: '',\n sig: { alg: 'bip322', pubkey: env.from.address, value: '' },\n recipients: env.recipients.map((r) => ({ ...r, wrapped_key: '' })),\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\n/**\n * Compute the final envelope id: SHA-256 of the canonical envelope with\n * `sig.value` stripped to empty. The `id` field itself is excluded from the\n * hash input (we set it to empty before hashing).\n */\nfunction computeEnvelopeId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n sig: { alg: env.sig.alg, pubkey: env.sig.pubkey, value: '' },\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\nexport async function seal(input: SealInput): Promise<LockEnvelope> {\n // An envelope with zero recipients is unreadable by anyone — the\n // content key is only wrapped per-recipient, so the caller would\n // produce a silently-unusable ciphertext. Reject upfront with a\n // clear error rather than emitting junk.\n if (!input.recipients || input.recipients.length === 0) {\n throw new LockError(\n 'E_NO_RECIPIENTS',\n 'seal() requires at least one recipient'\n );\n }\n const kind: EnvelopeKind = input.kind ?? (input.payment ? 'payment' : 'identity');\n const now = new Date().toISOString();\n const nonce_ct = randomBytesN(12);\n\n const content_key = randomBytesN(32);\n\n // Build recipients with wrapped keys.\n const recipients: EnvelopeRecipient[] = [];\n for (const r of input.recipients) {\n const device_pk_bytes = hexDecode(r.device_pk);\n const ephemeral = generateX25519KeyPair();\n const shared = x25519Shared(ephemeral.secret, device_pk_bytes);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + r.device_id),\n 32\n );\n const nonce_kek = randomBytesN(12);\n const wrapped = aesGcmEncrypt(kek, nonce_kek, content_key, utf8Encode(r.device_id));\n zeroize(ephemeral.secret);\n zeroize(shared);\n zeroize(kek);\n recipients.push({\n address: r.address,\n device_id: r.device_id,\n device_pk: r.device_pk,\n eph_pk: hexEncode(ephemeral.public),\n wrapped_key: b64urlEncode(wrapped),\n nonce_kek: hexEncode(nonce_kek),\n });\n }\n\n // Draft envelope (id, sig, ciphertext empty) for AAD computation.\n const draftEnv: LockEnvelope = {\n v: ENVELOPE_VERSION,\n kind,\n id: '',\n alg: DEFAULT_ALG,\n from: {\n address: input.sender.address,\n ...(input.sender.attestation_id\n ? { attestation_id: input.sender.attestation_id }\n : {}),\n },\n recipients,\n ciphertext: '',\n nonce_ct: hexEncode(nonce_ct),\n ...(input.hint !== undefined ? { hint: input.hint } : {}),\n created_at: now,\n expires_at: input.expiresAt ? input.expiresAt.toISOString() : null,\n payment: input.payment ?? null,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: '' },\n };\n\n const draftId = computeDraftId(draftEnv);\n const ciphertext = aesGcmEncrypt(content_key, nonce_ct, input.payload, draftId);\n zeroize(content_key);\n\n const withCt: LockEnvelope = { ...draftEnv, ciphertext: b64urlEncode(ciphertext) };\n const envelopeIdBytes = computeEnvelopeId(withCt);\n const envelopeId = hexEncode(envelopeIdBytes);\n\n const signatureB64 = await input.sender.signMessage(envelopeId);\n\n return {\n ...withCt,\n id: envelopeId,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: signatureB64 },\n };\n}\n\nexport async function unseal(input: UnsealInput): Promise<UnsealResult> {\n const env = input.envelope;\n\n // Expiry.\n if (env.expires_at) {\n const now = input.now ? input.now() : new Date();\n if (now.getTime() > new Date(env.expires_at).getTime()) {\n throw makeError('E_EXPIRED', 'envelope is expired');\n }\n }\n\n // Recompute envelope id.\n const idBytes = computeEnvelopeId(env);\n const idHex = hexEncode(idBytes);\n if (idHex !== env.id) {\n throw makeError('E_BAD_SIG', 'envelope id mismatch');\n }\n\n // Verify sender signature unless caller opts out (self-seal).\n if (!input.skipSenderVerification) {\n if (!input.verifyBip322) {\n throw makeError('E_BAD_SIG', 'no bip322 verifier supplied');\n }\n const ok = await input.verifyBip322(env.id, env.sig.value, env.sig.pubkey);\n if (!ok) throw makeError('E_BAD_SIG', 'sender signature did not verify');\n }\n\n // Find our recipient entry.\n const mine = env.recipients.find((r) => r.device_id === input.device.device_id);\n if (!mine) throw makeError('E_NOT_ADDRESSED', 'no matching device_id in recipients');\n\n // Unwrap content key.\n const draftId = computeDraftId({ ...env, id: env.id });\n const eph_pk = hexDecode(mine.eph_pk);\n const shared = x25519Shared(input.device.secretKey, eph_pk);\n const nonce_ct = hexDecode(env.nonce_ct);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + mine.device_id),\n 32\n );\n const nonce_kek = hexDecode(mine.nonce_kek);\n const wrapped = b64urlDecode(mine.wrapped_key);\n\n let content_key: Uint8Array;\n try {\n content_key = aesGcmDecrypt(kek, nonce_kek, wrapped, utf8Encode(mine.device_id));\n } catch (e) {\n throw makeError('E_BAD_TAG', 'wrapped key failed to decrypt');\n } finally {\n zeroize(shared);\n zeroize(kek);\n }\n\n // Decrypt ciphertext.\n const ciphertext = b64urlDecode(env.ciphertext);\n let payload: Uint8Array;\n try {\n payload = aesGcmDecrypt(content_key, nonce_ct, ciphertext, draftId);\n } catch (e) {\n zeroize(content_key);\n throw makeError('E_BAD_TAG', 'ciphertext failed to decrypt');\n }\n zeroize(content_key);\n\n return {\n payload,\n envelopeId: env.id,\n sender: env.from,\n matchedDeviceId: mine.device_id,\n };\n}\n\nexport class LockError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.code = code;\n this.name = 'LockError';\n }\n}\n\nfunction makeError(code: string, message: string): LockError {\n return new LockError(code, message);\n}\n\nexport { canonicalize, canonicalBytes };\n"]}

@@ -108,2 +108,8 @@ import { utf8Encode, randomBytesN, hexDecode, generateX25519KeyPair, x25519Shared, hkdfSha256, aesGcmEncrypt, zeroize, hexEncode, b64urlEncode, b64urlDecode, aesGcmDecrypt, sha256Bytes } from '@orangecheck/lock-crypto';

async function seal(input) {
if (!input.recipients || input.recipients.length === 0) {
throw new LockError(
"E_NO_RECIPIENTS",
"seal() requires at least one recipient"
);
}
const kind = input.kind ?? (input.payment ? "payment" : "identity");

@@ -110,0 +116,0 @@ const now = (/* @__PURE__ */ new Date()).toISOString();

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

{"version":3,"sources":["../src/types.ts","../src/canonical.ts","../src/seal.ts"],"names":["utf8Encode"],"mappings":";;;AAEO,IAAM,gBAAA,GAAmB;ACgBhC,IAAM,yBAAA,GAA4B,CAAC,YAAY,CAAA;AAExC,SAAS,aAAa,KAAA,EAA0B;AACnD,EAAA,OAAO,MAAA,CAAO,KAAA,EAAO,EAAE,CAAA;AAC3B;AAEO,SAAS,eAAe,KAAA,EAA8B;AACzD,EAAA,OAAO,UAAA,CAAW,YAAA,CAAa,KAAK,CAAA,GAAI,IAAI,CAAA;AAChD;AAEA,SAAS,MAAA,CAAO,GAAc,IAAA,EAAwB;AAClD,EAAA,IAAI,CAAA,KAAM,MAAM,OAAO,MAAA;AACvB,EAAA,IAAI,OAAO,CAAA,KAAM,SAAA,EAAW,OAAO,IAAI,MAAA,GAAS,OAAA;AAChD,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,aAAa,CAAC,CAAA;AAChD,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,aAAa,CAAC,CAAA;AAChD,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG;AAClB,IAAA,MAAM,QAAkB,EAAC;AACzB,IAAA,IAAI,KAAA,GAAQ,CAAA;AAEZ,IAAA,IACI,IAAA,CAAK,MAAA,KAAW,yBAAA,CAA0B,MAAA,IAC1C,KAAK,KAAA,CAAM,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,KAAM,yBAAA,CAA0B,CAAC,CAAC,KACvD,KAAA,CAAM,KAAA;AAAA,MACF,CAAC,EAAA,KACG,EAAA,IACA,OAAO,EAAA,KAAO,QAAA,IACd,CAAC,KAAA,CAAM,OAAA,CAAQ,EAAE,CAAA,IACjB,OAAQ,GAAiC,SAAA,KAAc;AAAA,KAC/D,EACF;AACE,MAAA,KAAA,GAAQ,CAAC,GAAG,KAAK,EAAE,IAAA,CAAK,CAAC,GAAG,CAAA,KAAM;AAC9B,QAAA,MAAM,MAAO,CAAA,CAAgC,SAAA;AAC7C,QAAA,MAAM,MAAO,CAAA,CAAgC,SAAA;AAC7C,QAAA,OAAO,GAAA,GAAM,GAAA,GAAM,EAAA,GAAK,GAAA,GAAM,MAAM,CAAA,GAAI,CAAA;AAAA,MAC5C,CAAC,CAAA;AAAA,IACL;AACA,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACnC,MAAA,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,CAAC,CAAA,EAAI,IAAA,CAAK,MAAA,CAAO,MAAA,CAAO,CAAC,CAAC,CAAC,CAAC,CAAA;AAAA,IACxD;AACA,IAAA,OAAO,GAAA,GAAM,KAAA,CAAM,IAAA,CAAK,GAAG,CAAA,GAAI,GAAA;AAAA,EACnC;AACA,EAAA,IAAI,OAAO,MAAM,QAAA,EAAU;AACvB,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,IAAA,CAAK,CAAC,EAAE,IAAA,EAAK;AACjC,IAAA,MAAM,QAAkB,EAAC;AACzB,IAAA,KAAA,MAAW,KAAK,IAAA,EAAM;AAClB,MAAA,MAAM,KAAA,GAAQ,EAAE,CAAC,CAAA;AACjB,MAAA,IAAI,UAAU,MAAA,EAAW;AACzB,MAAA,KAAA,CAAM,IAAA,CAAK,YAAA,CAAa,CAAC,CAAA,GAAI,GAAA,GAAM,MAAA,CAAO,KAAA,EAAoB,IAAA,CAAK,MAAA,CAAO,CAAC,CAAC,CAAC,CAAA;AAAA,IACjF;AACA,IAAA,OAAO,GAAA,GAAM,KAAA,CAAM,IAAA,CAAK,GAAG,CAAA,GAAI,GAAA;AAAA,EACnC;AACA,EAAA,MAAM,IAAI,KAAA,CAAM,oCAAA,GAAuC,OAAO,CAAC,CAAA;AACnE;AAEA,SAAS,aAAa,CAAA,EAAmB;AACrC,EAAA,IAAI,CAAC,OAAO,QAAA,CAAS,CAAC,GAAG,MAAM,IAAI,MAAM,4CAA4C,CAAA;AACrF,EAAA,IAAI,MAAA,CAAO,EAAA,CAAG,CAAA,EAAG,EAAE,GAAG,OAAO,GAAA;AAC7B,EAAA,IAAI,MAAA,CAAO,UAAU,CAAC,CAAA,IAAK,KAAK,GAAA,CAAI,CAAC,IAAI,IAAA,EAAM;AAC3C,IAAA,OAAO,OAAO,CAAC,CAAA;AAAA,EACnB;AAIA,EAAA,OAAO,IAAA,CAAK,UAAU,CAAC,CAAA;AAC3B;AAEA,IAAM,YAAA,GAAuC;AAAA,EACzC,IAAA,EAAM,MAAA;AAAA,EACN,GAAA,EAAK,KAAA;AAAA,EACL,IAAA,EAAM,KAAA;AAAA,EACN,IAAA,EAAM,KAAA;AAAA,EACN,IAAA,EAAM,KAAA;AAAA,EACN,IAAA,EAAM,KAAA;AAAA,EACN,GAAA,EAAM;AACV,CAAA;AAEA,SAAS,aAAa,CAAA,EAAmB;AACrC,EAAA,IAAI,GAAA,GAAM,GAAA;AACV,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,QAAQ,CAAA,EAAA,EAAK;AAC/B,IAAA,MAAM,EAAA,GAAK,EAAE,CAAC,CAAA;AACd,IAAA,MAAM,IAAA,GAAO,EAAA,CAAG,UAAA,CAAW,CAAC,CAAA;AAC5B,IAAA,IAAI,YAAA,CAAa,EAAE,CAAA,EAAG;AAClB,MAAA,GAAA,IAAO,aAAa,EAAE,CAAA;AAAA,IAC1B,CAAA,MAAA,IAAW,OAAO,EAAA,EAAM;AACpB,MAAA,GAAA,IAAO,QAAQ,IAAA,CAAK,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AAAA,IACpD,CAAA,MAAO;AACH,MAAA,GAAA,IAAO,EAAA;AAAA,IACX;AAAA,EACJ;AACA,EAAA,GAAA,IAAO,GAAA;AACP,EAAA,OAAO,GAAA;AACX;AC/EA,IAAM,WAAA,GAA2B;AAAA,EAC7B,GAAA,EAAK,QAAA;AAAA,EACL,IAAA,EAAM,aAAA;AAAA,EACN,GAAA,EAAK;AACT,CAAA;AAEA,SAAS,eAAe,GAAA,EAA8B;AAClD,EAAA,OAAO,GAAA;AACX;AAOA,SAAS,eAAe,GAAA,EAA+B;AACnD,EAAA,MAAM,KAAA,GAAsB;AAAA,IACxB,GAAG,GAAA;AAAA,IACH,EAAA,EAAI,EAAA;AAAA,IACJ,UAAA,EAAY,EAAA;AAAA,IACZ,GAAA,EAAK,EAAE,GAAA,EAAK,QAAA,EAAU,QAAQ,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,KAAA,EAAO,EAAA,EAAG;AAAA,IAC1D,UAAA,EAAY,GAAA,CAAI,UAAA,CAAW,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,GAAG,CAAA,EAAG,WAAA,EAAa,EAAA,EAAG,CAAE;AAAA,GACrE;AACA,EAAA,MAAM,KAAA,GAAQ,cAAA,CAAe,cAAA,CAAe,KAAK,CAAC,CAAA;AAClD,EAAA,OAAO,YAAY,KAAK,CAAA;AAC5B;AAOA,SAAS,kBAAkB,GAAA,EAA+B;AACtD,EAAA,MAAM,KAAA,GAAsB;AAAA,IACxB,GAAG,GAAA;AAAA,IACH,EAAA,EAAI,EAAA;AAAA,IACJ,GAAA,EAAK,EAAE,GAAA,EAAK,GAAA,CAAI,GAAA,CAAI,GAAA,EAAK,MAAA,EAAQ,GAAA,CAAI,GAAA,CAAI,MAAA,EAAQ,KAAA,EAAO,EAAA;AAAG,GAC/D;AACA,EAAA,MAAM,KAAA,GAAQ,cAAA,CAAe,cAAA,CAAe,KAAK,CAAC,CAAA;AAClD,EAAA,OAAO,YAAY,KAAK,CAAA;AAC5B;AAEA,eAAsB,KAAK,KAAA,EAAyC;AAChE,EAAA,MAAM,IAAA,GAAqB,KAAA,CAAM,IAAA,KAAS,KAAA,CAAM,UAAU,SAAA,GAAY,UAAA,CAAA;AACtE,EAAA,MAAM,GAAA,GAAA,iBAAM,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AACnC,EAAA,MAAM,QAAA,GAAW,aAAa,EAAE,CAAA;AAEhC,EAAA,MAAM,WAAA,GAAc,aAAa,EAAE,CAAA;AAGnC,EAAA,MAAM,aAAkC,EAAC;AACzC,EAAA,KAAA,MAAW,CAAA,IAAK,MAAM,UAAA,EAAY;AAC9B,IAAA,MAAM,eAAA,GAAkB,SAAA,CAAU,CAAA,CAAE,SAAS,CAAA;AAC7C,IAAA,MAAM,YAAY,qBAAA,EAAsB;AACxC,IAAA,MAAM,MAAA,GAAS,YAAA,CAAa,SAAA,CAAU,MAAA,EAAQ,eAAe,CAAA;AAC7D,IAAA,MAAM,GAAA,GAAM,UAAA;AAAA,MACR,MAAA;AAAA,MACA,QAAA;AAAA,MACAA,UAAAA,CAAW,iBAAA,GAAoB,CAAA,CAAE,SAAS,CAAA;AAAA,MAC1C;AAAA,KACJ;AACA,IAAA,MAAM,SAAA,GAAY,aAAa,EAAE,CAAA;AACjC,IAAA,MAAM,OAAA,GAAU,cAAc,GAAA,EAAK,SAAA,EAAW,aAAaA,UAAAA,CAAW,CAAA,CAAE,SAAS,CAAC,CAAA;AAClF,IAAA,OAAA,CAAQ,UAAU,MAAM,CAAA;AACxB,IAAA,OAAA,CAAQ,MAAM,CAAA;AACd,IAAA,OAAA,CAAQ,GAAG,CAAA;AACX,IAAA,UAAA,CAAW,IAAA,CAAK;AAAA,MACZ,SAAS,CAAA,CAAE,OAAA;AAAA,MACX,WAAW,CAAA,CAAE,SAAA;AAAA,MACb,WAAW,CAAA,CAAE,SAAA;AAAA,MACb,MAAA,EAAQ,SAAA,CAAU,SAAA,CAAU,MAAM,CAAA;AAAA,MAClC,WAAA,EAAa,aAAa,OAAO,CAAA;AAAA,MACjC,SAAA,EAAW,UAAU,SAAS;AAAA,KACjC,CAAA;AAAA,EACL;AAGA,EAAA,MAAM,QAAA,GAAyB;AAAA,IAC3B,CAAA,EAAG,gBAAA;AAAA,IACH,IAAA;AAAA,IACA,EAAA,EAAI,EAAA;AAAA,IACJ,GAAA,EAAK,WAAA;AAAA,IACL,IAAA,EAAM;AAAA,MACF,OAAA,EAAS,MAAM,MAAA,CAAO,OAAA;AAAA,MACtB,GAAI,KAAA,CAAM,MAAA,CAAO,cAAA,GACX,EAAE,gBAAgB,KAAA,CAAM,MAAA,CAAO,cAAA,EAAe,GAC9C;AAAC,KACX;AAAA,IACA,UAAA;AAAA,IACA,UAAA,EAAY,EAAA;AAAA,IACZ,QAAA,EAAU,UAAU,QAAQ,CAAA;AAAA,IAC5B,GAAI,MAAM,IAAA,KAAS,MAAA,GAAY,EAAE,IAAA,EAAM,KAAA,CAAM,IAAA,EAAK,GAAI,EAAC;AAAA,IACvD,UAAA,EAAY,GAAA;AAAA,IACZ,YAAY,KAAA,CAAM,SAAA,GAAY,KAAA,CAAM,SAAA,CAAU,aAAY,GAAI,IAAA;AAAA,IAC9D,OAAA,EAAS,MAAM,OAAA,IAAW,IAAA;AAAA,IAC1B,GAAA,EAAK,EAAE,GAAA,EAAK,QAAA,EAAU,QAAQ,KAAA,CAAM,MAAA,CAAO,OAAA,EAAS,KAAA,EAAO,EAAA;AAAG,GAClE;AAEA,EAAA,MAAM,OAAA,GAAU,eAAe,QAAQ,CAAA;AACvC,EAAA,MAAM,aAAa,aAAA,CAAc,WAAA,EAAa,QAAA,EAAU,KAAA,CAAM,SAAS,OAAO,CAAA;AAC9E,EAAA,OAAA,CAAQ,WAAW,CAAA;AAEnB,EAAA,MAAM,SAAuB,EAAE,GAAG,UAAU,UAAA,EAAY,YAAA,CAAa,UAAU,CAAA,EAAE;AACjF,EAAA,MAAM,eAAA,GAAkB,kBAAkB,MAAM,CAAA;AAChD,EAAA,MAAM,UAAA,GAAa,UAAU,eAAe,CAAA;AAE5C,EAAA,MAAM,YAAA,GAAe,MAAM,KAAA,CAAM,MAAA,CAAO,YAAY,UAAU,CAAA;AAE9D,EAAA,OAAO;AAAA,IACH,GAAG,MAAA;AAAA,IACH,EAAA,EAAI,UAAA;AAAA,IACJ,GAAA,EAAK,EAAE,GAAA,EAAK,QAAA,EAAU,QAAQ,KAAA,CAAM,MAAA,CAAO,OAAA,EAAS,KAAA,EAAO,YAAA;AAAa,GAC5E;AACJ;AAEA,eAAsB,OAAO,KAAA,EAA2C;AACpE,EAAA,MAAM,MAAM,KAAA,CAAM,QAAA;AAGlB,EAAA,IAAI,IAAI,UAAA,EAAY;AAChB,IAAA,MAAM,MAAM,KAAA,CAAM,GAAA,GAAM,MAAM,GAAA,EAAI,uBAAQ,IAAA,EAAK;AAC/C,IAAA,IAAI,GAAA,CAAI,SAAQ,GAAI,IAAI,KAAK,GAAA,CAAI,UAAU,CAAA,CAAE,OAAA,EAAQ,EAAG;AACpD,MAAA,MAAM,SAAA,CAAU,aAAa,qBAAqB,CAAA;AAAA,IACtD;AAAA,EACJ;AAGA,EAAA,MAAM,OAAA,GAAU,kBAAkB,GAAG,CAAA;AACrC,EAAA,MAAM,KAAA,GAAQ,UAAU,OAAO,CAAA;AAC/B,EAAA,IAAI,KAAA,KAAU,IAAI,EAAA,EAAI;AAClB,IAAA,MAAM,SAAA,CAAU,aAAa,sBAAsB,CAAA;AAAA,EACvD;AAGA,EAAA,IAAI,CAAC,MAAM,sBAAA,EAAwB;AAC/B,IAAA,IAAI,CAAC,MAAM,YAAA,EAAc;AACrB,MAAA,MAAM,SAAA,CAAU,aAAa,6BAA6B,CAAA;AAAA,IAC9D;AACA,IAAA,MAAM,EAAA,GAAK,MAAM,KAAA,CAAM,YAAA,CAAa,GAAA,CAAI,EAAA,EAAI,GAAA,CAAI,GAAA,CAAI,KAAA,EAAO,GAAA,CAAI,GAAA,CAAI,MAAM,CAAA;AACzE,IAAA,IAAI,CAAC,EAAA,EAAI,MAAM,SAAA,CAAU,aAAa,iCAAiC,CAAA;AAAA,EAC3E;AAGA,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,UAAA,CAAW,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,SAAA,KAAc,KAAA,CAAM,MAAA,CAAO,SAAS,CAAA;AAC9E,EAAA,IAAI,CAAC,IAAA,EAAM,MAAM,SAAA,CAAU,mBAAmB,qCAAqC,CAAA;AAGnF,EAAA,MAAM,OAAA,GAAU,eAAe,EAAE,GAAG,KAAK,EAAA,EAAI,GAAA,CAAI,IAAI,CAAA;AACrD,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,IAAA,CAAK,MAAM,CAAA;AACpC,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,KAAA,CAAM,MAAA,CAAO,WAAW,MAAM,CAAA;AAC1D,EAAA,MAAM,QAAA,GAAW,SAAA,CAAU,GAAA,CAAI,QAAQ,CAAA;AACvC,EAAA,MAAM,GAAA,GAAM,UAAA;AAAA,IACR,MAAA;AAAA,IACA,QAAA;AAAA,IACAA,UAAAA,CAAW,iBAAA,GAAoB,IAAA,CAAK,SAAS,CAAA;AAAA,IAC7C;AAAA,GACJ;AACA,EAAA,MAAM,SAAA,GAAY,SAAA,CAAU,IAAA,CAAK,SAAS,CAAA;AAC1C,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,IAAA,CAAK,WAAW,CAAA;AAE7C,EAAA,IAAI,WAAA;AACJ,EAAA,IAAI;AACA,IAAA,WAAA,GAAc,cAAc,GAAA,EAAK,SAAA,EAAW,SAASA,UAAAA,CAAW,IAAA,CAAK,SAAS,CAAC,CAAA;AAAA,EACnF,SAAS,CAAA,EAAG;AACR,IAAA,MAAM,SAAA,CAAU,aAAa,+BAA+B,CAAA;AAAA,EAChE,CAAA,SAAE;AACE,IAAA,OAAA,CAAQ,MAAM,CAAA;AACd,IAAA,OAAA,CAAQ,GAAG,CAAA;AAAA,EACf;AAGA,EAAA,MAAM,UAAA,GAAa,YAAA,CAAa,GAAA,CAAI,UAAU,CAAA;AAC9C,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI;AACA,IAAA,OAAA,GAAU,aAAA,CAAc,WAAA,EAAa,QAAA,EAAU,UAAA,EAAY,OAAO,CAAA;AAAA,EACtE,SAAS,CAAA,EAAG;AACR,IAAA,OAAA,CAAQ,WAAW,CAAA;AACnB,IAAA,MAAM,SAAA,CAAU,aAAa,8BAA8B,CAAA;AAAA,EAC/D;AACA,EAAA,OAAA,CAAQ,WAAW,CAAA;AAEnB,EAAA,OAAO;AAAA,IACH,OAAA;AAAA,IACA,YAAY,GAAA,CAAI,EAAA;AAAA,IAChB,QAAQ,GAAA,CAAI,IAAA;AAAA,IACZ,iBAAiB,IAAA,CAAK;AAAA,GAC1B;AACJ;AAEO,IAAM,SAAA,GAAN,cAAwB,KAAA,CAAM;AAAA,EAEjC,WAAA,CAAY,MAAc,OAAA,EAAiB;AACvC,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,WAAA;AAAA,EAChB;AACJ;AAEA,SAAS,SAAA,CAAU,MAAc,OAAA,EAA4B;AACzD,EAAA,OAAO,IAAI,SAAA,CAAU,IAAA,EAAM,OAAO,CAAA;AACtC","file":"index.mjs","sourcesContent":["// Wire types for OC Lock v2 envelopes. See SPEC.md §4.\n\nexport const ENVELOPE_VERSION = 2 as const;\n\nexport type EnvelopeKind = 'identity' | 'payment';\n\nexport interface EnvelopeAlg {\n kem: 'x25519';\n aead: 'aes-256-gcm';\n kdf: 'hkdf-sha256';\n}\n\nexport interface EnvelopeFrom {\n address: string;\n attestation_id?: string;\n}\n\nexport interface EnvelopeRecipient {\n address: string;\n device_id: string;\n device_pk: string; // 32-byte hex X25519 pubkey\n eph_pk: string; // 32-byte hex X25519 ephemeral pubkey\n wrapped_key: string; // base64url(aead(content_key, kek, nonce_kek))\n nonce_kek: string; // 12-byte hex\n}\n\nexport interface EnvelopePayment {\n amount_sats: number;\n address: string;\n confirmations: number;\n relay: string;\n}\n\nexport interface EnvelopeSignature {\n alg: 'bip322';\n pubkey: string; // sender btc address\n value: string; // base64 BIP-322\n}\n\nexport interface LockEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: EnvelopeKind;\n id: string; // 32-byte hex\n alg: EnvelopeAlg;\n from: EnvelopeFrom;\n recipients: EnvelopeRecipient[];\n ciphertext: string; // base64url\n nonce_ct: string; // 12-byte hex\n hint?: string;\n created_at: string; // iso8601 utc\n expires_at: string | null;\n payment: EnvelopePayment | null;\n sig: EnvelopeSignature;\n}\n\n// Inputs\n\nexport interface DeviceRecord {\n address: string;\n device_id: string;\n device_pk: string; // hex\n}\n\nexport interface SealInput {\n payload: Uint8Array;\n sender: {\n address: string;\n attestation_id?: string;\n /**\n * Returns a BIP-322 base64 signature of the given message bytes as UTF-8.\n * Exactly one call per seal (over the envelope id).\n */\n signMessage: (msg: string) => Promise<string>;\n };\n recipients: DeviceRecord[];\n kind?: EnvelopeKind;\n hint?: string;\n expiresAt?: Date | null;\n payment?: EnvelopePayment | null;\n}\n\nexport interface UnsealInput {\n envelope: LockEnvelope;\n device: {\n device_id: string;\n /** 32-byte X25519 private key for this device. */\n secretKey: Uint8Array;\n };\n /**\n * Called to verify the sender's BIP-322 signature. Returns true if valid.\n * The verifier is injected because verification libraries differ between\n * Node and browser contexts; consumers plug their own.\n */\n verifyBip322?: (msg: string, signatureB64: string, address: string) => Promise<boolean>;\n /** Skip sender signature verification. Default false; true only for self-seals. */\n skipSenderVerification?: boolean;\n /** Current time; defaults to Date.now(). Used for expiry enforcement. */\n now?: () => Date;\n}\n\nexport interface UnsealResult {\n payload: Uint8Array;\n envelopeId: string;\n sender: EnvelopeFrom;\n matchedDeviceId: string;\n}\n","// RFC 8785 JSON Canonicalization Scheme with one addition: within the\n// envelope, `recipients` entries are sorted by `device_id` ascending before\n// canonicalization. See SPEC.md §5.\n//\n// We implement a small, self-contained canonicalizer. Inputs are plain JSON\n// values (objects, arrays, strings, numbers, booleans, null). Undefined and\n// functions are rejected.\n\nimport { utf8Encode } from '@orangecheck/lock-crypto';\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nconst JSON_RECIPIENTS_SORT_PATH = ['recipients'] as const;\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value, []);\n}\n\nexport function canonicalBytes(value: JsonValue): Uint8Array {\n return utf8Encode(canonicalize(value) + '\\n');\n}\n\nfunction encode(v: JsonValue, path: string[]): 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 let items = v;\n // Sort recipients by device_id when we're at path ['recipients'].\n if (\n path.length === JSON_RECIPIENTS_SORT_PATH.length &&\n path.every((p, i) => p === JSON_RECIPIENTS_SORT_PATH[i]) &&\n items.every(\n (it) =>\n it &&\n typeof it === 'object' &&\n !Array.isArray(it) &&\n typeof (it as Record<string, JsonValue>).device_id === 'string'\n )\n ) {\n items = [...items].sort((a, b) => {\n const aid = (a as Record<string, JsonValue>).device_id as string;\n const bid = (b as Record<string, JsonValue>).device_id as string;\n return aid < bid ? -1 : aid > bid ? 1 : 0;\n });\n }\n for (let i = 0; i < items.length; i++) {\n parts.push(encode(items[i]!, path.concat(String(i))));\n }\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, path.concat(k)));\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 // Follow JSON.stringify for non-integers; it produces canonical-ish output\n // that matches IEEE 754 round-tripping for our use case (we never emit\n // floats ourselves).\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","// Seal and unseal OC Lock envelopes. See SPEC.md §4.\n\nimport {\n aesGcmDecrypt,\n aesGcmEncrypt,\n b64urlDecode,\n b64urlEncode,\n generateX25519KeyPair,\n hexDecode,\n hexEncode,\n hkdfSha256,\n randomBytesN,\n sha256Bytes,\n utf8Encode,\n x25519Shared,\n zeroize,\n} from '@orangecheck/lock-crypto';\n\nimport { canonicalBytes, canonicalize, type JsonValue } from './canonical.js';\nimport {\n ENVELOPE_VERSION,\n type EnvelopeAlg,\n type EnvelopeKind,\n type EnvelopeRecipient,\n type LockEnvelope,\n type SealInput,\n type UnsealInput,\n type UnsealResult,\n} from './types.js';\n\nconst DEFAULT_ALG: EnvelopeAlg = {\n kem: 'x25519',\n aead: 'aes-256-gcm',\n kdf: 'hkdf-sha256',\n};\n\nfunction envelopeToJson(env: LockEnvelope): JsonValue {\n return env as unknown as JsonValue;\n}\n\n/**\n * Compute the draft envelope id used as AAD during AEAD operations. This is\n * the canonical envelope with `ciphertext`, `sig`, and each\n * `recipients[*].wrapped_key` elided per SPEC §4.2 step 3.\n */\nfunction computeDraftId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n ciphertext: '',\n sig: { alg: 'bip322', pubkey: env.from.address, value: '' },\n recipients: env.recipients.map((r) => ({ ...r, wrapped_key: '' })),\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\n/**\n * Compute the final envelope id: SHA-256 of the canonical envelope with\n * `sig.value` stripped to empty. The `id` field itself is excluded from the\n * hash input (we set it to empty before hashing).\n */\nfunction computeEnvelopeId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n sig: { alg: env.sig.alg, pubkey: env.sig.pubkey, value: '' },\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\nexport async function seal(input: SealInput): Promise<LockEnvelope> {\n const kind: EnvelopeKind = input.kind ?? (input.payment ? 'payment' : 'identity');\n const now = new Date().toISOString();\n const nonce_ct = randomBytesN(12);\n\n const content_key = randomBytesN(32);\n\n // Build recipients with wrapped keys.\n const recipients: EnvelopeRecipient[] = [];\n for (const r of input.recipients) {\n const device_pk_bytes = hexDecode(r.device_pk);\n const ephemeral = generateX25519KeyPair();\n const shared = x25519Shared(ephemeral.secret, device_pk_bytes);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + r.device_id),\n 32\n );\n const nonce_kek = randomBytesN(12);\n const wrapped = aesGcmEncrypt(kek, nonce_kek, content_key, utf8Encode(r.device_id));\n zeroize(ephemeral.secret);\n zeroize(shared);\n zeroize(kek);\n recipients.push({\n address: r.address,\n device_id: r.device_id,\n device_pk: r.device_pk,\n eph_pk: hexEncode(ephemeral.public),\n wrapped_key: b64urlEncode(wrapped),\n nonce_kek: hexEncode(nonce_kek),\n });\n }\n\n // Draft envelope (id, sig, ciphertext empty) for AAD computation.\n const draftEnv: LockEnvelope = {\n v: ENVELOPE_VERSION,\n kind,\n id: '',\n alg: DEFAULT_ALG,\n from: {\n address: input.sender.address,\n ...(input.sender.attestation_id\n ? { attestation_id: input.sender.attestation_id }\n : {}),\n },\n recipients,\n ciphertext: '',\n nonce_ct: hexEncode(nonce_ct),\n ...(input.hint !== undefined ? { hint: input.hint } : {}),\n created_at: now,\n expires_at: input.expiresAt ? input.expiresAt.toISOString() : null,\n payment: input.payment ?? null,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: '' },\n };\n\n const draftId = computeDraftId(draftEnv);\n const ciphertext = aesGcmEncrypt(content_key, nonce_ct, input.payload, draftId);\n zeroize(content_key);\n\n const withCt: LockEnvelope = { ...draftEnv, ciphertext: b64urlEncode(ciphertext) };\n const envelopeIdBytes = computeEnvelopeId(withCt);\n const envelopeId = hexEncode(envelopeIdBytes);\n\n const signatureB64 = await input.sender.signMessage(envelopeId);\n\n return {\n ...withCt,\n id: envelopeId,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: signatureB64 },\n };\n}\n\nexport async function unseal(input: UnsealInput): Promise<UnsealResult> {\n const env = input.envelope;\n\n // Expiry.\n if (env.expires_at) {\n const now = input.now ? input.now() : new Date();\n if (now.getTime() > new Date(env.expires_at).getTime()) {\n throw makeError('E_EXPIRED', 'envelope is expired');\n }\n }\n\n // Recompute envelope id.\n const idBytes = computeEnvelopeId(env);\n const idHex = hexEncode(idBytes);\n if (idHex !== env.id) {\n throw makeError('E_BAD_SIG', 'envelope id mismatch');\n }\n\n // Verify sender signature unless caller opts out (self-seal).\n if (!input.skipSenderVerification) {\n if (!input.verifyBip322) {\n throw makeError('E_BAD_SIG', 'no bip322 verifier supplied');\n }\n const ok = await input.verifyBip322(env.id, env.sig.value, env.sig.pubkey);\n if (!ok) throw makeError('E_BAD_SIG', 'sender signature did not verify');\n }\n\n // Find our recipient entry.\n const mine = env.recipients.find((r) => r.device_id === input.device.device_id);\n if (!mine) throw makeError('E_NOT_ADDRESSED', 'no matching device_id in recipients');\n\n // Unwrap content key.\n const draftId = computeDraftId({ ...env, id: env.id });\n const eph_pk = hexDecode(mine.eph_pk);\n const shared = x25519Shared(input.device.secretKey, eph_pk);\n const nonce_ct = hexDecode(env.nonce_ct);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + mine.device_id),\n 32\n );\n const nonce_kek = hexDecode(mine.nonce_kek);\n const wrapped = b64urlDecode(mine.wrapped_key);\n\n let content_key: Uint8Array;\n try {\n content_key = aesGcmDecrypt(kek, nonce_kek, wrapped, utf8Encode(mine.device_id));\n } catch (e) {\n throw makeError('E_BAD_TAG', 'wrapped key failed to decrypt');\n } finally {\n zeroize(shared);\n zeroize(kek);\n }\n\n // Decrypt ciphertext.\n const ciphertext = b64urlDecode(env.ciphertext);\n let payload: Uint8Array;\n try {\n payload = aesGcmDecrypt(content_key, nonce_ct, ciphertext, draftId);\n } catch (e) {\n zeroize(content_key);\n throw makeError('E_BAD_TAG', 'ciphertext failed to decrypt');\n }\n zeroize(content_key);\n\n return {\n payload,\n envelopeId: env.id,\n sender: env.from,\n matchedDeviceId: mine.device_id,\n };\n}\n\nexport class LockError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.code = code;\n this.name = 'LockError';\n }\n}\n\nfunction makeError(code: string, message: string): LockError {\n return new LockError(code, message);\n}\n\nexport { canonicalize, canonicalBytes };\n"]}
{"version":3,"sources":["../src/types.ts","../src/canonical.ts","../src/seal.ts"],"names":["utf8Encode"],"mappings":";;;AAEO,IAAM,gBAAA,GAAmB;ACgBhC,IAAM,yBAAA,GAA4B,CAAC,YAAY,CAAA;AAExC,SAAS,aAAa,KAAA,EAA0B;AACnD,EAAA,OAAO,MAAA,CAAO,KAAA,EAAO,EAAE,CAAA;AAC3B;AAEO,SAAS,eAAe,KAAA,EAA8B;AACzD,EAAA,OAAO,UAAA,CAAW,YAAA,CAAa,KAAK,CAAA,GAAI,IAAI,CAAA;AAChD;AAEA,SAAS,MAAA,CAAO,GAAc,IAAA,EAAwB;AAClD,EAAA,IAAI,CAAA,KAAM,MAAM,OAAO,MAAA;AACvB,EAAA,IAAI,OAAO,CAAA,KAAM,SAAA,EAAW,OAAO,IAAI,MAAA,GAAS,OAAA;AAChD,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,aAAa,CAAC,CAAA;AAChD,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,aAAa,CAAC,CAAA;AAChD,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG;AAClB,IAAA,MAAM,QAAkB,EAAC;AACzB,IAAA,IAAI,KAAA,GAAQ,CAAA;AAEZ,IAAA,IACI,IAAA,CAAK,MAAA,KAAW,yBAAA,CAA0B,MAAA,IAC1C,KAAK,KAAA,CAAM,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,KAAM,yBAAA,CAA0B,CAAC,CAAC,KACvD,KAAA,CAAM,KAAA;AAAA,MACF,CAAC,EAAA,KACG,EAAA,IACA,OAAO,EAAA,KAAO,QAAA,IACd,CAAC,KAAA,CAAM,OAAA,CAAQ,EAAE,CAAA,IACjB,OAAQ,GAAiC,SAAA,KAAc;AAAA,KAC/D,EACF;AACE,MAAA,KAAA,GAAQ,CAAC,GAAG,KAAK,EAAE,IAAA,CAAK,CAAC,GAAG,CAAA,KAAM;AAC9B,QAAA,MAAM,MAAO,CAAA,CAAgC,SAAA;AAC7C,QAAA,MAAM,MAAO,CAAA,CAAgC,SAAA;AAC7C,QAAA,OAAO,GAAA,GAAM,GAAA,GAAM,EAAA,GAAK,GAAA,GAAM,MAAM,CAAA,GAAI,CAAA;AAAA,MAC5C,CAAC,CAAA;AAAA,IACL;AACA,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACnC,MAAA,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,CAAC,CAAA,EAAI,IAAA,CAAK,MAAA,CAAO,MAAA,CAAO,CAAC,CAAC,CAAC,CAAC,CAAA;AAAA,IACxD;AACA,IAAA,OAAO,GAAA,GAAM,KAAA,CAAM,IAAA,CAAK,GAAG,CAAA,GAAI,GAAA;AAAA,EACnC;AACA,EAAA,IAAI,OAAO,MAAM,QAAA,EAAU;AACvB,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,IAAA,CAAK,CAAC,EAAE,IAAA,EAAK;AACjC,IAAA,MAAM,QAAkB,EAAC;AACzB,IAAA,KAAA,MAAW,KAAK,IAAA,EAAM;AAClB,MAAA,MAAM,KAAA,GAAQ,EAAE,CAAC,CAAA;AACjB,MAAA,IAAI,UAAU,MAAA,EAAW;AACzB,MAAA,KAAA,CAAM,IAAA,CAAK,YAAA,CAAa,CAAC,CAAA,GAAI,GAAA,GAAM,MAAA,CAAO,KAAA,EAAoB,IAAA,CAAK,MAAA,CAAO,CAAC,CAAC,CAAC,CAAA;AAAA,IACjF;AACA,IAAA,OAAO,GAAA,GAAM,KAAA,CAAM,IAAA,CAAK,GAAG,CAAA,GAAI,GAAA;AAAA,EACnC;AACA,EAAA,MAAM,IAAI,KAAA,CAAM,oCAAA,GAAuC,OAAO,CAAC,CAAA;AACnE;AAEA,SAAS,aAAa,CAAA,EAAmB;AACrC,EAAA,IAAI,CAAC,OAAO,QAAA,CAAS,CAAC,GAAG,MAAM,IAAI,MAAM,4CAA4C,CAAA;AACrF,EAAA,IAAI,MAAA,CAAO,EAAA,CAAG,CAAA,EAAG,EAAE,GAAG,OAAO,GAAA;AAC7B,EAAA,IAAI,MAAA,CAAO,UAAU,CAAC,CAAA,IAAK,KAAK,GAAA,CAAI,CAAC,IAAI,IAAA,EAAM;AAC3C,IAAA,OAAO,OAAO,CAAC,CAAA;AAAA,EACnB;AAIA,EAAA,OAAO,IAAA,CAAK,UAAU,CAAC,CAAA;AAC3B;AAEA,IAAM,YAAA,GAAuC;AAAA,EACzC,IAAA,EAAM,MAAA;AAAA,EACN,GAAA,EAAK,KAAA;AAAA,EACL,IAAA,EAAM,KAAA;AAAA,EACN,IAAA,EAAM,KAAA;AAAA,EACN,IAAA,EAAM,KAAA;AAAA,EACN,IAAA,EAAM,KAAA;AAAA,EACN,GAAA,EAAM;AACV,CAAA;AAEA,SAAS,aAAa,CAAA,EAAmB;AACrC,EAAA,IAAI,GAAA,GAAM,GAAA;AACV,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,QAAQ,CAAA,EAAA,EAAK;AAC/B,IAAA,MAAM,EAAA,GAAK,EAAE,CAAC,CAAA;AACd,IAAA,MAAM,IAAA,GAAO,EAAA,CAAG,UAAA,CAAW,CAAC,CAAA;AAC5B,IAAA,IAAI,YAAA,CAAa,EAAE,CAAA,EAAG;AAClB,MAAA,GAAA,IAAO,aAAa,EAAE,CAAA;AAAA,IAC1B,CAAA,MAAA,IAAW,OAAO,EAAA,EAAM;AACpB,MAAA,GAAA,IAAO,QAAQ,IAAA,CAAK,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AAAA,IACpD,CAAA,MAAO;AACH,MAAA,GAAA,IAAO,EAAA;AAAA,IACX;AAAA,EACJ;AACA,EAAA,GAAA,IAAO,GAAA;AACP,EAAA,OAAO,GAAA;AACX;AC/EA,IAAM,WAAA,GAA2B;AAAA,EAC7B,GAAA,EAAK,QAAA;AAAA,EACL,IAAA,EAAM,aAAA;AAAA,EACN,GAAA,EAAK;AACT,CAAA;AAEA,SAAS,eAAe,GAAA,EAA8B;AAClD,EAAA,OAAO,GAAA;AACX;AAOA,SAAS,eAAe,GAAA,EAA+B;AACnD,EAAA,MAAM,KAAA,GAAsB;AAAA,IACxB,GAAG,GAAA;AAAA,IACH,EAAA,EAAI,EAAA;AAAA,IACJ,UAAA,EAAY,EAAA;AAAA,IACZ,GAAA,EAAK,EAAE,GAAA,EAAK,QAAA,EAAU,QAAQ,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,KAAA,EAAO,EAAA,EAAG;AAAA,IAC1D,UAAA,EAAY,GAAA,CAAI,UAAA,CAAW,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,GAAG,CAAA,EAAG,WAAA,EAAa,EAAA,EAAG,CAAE;AAAA,GACrE;AACA,EAAA,MAAM,KAAA,GAAQ,cAAA,CAAe,cAAA,CAAe,KAAK,CAAC,CAAA;AAClD,EAAA,OAAO,YAAY,KAAK,CAAA;AAC5B;AAOA,SAAS,kBAAkB,GAAA,EAA+B;AACtD,EAAA,MAAM,KAAA,GAAsB;AAAA,IACxB,GAAG,GAAA;AAAA,IACH,EAAA,EAAI,EAAA;AAAA,IACJ,GAAA,EAAK,EAAE,GAAA,EAAK,GAAA,CAAI,GAAA,CAAI,GAAA,EAAK,MAAA,EAAQ,GAAA,CAAI,GAAA,CAAI,MAAA,EAAQ,KAAA,EAAO,EAAA;AAAG,GAC/D;AACA,EAAA,MAAM,KAAA,GAAQ,cAAA,CAAe,cAAA,CAAe,KAAK,CAAC,CAAA;AAClD,EAAA,OAAO,YAAY,KAAK,CAAA;AAC5B;AAEA,eAAsB,KAAK,KAAA,EAAyC;AAKhE,EAAA,IAAI,CAAC,KAAA,CAAM,UAAA,IAAc,KAAA,CAAM,UAAA,CAAW,WAAW,CAAA,EAAG;AACpD,IAAA,MAAM,IAAI,SAAA;AAAA,MACN,iBAAA;AAAA,MACA;AAAA,KACJ;AAAA,EACJ;AACA,EAAA,MAAM,IAAA,GAAqB,KAAA,CAAM,IAAA,KAAS,KAAA,CAAM,UAAU,SAAA,GAAY,UAAA,CAAA;AACtE,EAAA,MAAM,GAAA,GAAA,iBAAM,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AACnC,EAAA,MAAM,QAAA,GAAW,aAAa,EAAE,CAAA;AAEhC,EAAA,MAAM,WAAA,GAAc,aAAa,EAAE,CAAA;AAGnC,EAAA,MAAM,aAAkC,EAAC;AACzC,EAAA,KAAA,MAAW,CAAA,IAAK,MAAM,UAAA,EAAY;AAC9B,IAAA,MAAM,eAAA,GAAkB,SAAA,CAAU,CAAA,CAAE,SAAS,CAAA;AAC7C,IAAA,MAAM,YAAY,qBAAA,EAAsB;AACxC,IAAA,MAAM,MAAA,GAAS,YAAA,CAAa,SAAA,CAAU,MAAA,EAAQ,eAAe,CAAA;AAC7D,IAAA,MAAM,GAAA,GAAM,UAAA;AAAA,MACR,MAAA;AAAA,MACA,QAAA;AAAA,MACAA,UAAAA,CAAW,iBAAA,GAAoB,CAAA,CAAE,SAAS,CAAA;AAAA,MAC1C;AAAA,KACJ;AACA,IAAA,MAAM,SAAA,GAAY,aAAa,EAAE,CAAA;AACjC,IAAA,MAAM,OAAA,GAAU,cAAc,GAAA,EAAK,SAAA,EAAW,aAAaA,UAAAA,CAAW,CAAA,CAAE,SAAS,CAAC,CAAA;AAClF,IAAA,OAAA,CAAQ,UAAU,MAAM,CAAA;AACxB,IAAA,OAAA,CAAQ,MAAM,CAAA;AACd,IAAA,OAAA,CAAQ,GAAG,CAAA;AACX,IAAA,UAAA,CAAW,IAAA,CAAK;AAAA,MACZ,SAAS,CAAA,CAAE,OAAA;AAAA,MACX,WAAW,CAAA,CAAE,SAAA;AAAA,MACb,WAAW,CAAA,CAAE,SAAA;AAAA,MACb,MAAA,EAAQ,SAAA,CAAU,SAAA,CAAU,MAAM,CAAA;AAAA,MAClC,WAAA,EAAa,aAAa,OAAO,CAAA;AAAA,MACjC,SAAA,EAAW,UAAU,SAAS;AAAA,KACjC,CAAA;AAAA,EACL;AAGA,EAAA,MAAM,QAAA,GAAyB;AAAA,IAC3B,CAAA,EAAG,gBAAA;AAAA,IACH,IAAA;AAAA,IACA,EAAA,EAAI,EAAA;AAAA,IACJ,GAAA,EAAK,WAAA;AAAA,IACL,IAAA,EAAM;AAAA,MACF,OAAA,EAAS,MAAM,MAAA,CAAO,OAAA;AAAA,MACtB,GAAI,KAAA,CAAM,MAAA,CAAO,cAAA,GACX,EAAE,gBAAgB,KAAA,CAAM,MAAA,CAAO,cAAA,EAAe,GAC9C;AAAC,KACX;AAAA,IACA,UAAA;AAAA,IACA,UAAA,EAAY,EAAA;AAAA,IACZ,QAAA,EAAU,UAAU,QAAQ,CAAA;AAAA,IAC5B,GAAI,MAAM,IAAA,KAAS,MAAA,GAAY,EAAE,IAAA,EAAM,KAAA,CAAM,IAAA,EAAK,GAAI,EAAC;AAAA,IACvD,UAAA,EAAY,GAAA;AAAA,IACZ,YAAY,KAAA,CAAM,SAAA,GAAY,KAAA,CAAM,SAAA,CAAU,aAAY,GAAI,IAAA;AAAA,IAC9D,OAAA,EAAS,MAAM,OAAA,IAAW,IAAA;AAAA,IAC1B,GAAA,EAAK,EAAE,GAAA,EAAK,QAAA,EAAU,QAAQ,KAAA,CAAM,MAAA,CAAO,OAAA,EAAS,KAAA,EAAO,EAAA;AAAG,GAClE;AAEA,EAAA,MAAM,OAAA,GAAU,eAAe,QAAQ,CAAA;AACvC,EAAA,MAAM,aAAa,aAAA,CAAc,WAAA,EAAa,QAAA,EAAU,KAAA,CAAM,SAAS,OAAO,CAAA;AAC9E,EAAA,OAAA,CAAQ,WAAW,CAAA;AAEnB,EAAA,MAAM,SAAuB,EAAE,GAAG,UAAU,UAAA,EAAY,YAAA,CAAa,UAAU,CAAA,EAAE;AACjF,EAAA,MAAM,eAAA,GAAkB,kBAAkB,MAAM,CAAA;AAChD,EAAA,MAAM,UAAA,GAAa,UAAU,eAAe,CAAA;AAE5C,EAAA,MAAM,YAAA,GAAe,MAAM,KAAA,CAAM,MAAA,CAAO,YAAY,UAAU,CAAA;AAE9D,EAAA,OAAO;AAAA,IACH,GAAG,MAAA;AAAA,IACH,EAAA,EAAI,UAAA;AAAA,IACJ,GAAA,EAAK,EAAE,GAAA,EAAK,QAAA,EAAU,QAAQ,KAAA,CAAM,MAAA,CAAO,OAAA,EAAS,KAAA,EAAO,YAAA;AAAa,GAC5E;AACJ;AAEA,eAAsB,OAAO,KAAA,EAA2C;AACpE,EAAA,MAAM,MAAM,KAAA,CAAM,QAAA;AAGlB,EAAA,IAAI,IAAI,UAAA,EAAY;AAChB,IAAA,MAAM,MAAM,KAAA,CAAM,GAAA,GAAM,MAAM,GAAA,EAAI,uBAAQ,IAAA,EAAK;AAC/C,IAAA,IAAI,GAAA,CAAI,SAAQ,GAAI,IAAI,KAAK,GAAA,CAAI,UAAU,CAAA,CAAE,OAAA,EAAQ,EAAG;AACpD,MAAA,MAAM,SAAA,CAAU,aAAa,qBAAqB,CAAA;AAAA,IACtD;AAAA,EACJ;AAGA,EAAA,MAAM,OAAA,GAAU,kBAAkB,GAAG,CAAA;AACrC,EAAA,MAAM,KAAA,GAAQ,UAAU,OAAO,CAAA;AAC/B,EAAA,IAAI,KAAA,KAAU,IAAI,EAAA,EAAI;AAClB,IAAA,MAAM,SAAA,CAAU,aAAa,sBAAsB,CAAA;AAAA,EACvD;AAGA,EAAA,IAAI,CAAC,MAAM,sBAAA,EAAwB;AAC/B,IAAA,IAAI,CAAC,MAAM,YAAA,EAAc;AACrB,MAAA,MAAM,SAAA,CAAU,aAAa,6BAA6B,CAAA;AAAA,IAC9D;AACA,IAAA,MAAM,EAAA,GAAK,MAAM,KAAA,CAAM,YAAA,CAAa,GAAA,CAAI,EAAA,EAAI,GAAA,CAAI,GAAA,CAAI,KAAA,EAAO,GAAA,CAAI,GAAA,CAAI,MAAM,CAAA;AACzE,IAAA,IAAI,CAAC,EAAA,EAAI,MAAM,SAAA,CAAU,aAAa,iCAAiC,CAAA;AAAA,EAC3E;AAGA,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,UAAA,CAAW,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,SAAA,KAAc,KAAA,CAAM,MAAA,CAAO,SAAS,CAAA;AAC9E,EAAA,IAAI,CAAC,IAAA,EAAM,MAAM,SAAA,CAAU,mBAAmB,qCAAqC,CAAA;AAGnF,EAAA,MAAM,OAAA,GAAU,eAAe,EAAE,GAAG,KAAK,EAAA,EAAI,GAAA,CAAI,IAAI,CAAA;AACrD,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,IAAA,CAAK,MAAM,CAAA;AACpC,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,KAAA,CAAM,MAAA,CAAO,WAAW,MAAM,CAAA;AAC1D,EAAA,MAAM,QAAA,GAAW,SAAA,CAAU,GAAA,CAAI,QAAQ,CAAA;AACvC,EAAA,MAAM,GAAA,GAAM,UAAA;AAAA,IACR,MAAA;AAAA,IACA,QAAA;AAAA,IACAA,UAAAA,CAAW,iBAAA,GAAoB,IAAA,CAAK,SAAS,CAAA;AAAA,IAC7C;AAAA,GACJ;AACA,EAAA,MAAM,SAAA,GAAY,SAAA,CAAU,IAAA,CAAK,SAAS,CAAA;AAC1C,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,IAAA,CAAK,WAAW,CAAA;AAE7C,EAAA,IAAI,WAAA;AACJ,EAAA,IAAI;AACA,IAAA,WAAA,GAAc,cAAc,GAAA,EAAK,SAAA,EAAW,SAASA,UAAAA,CAAW,IAAA,CAAK,SAAS,CAAC,CAAA;AAAA,EACnF,SAAS,CAAA,EAAG;AACR,IAAA,MAAM,SAAA,CAAU,aAAa,+BAA+B,CAAA;AAAA,EAChE,CAAA,SAAE;AACE,IAAA,OAAA,CAAQ,MAAM,CAAA;AACd,IAAA,OAAA,CAAQ,GAAG,CAAA;AAAA,EACf;AAGA,EAAA,MAAM,UAAA,GAAa,YAAA,CAAa,GAAA,CAAI,UAAU,CAAA;AAC9C,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI;AACA,IAAA,OAAA,GAAU,aAAA,CAAc,WAAA,EAAa,QAAA,EAAU,UAAA,EAAY,OAAO,CAAA;AAAA,EACtE,SAAS,CAAA,EAAG;AACR,IAAA,OAAA,CAAQ,WAAW,CAAA;AACnB,IAAA,MAAM,SAAA,CAAU,aAAa,8BAA8B,CAAA;AAAA,EAC/D;AACA,EAAA,OAAA,CAAQ,WAAW,CAAA;AAEnB,EAAA,OAAO;AAAA,IACH,OAAA;AAAA,IACA,YAAY,GAAA,CAAI,EAAA;AAAA,IAChB,QAAQ,GAAA,CAAI,IAAA;AAAA,IACZ,iBAAiB,IAAA,CAAK;AAAA,GAC1B;AACJ;AAEO,IAAM,SAAA,GAAN,cAAwB,KAAA,CAAM;AAAA,EAEjC,WAAA,CAAY,MAAc,OAAA,EAAiB;AACvC,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,WAAA;AAAA,EAChB;AACJ;AAEA,SAAS,SAAA,CAAU,MAAc,OAAA,EAA4B;AACzD,EAAA,OAAO,IAAI,SAAA,CAAU,IAAA,EAAM,OAAO,CAAA;AACtC","file":"index.mjs","sourcesContent":["// Wire types for OC Lock v2 envelopes. See SPEC.md §4.\n\nexport const ENVELOPE_VERSION = 2 as const;\n\nexport type EnvelopeKind = 'identity' | 'payment';\n\nexport interface EnvelopeAlg {\n kem: 'x25519';\n aead: 'aes-256-gcm';\n kdf: 'hkdf-sha256';\n}\n\nexport interface EnvelopeFrom {\n address: string;\n attestation_id?: string;\n}\n\nexport interface EnvelopeRecipient {\n address: string;\n device_id: string;\n device_pk: string; // 32-byte hex X25519 pubkey\n eph_pk: string; // 32-byte hex X25519 ephemeral pubkey\n wrapped_key: string; // base64url(aead(content_key, kek, nonce_kek))\n nonce_kek: string; // 12-byte hex\n}\n\nexport interface EnvelopePayment {\n amount_sats: number;\n address: string;\n confirmations: number;\n relay: string;\n}\n\nexport interface EnvelopeSignature {\n alg: 'bip322';\n pubkey: string; // sender btc address\n value: string; // base64 BIP-322\n}\n\nexport interface LockEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: EnvelopeKind;\n id: string; // 32-byte hex\n alg: EnvelopeAlg;\n from: EnvelopeFrom;\n recipients: EnvelopeRecipient[];\n ciphertext: string; // base64url\n nonce_ct: string; // 12-byte hex\n hint?: string;\n created_at: string; // iso8601 utc\n expires_at: string | null;\n payment: EnvelopePayment | null;\n sig: EnvelopeSignature;\n}\n\n// Inputs\n\nexport interface DeviceRecord {\n address: string;\n device_id: string;\n device_pk: string; // hex\n}\n\nexport interface SealInput {\n payload: Uint8Array;\n sender: {\n address: string;\n attestation_id?: string;\n /**\n * Returns a BIP-322 base64 signature of the given message bytes as UTF-8.\n * Exactly one call per seal (over the envelope id).\n */\n signMessage: (msg: string) => Promise<string>;\n };\n recipients: DeviceRecord[];\n kind?: EnvelopeKind;\n hint?: string;\n expiresAt?: Date | null;\n payment?: EnvelopePayment | null;\n}\n\nexport interface UnsealInput {\n envelope: LockEnvelope;\n device: {\n device_id: string;\n /** 32-byte X25519 private key for this device. */\n secretKey: Uint8Array;\n };\n /**\n * Called to verify the sender's BIP-322 signature. Returns true if valid.\n * The verifier is injected because verification libraries differ between\n * Node and browser contexts; consumers plug their own.\n */\n verifyBip322?: (msg: string, signatureB64: string, address: string) => Promise<boolean>;\n /** Skip sender signature verification. Default false; true only for self-seals. */\n skipSenderVerification?: boolean;\n /** Current time; defaults to Date.now(). Used for expiry enforcement. */\n now?: () => Date;\n}\n\nexport interface UnsealResult {\n payload: Uint8Array;\n envelopeId: string;\n sender: EnvelopeFrom;\n matchedDeviceId: string;\n}\n","// RFC 8785 JSON Canonicalization Scheme with one addition: within the\n// envelope, `recipients` entries are sorted by `device_id` ascending before\n// canonicalization. See SPEC.md §5.\n//\n// We implement a small, self-contained canonicalizer. Inputs are plain JSON\n// values (objects, arrays, strings, numbers, booleans, null). Undefined and\n// functions are rejected.\n\nimport { utf8Encode } from '@orangecheck/lock-crypto';\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nconst JSON_RECIPIENTS_SORT_PATH = ['recipients'] as const;\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value, []);\n}\n\nexport function canonicalBytes(value: JsonValue): Uint8Array {\n return utf8Encode(canonicalize(value) + '\\n');\n}\n\nfunction encode(v: JsonValue, path: string[]): 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 let items = v;\n // Sort recipients by device_id when we're at path ['recipients'].\n if (\n path.length === JSON_RECIPIENTS_SORT_PATH.length &&\n path.every((p, i) => p === JSON_RECIPIENTS_SORT_PATH[i]) &&\n items.every(\n (it) =>\n it &&\n typeof it === 'object' &&\n !Array.isArray(it) &&\n typeof (it as Record<string, JsonValue>).device_id === 'string'\n )\n ) {\n items = [...items].sort((a, b) => {\n const aid = (a as Record<string, JsonValue>).device_id as string;\n const bid = (b as Record<string, JsonValue>).device_id as string;\n return aid < bid ? -1 : aid > bid ? 1 : 0;\n });\n }\n for (let i = 0; i < items.length; i++) {\n parts.push(encode(items[i]!, path.concat(String(i))));\n }\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, path.concat(k)));\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 // Follow JSON.stringify for non-integers; it produces canonical-ish output\n // that matches IEEE 754 round-tripping for our use case (we never emit\n // floats ourselves).\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","// Seal and unseal OC Lock envelopes. See SPEC.md §4.\n\nimport {\n aesGcmDecrypt,\n aesGcmEncrypt,\n b64urlDecode,\n b64urlEncode,\n generateX25519KeyPair,\n hexDecode,\n hexEncode,\n hkdfSha256,\n randomBytesN,\n sha256Bytes,\n utf8Encode,\n x25519Shared,\n zeroize,\n} from '@orangecheck/lock-crypto';\n\nimport { canonicalBytes, canonicalize, type JsonValue } from './canonical.js';\nimport {\n ENVELOPE_VERSION,\n type EnvelopeAlg,\n type EnvelopeKind,\n type EnvelopeRecipient,\n type LockEnvelope,\n type SealInput,\n type UnsealInput,\n type UnsealResult,\n} from './types.js';\n\nconst DEFAULT_ALG: EnvelopeAlg = {\n kem: 'x25519',\n aead: 'aes-256-gcm',\n kdf: 'hkdf-sha256',\n};\n\nfunction envelopeToJson(env: LockEnvelope): JsonValue {\n return env as unknown as JsonValue;\n}\n\n/**\n * Compute the draft envelope id used as AAD during AEAD operations. This is\n * the canonical envelope with `ciphertext`, `sig`, and each\n * `recipients[*].wrapped_key` elided per SPEC §4.2 step 3.\n */\nfunction computeDraftId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n ciphertext: '',\n sig: { alg: 'bip322', pubkey: env.from.address, value: '' },\n recipients: env.recipients.map((r) => ({ ...r, wrapped_key: '' })),\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\n/**\n * Compute the final envelope id: SHA-256 of the canonical envelope with\n * `sig.value` stripped to empty. The `id` field itself is excluded from the\n * hash input (we set it to empty before hashing).\n */\nfunction computeEnvelopeId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n sig: { alg: env.sig.alg, pubkey: env.sig.pubkey, value: '' },\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\nexport async function seal(input: SealInput): Promise<LockEnvelope> {\n // An envelope with zero recipients is unreadable by anyone — the\n // content key is only wrapped per-recipient, so the caller would\n // produce a silently-unusable ciphertext. Reject upfront with a\n // clear error rather than emitting junk.\n if (!input.recipients || input.recipients.length === 0) {\n throw new LockError(\n 'E_NO_RECIPIENTS',\n 'seal() requires at least one recipient'\n );\n }\n const kind: EnvelopeKind = input.kind ?? (input.payment ? 'payment' : 'identity');\n const now = new Date().toISOString();\n const nonce_ct = randomBytesN(12);\n\n const content_key = randomBytesN(32);\n\n // Build recipients with wrapped keys.\n const recipients: EnvelopeRecipient[] = [];\n for (const r of input.recipients) {\n const device_pk_bytes = hexDecode(r.device_pk);\n const ephemeral = generateX25519KeyPair();\n const shared = x25519Shared(ephemeral.secret, device_pk_bytes);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + r.device_id),\n 32\n );\n const nonce_kek = randomBytesN(12);\n const wrapped = aesGcmEncrypt(kek, nonce_kek, content_key, utf8Encode(r.device_id));\n zeroize(ephemeral.secret);\n zeroize(shared);\n zeroize(kek);\n recipients.push({\n address: r.address,\n device_id: r.device_id,\n device_pk: r.device_pk,\n eph_pk: hexEncode(ephemeral.public),\n wrapped_key: b64urlEncode(wrapped),\n nonce_kek: hexEncode(nonce_kek),\n });\n }\n\n // Draft envelope (id, sig, ciphertext empty) for AAD computation.\n const draftEnv: LockEnvelope = {\n v: ENVELOPE_VERSION,\n kind,\n id: '',\n alg: DEFAULT_ALG,\n from: {\n address: input.sender.address,\n ...(input.sender.attestation_id\n ? { attestation_id: input.sender.attestation_id }\n : {}),\n },\n recipients,\n ciphertext: '',\n nonce_ct: hexEncode(nonce_ct),\n ...(input.hint !== undefined ? { hint: input.hint } : {}),\n created_at: now,\n expires_at: input.expiresAt ? input.expiresAt.toISOString() : null,\n payment: input.payment ?? null,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: '' },\n };\n\n const draftId = computeDraftId(draftEnv);\n const ciphertext = aesGcmEncrypt(content_key, nonce_ct, input.payload, draftId);\n zeroize(content_key);\n\n const withCt: LockEnvelope = { ...draftEnv, ciphertext: b64urlEncode(ciphertext) };\n const envelopeIdBytes = computeEnvelopeId(withCt);\n const envelopeId = hexEncode(envelopeIdBytes);\n\n const signatureB64 = await input.sender.signMessage(envelopeId);\n\n return {\n ...withCt,\n id: envelopeId,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: signatureB64 },\n };\n}\n\nexport async function unseal(input: UnsealInput): Promise<UnsealResult> {\n const env = input.envelope;\n\n // Expiry.\n if (env.expires_at) {\n const now = input.now ? input.now() : new Date();\n if (now.getTime() > new Date(env.expires_at).getTime()) {\n throw makeError('E_EXPIRED', 'envelope is expired');\n }\n }\n\n // Recompute envelope id.\n const idBytes = computeEnvelopeId(env);\n const idHex = hexEncode(idBytes);\n if (idHex !== env.id) {\n throw makeError('E_BAD_SIG', 'envelope id mismatch');\n }\n\n // Verify sender signature unless caller opts out (self-seal).\n if (!input.skipSenderVerification) {\n if (!input.verifyBip322) {\n throw makeError('E_BAD_SIG', 'no bip322 verifier supplied');\n }\n const ok = await input.verifyBip322(env.id, env.sig.value, env.sig.pubkey);\n if (!ok) throw makeError('E_BAD_SIG', 'sender signature did not verify');\n }\n\n // Find our recipient entry.\n const mine = env.recipients.find((r) => r.device_id === input.device.device_id);\n if (!mine) throw makeError('E_NOT_ADDRESSED', 'no matching device_id in recipients');\n\n // Unwrap content key.\n const draftId = computeDraftId({ ...env, id: env.id });\n const eph_pk = hexDecode(mine.eph_pk);\n const shared = x25519Shared(input.device.secretKey, eph_pk);\n const nonce_ct = hexDecode(env.nonce_ct);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + mine.device_id),\n 32\n );\n const nonce_kek = hexDecode(mine.nonce_kek);\n const wrapped = b64urlDecode(mine.wrapped_key);\n\n let content_key: Uint8Array;\n try {\n content_key = aesGcmDecrypt(kek, nonce_kek, wrapped, utf8Encode(mine.device_id));\n } catch (e) {\n throw makeError('E_BAD_TAG', 'wrapped key failed to decrypt');\n } finally {\n zeroize(shared);\n zeroize(kek);\n }\n\n // Decrypt ciphertext.\n const ciphertext = b64urlDecode(env.ciphertext);\n let payload: Uint8Array;\n try {\n payload = aesGcmDecrypt(content_key, nonce_ct, ciphertext, draftId);\n } catch (e) {\n zeroize(content_key);\n throw makeError('E_BAD_TAG', 'ciphertext failed to decrypt');\n }\n zeroize(content_key);\n\n return {\n payload,\n envelopeId: env.id,\n sender: env.from,\n matchedDeviceId: mine.device_id,\n };\n}\n\nexport class LockError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.code = code;\n this.name = 'LockError';\n }\n}\n\nfunction makeError(code: string, message: string): LockError {\n return new LockError(code, message);\n}\n\nexport { canonicalize, canonicalBytes };\n"]}

@@ -114,2 +114,8 @@ 'use strict';

async function seal(input) {
if (!input.recipients || input.recipients.length === 0) {
throw new LockError(
"E_NO_RECIPIENTS",
"seal() requires at least one recipient"
);
}
const kind = input.kind ?? (input.payment ? "payment" : "identity");

@@ -116,0 +122,0 @@ const now = (/* @__PURE__ */ new Date()).toISOString();

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

{"version":3,"sources":["../src/canonical.ts","../src/types.ts","../src/seal.ts"],"names":["utf8Encode","sha256Bytes","randomBytesN","hexDecode","generateX25519KeyPair","x25519Shared","hkdfSha256","aesGcmEncrypt","zeroize","hexEncode","b64urlEncode","b64urlDecode","aesGcmDecrypt"],"mappings":";;;;;AAkBA,IAAM,yBAAA,GAA4B,CAAC,YAAY,CAAA;AAExC,SAAS,aAAa,KAAA,EAA0B;AACnD,EAAA,OAAO,MAAA,CAAO,KAAA,EAAO,EAAE,CAAA;AAC3B;AAEO,SAAS,eAAe,KAAA,EAA8B;AACzD,EAAA,OAAOA,qBAAA,CAAW,YAAA,CAAa,KAAK,CAAA,GAAI,IAAI,CAAA;AAChD;AAEA,SAAS,MAAA,CAAO,GAAc,IAAA,EAAwB;AAClD,EAAA,IAAI,CAAA,KAAM,MAAM,OAAO,MAAA;AACvB,EAAA,IAAI,OAAO,CAAA,KAAM,SAAA,EAAW,OAAO,IAAI,MAAA,GAAS,OAAA;AAChD,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,aAAa,CAAC,CAAA;AAChD,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,aAAa,CAAC,CAAA;AAChD,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG;AAClB,IAAA,MAAM,QAAkB,EAAC;AACzB,IAAA,IAAI,KAAA,GAAQ,CAAA;AAEZ,IAAA,IACI,IAAA,CAAK,MAAA,KAAW,yBAAA,CAA0B,MAAA,IAC1C,KAAK,KAAA,CAAM,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,KAAM,yBAAA,CAA0B,CAAC,CAAC,KACvD,KAAA,CAAM,KAAA;AAAA,MACF,CAAC,EAAA,KACG,EAAA,IACA,OAAO,EAAA,KAAO,QAAA,IACd,CAAC,KAAA,CAAM,OAAA,CAAQ,EAAE,CAAA,IACjB,OAAQ,GAAiC,SAAA,KAAc;AAAA,KAC/D,EACF;AACE,MAAA,KAAA,GAAQ,CAAC,GAAG,KAAK,EAAE,IAAA,CAAK,CAAC,GAAG,CAAA,KAAM;AAC9B,QAAA,MAAM,MAAO,CAAA,CAAgC,SAAA;AAC7C,QAAA,MAAM,MAAO,CAAA,CAAgC,SAAA;AAC7C,QAAA,OAAO,GAAA,GAAM,GAAA,GAAM,EAAA,GAAK,GAAA,GAAM,MAAM,CAAA,GAAI,CAAA;AAAA,MAC5C,CAAC,CAAA;AAAA,IACL;AACA,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACnC,MAAA,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,CAAC,CAAA,EAAI,IAAA,CAAK,MAAA,CAAO,MAAA,CAAO,CAAC,CAAC,CAAC,CAAC,CAAA;AAAA,IACxD;AACA,IAAA,OAAO,GAAA,GAAM,KAAA,CAAM,IAAA,CAAK,GAAG,CAAA,GAAI,GAAA;AAAA,EACnC;AACA,EAAA,IAAI,OAAO,MAAM,QAAA,EAAU;AACvB,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,IAAA,CAAK,CAAC,EAAE,IAAA,EAAK;AACjC,IAAA,MAAM,QAAkB,EAAC;AACzB,IAAA,KAAA,MAAW,KAAK,IAAA,EAAM;AAClB,MAAA,MAAM,KAAA,GAAQ,EAAE,CAAC,CAAA;AACjB,MAAA,IAAI,UAAU,MAAA,EAAW;AACzB,MAAA,KAAA,CAAM,IAAA,CAAK,YAAA,CAAa,CAAC,CAAA,GAAI,GAAA,GAAM,MAAA,CAAO,KAAA,EAAoB,IAAA,CAAK,MAAA,CAAO,CAAC,CAAC,CAAC,CAAA;AAAA,IACjF;AACA,IAAA,OAAO,GAAA,GAAM,KAAA,CAAM,IAAA,CAAK,GAAG,CAAA,GAAI,GAAA;AAAA,EACnC;AACA,EAAA,MAAM,IAAI,KAAA,CAAM,oCAAA,GAAuC,OAAO,CAAC,CAAA;AACnE;AAEA,SAAS,aAAa,CAAA,EAAmB;AACrC,EAAA,IAAI,CAAC,OAAO,QAAA,CAAS,CAAC,GAAG,MAAM,IAAI,MAAM,4CAA4C,CAAA;AACrF,EAAA,IAAI,MAAA,CAAO,EAAA,CAAG,CAAA,EAAG,EAAE,GAAG,OAAO,GAAA;AAC7B,EAAA,IAAI,MAAA,CAAO,UAAU,CAAC,CAAA,IAAK,KAAK,GAAA,CAAI,CAAC,IAAI,IAAA,EAAM;AAC3C,IAAA,OAAO,OAAO,CAAC,CAAA;AAAA,EACnB;AAIA,EAAA,OAAO,IAAA,CAAK,UAAU,CAAC,CAAA;AAC3B;AAEA,IAAM,YAAA,GAAuC;AAAA,EACzC,IAAA,EAAM,MAAA;AAAA,EACN,GAAA,EAAK,KAAA;AAAA,EACL,IAAA,EAAM,KAAA;AAAA,EACN,IAAA,EAAM,KAAA;AAAA,EACN,IAAA,EAAM,KAAA;AAAA,EACN,IAAA,EAAM,KAAA;AAAA,EACN,GAAA,EAAM;AACV,CAAA;AAEA,SAAS,aAAa,CAAA,EAAmB;AACrC,EAAA,IAAI,GAAA,GAAM,GAAA;AACV,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,QAAQ,CAAA,EAAA,EAAK;AAC/B,IAAA,MAAM,EAAA,GAAK,EAAE,CAAC,CAAA;AACd,IAAA,MAAM,IAAA,GAAO,EAAA,CAAG,UAAA,CAAW,CAAC,CAAA;AAC5B,IAAA,IAAI,YAAA,CAAa,EAAE,CAAA,EAAG;AAClB,MAAA,GAAA,IAAO,aAAa,EAAE,CAAA;AAAA,IAC1B,CAAA,MAAA,IAAW,OAAO,EAAA,EAAM;AACpB,MAAA,GAAA,IAAO,QAAQ,IAAA,CAAK,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AAAA,IACpD,CAAA,MAAO;AACH,MAAA,GAAA,IAAO,EAAA;AAAA,IACX;AAAA,EACJ;AACA,EAAA,GAAA,IAAO,GAAA;AACP,EAAA,OAAO,GAAA;AACX;;;AC3GO,IAAM,gBAAA,GAAmB,CAAA;;;AC4BhC,IAAM,WAAA,GAA2B;AAAA,EAC7B,GAAA,EAAK,QAAA;AAAA,EACL,IAAA,EAAM,aAAA;AAAA,EACN,GAAA,EAAK;AACT,CAAA;AAEA,SAAS,eAAe,GAAA,EAA8B;AAClD,EAAA,OAAO,GAAA;AACX;AAOA,SAAS,eAAe,GAAA,EAA+B;AACnD,EAAA,MAAM,KAAA,GAAsB;AAAA,IACxB,GAAG,GAAA;AAAA,IACH,EAAA,EAAI,EAAA;AAAA,IACJ,UAAA,EAAY,EAAA;AAAA,IACZ,GAAA,EAAK,EAAE,GAAA,EAAK,QAAA,EAAU,QAAQ,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,KAAA,EAAO,EAAA,EAAG;AAAA,IAC1D,UAAA,EAAY,GAAA,CAAI,UAAA,CAAW,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,GAAG,CAAA,EAAG,WAAA,EAAa,EAAA,EAAG,CAAE;AAAA,GACrE;AACA,EAAA,MAAM,KAAA,GAAQ,cAAA,CAAe,cAAA,CAAe,KAAK,CAAC,CAAA;AAClD,EAAA,OAAOC,uBAAY,KAAK,CAAA;AAC5B;AAOA,SAAS,kBAAkB,GAAA,EAA+B;AACtD,EAAA,MAAM,KAAA,GAAsB;AAAA,IACxB,GAAG,GAAA;AAAA,IACH,EAAA,EAAI,EAAA;AAAA,IACJ,GAAA,EAAK,EAAE,GAAA,EAAK,GAAA,CAAI,GAAA,CAAI,GAAA,EAAK,MAAA,EAAQ,GAAA,CAAI,GAAA,CAAI,MAAA,EAAQ,KAAA,EAAO,EAAA;AAAG,GAC/D;AACA,EAAA,MAAM,KAAA,GAAQ,cAAA,CAAe,cAAA,CAAe,KAAK,CAAC,CAAA;AAClD,EAAA,OAAOA,uBAAY,KAAK,CAAA;AAC5B;AAEA,eAAsB,KAAK,KAAA,EAAyC;AAChE,EAAA,MAAM,IAAA,GAAqB,KAAA,CAAM,IAAA,KAAS,KAAA,CAAM,UAAU,SAAA,GAAY,UAAA,CAAA;AACtE,EAAA,MAAM,GAAA,GAAA,iBAAM,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AACnC,EAAA,MAAM,QAAA,GAAWC,wBAAa,EAAE,CAAA;AAEhC,EAAA,MAAM,WAAA,GAAcA,wBAAa,EAAE,CAAA;AAGnC,EAAA,MAAM,aAAkC,EAAC;AACzC,EAAA,KAAA,MAAW,CAAA,IAAK,MAAM,UAAA,EAAY;AAC9B,IAAA,MAAM,eAAA,GAAkBC,oBAAA,CAAU,CAAA,CAAE,SAAS,CAAA;AAC7C,IAAA,MAAM,YAAYC,gCAAA,EAAsB;AACxC,IAAA,MAAM,MAAA,GAASC,uBAAA,CAAa,SAAA,CAAU,MAAA,EAAQ,eAAe,CAAA;AAC7D,IAAA,MAAM,GAAA,GAAMC,qBAAA;AAAA,MACR,MAAA;AAAA,MACA,QAAA;AAAA,MACAN,qBAAAA,CAAW,iBAAA,GAAoB,CAAA,CAAE,SAAS,CAAA;AAAA,MAC1C;AAAA,KACJ;AACA,IAAA,MAAM,SAAA,GAAYE,wBAAa,EAAE,CAAA;AACjC,IAAA,MAAM,OAAA,GAAUK,yBAAc,GAAA,EAAK,SAAA,EAAW,aAAaP,qBAAAA,CAAW,CAAA,CAAE,SAAS,CAAC,CAAA;AAClF,IAAAQ,kBAAA,CAAQ,UAAU,MAAM,CAAA;AACxB,IAAAA,kBAAA,CAAQ,MAAM,CAAA;AACd,IAAAA,kBAAA,CAAQ,GAAG,CAAA;AACX,IAAA,UAAA,CAAW,IAAA,CAAK;AAAA,MACZ,SAAS,CAAA,CAAE,OAAA;AAAA,MACX,WAAW,CAAA,CAAE,SAAA;AAAA,MACb,WAAW,CAAA,CAAE,SAAA;AAAA,MACb,MAAA,EAAQC,oBAAA,CAAU,SAAA,CAAU,MAAM,CAAA;AAAA,MAClC,WAAA,EAAaC,wBAAa,OAAO,CAAA;AAAA,MACjC,SAAA,EAAWD,qBAAU,SAAS;AAAA,KACjC,CAAA;AAAA,EACL;AAGA,EAAA,MAAM,QAAA,GAAyB;AAAA,IAC3B,CAAA,EAAG,gBAAA;AAAA,IACH,IAAA;AAAA,IACA,EAAA,EAAI,EAAA;AAAA,IACJ,GAAA,EAAK,WAAA;AAAA,IACL,IAAA,EAAM;AAAA,MACF,OAAA,EAAS,MAAM,MAAA,CAAO,OAAA;AAAA,MACtB,GAAI,KAAA,CAAM,MAAA,CAAO,cAAA,GACX,EAAE,gBAAgB,KAAA,CAAM,MAAA,CAAO,cAAA,EAAe,GAC9C;AAAC,KACX;AAAA,IACA,UAAA;AAAA,IACA,UAAA,EAAY,EAAA;AAAA,IACZ,QAAA,EAAUA,qBAAU,QAAQ,CAAA;AAAA,IAC5B,GAAI,MAAM,IAAA,KAAS,MAAA,GAAY,EAAE,IAAA,EAAM,KAAA,CAAM,IAAA,EAAK,GAAI,EAAC;AAAA,IACvD,UAAA,EAAY,GAAA;AAAA,IACZ,YAAY,KAAA,CAAM,SAAA,GAAY,KAAA,CAAM,SAAA,CAAU,aAAY,GAAI,IAAA;AAAA,IAC9D,OAAA,EAAS,MAAM,OAAA,IAAW,IAAA;AAAA,IAC1B,GAAA,EAAK,EAAE,GAAA,EAAK,QAAA,EAAU,QAAQ,KAAA,CAAM,MAAA,CAAO,OAAA,EAAS,KAAA,EAAO,EAAA;AAAG,GAClE;AAEA,EAAA,MAAM,OAAA,GAAU,eAAe,QAAQ,CAAA;AACvC,EAAA,MAAM,aAAaF,wBAAA,CAAc,WAAA,EAAa,QAAA,EAAU,KAAA,CAAM,SAAS,OAAO,CAAA;AAC9E,EAAAC,kBAAA,CAAQ,WAAW,CAAA;AAEnB,EAAA,MAAM,SAAuB,EAAE,GAAG,UAAU,UAAA,EAAYE,uBAAA,CAAa,UAAU,CAAA,EAAE;AACjF,EAAA,MAAM,eAAA,GAAkB,kBAAkB,MAAM,CAAA;AAChD,EAAA,MAAM,UAAA,GAAaD,qBAAU,eAAe,CAAA;AAE5C,EAAA,MAAM,YAAA,GAAe,MAAM,KAAA,CAAM,MAAA,CAAO,YAAY,UAAU,CAAA;AAE9D,EAAA,OAAO;AAAA,IACH,GAAG,MAAA;AAAA,IACH,EAAA,EAAI,UAAA;AAAA,IACJ,GAAA,EAAK,EAAE,GAAA,EAAK,QAAA,EAAU,QAAQ,KAAA,CAAM,MAAA,CAAO,OAAA,EAAS,KAAA,EAAO,YAAA;AAAa,GAC5E;AACJ;AAEA,eAAsB,OAAO,KAAA,EAA2C;AACpE,EAAA,MAAM,MAAM,KAAA,CAAM,QAAA;AAGlB,EAAA,IAAI,IAAI,UAAA,EAAY;AAChB,IAAA,MAAM,MAAM,KAAA,CAAM,GAAA,GAAM,MAAM,GAAA,EAAI,uBAAQ,IAAA,EAAK;AAC/C,IAAA,IAAI,GAAA,CAAI,SAAQ,GAAI,IAAI,KAAK,GAAA,CAAI,UAAU,CAAA,CAAE,OAAA,EAAQ,EAAG;AACpD,MAAA,MAAM,SAAA,CAAU,aAAa,qBAAqB,CAAA;AAAA,IACtD;AAAA,EACJ;AAGA,EAAA,MAAM,OAAA,GAAU,kBAAkB,GAAG,CAAA;AACrC,EAAA,MAAM,KAAA,GAAQA,qBAAU,OAAO,CAAA;AAC/B,EAAA,IAAI,KAAA,KAAU,IAAI,EAAA,EAAI;AAClB,IAAA,MAAM,SAAA,CAAU,aAAa,sBAAsB,CAAA;AAAA,EACvD;AAGA,EAAA,IAAI,CAAC,MAAM,sBAAA,EAAwB;AAC/B,IAAA,IAAI,CAAC,MAAM,YAAA,EAAc;AACrB,MAAA,MAAM,SAAA,CAAU,aAAa,6BAA6B,CAAA;AAAA,IAC9D;AACA,IAAA,MAAM,EAAA,GAAK,MAAM,KAAA,CAAM,YAAA,CAAa,GAAA,CAAI,EAAA,EAAI,GAAA,CAAI,GAAA,CAAI,KAAA,EAAO,GAAA,CAAI,GAAA,CAAI,MAAM,CAAA;AACzE,IAAA,IAAI,CAAC,EAAA,EAAI,MAAM,SAAA,CAAU,aAAa,iCAAiC,CAAA;AAAA,EAC3E;AAGA,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,UAAA,CAAW,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,SAAA,KAAc,KAAA,CAAM,MAAA,CAAO,SAAS,CAAA;AAC9E,EAAA,IAAI,CAAC,IAAA,EAAM,MAAM,SAAA,CAAU,mBAAmB,qCAAqC,CAAA;AAGnF,EAAA,MAAM,OAAA,GAAU,eAAe,EAAE,GAAG,KAAK,EAAA,EAAI,GAAA,CAAI,IAAI,CAAA;AACrD,EAAA,MAAM,MAAA,GAASN,oBAAA,CAAU,IAAA,CAAK,MAAM,CAAA;AACpC,EAAA,MAAM,MAAA,GAASE,uBAAA,CAAa,KAAA,CAAM,MAAA,CAAO,WAAW,MAAM,CAAA;AAC1D,EAAA,MAAM,QAAA,GAAWF,oBAAA,CAAU,GAAA,CAAI,QAAQ,CAAA;AACvC,EAAA,MAAM,GAAA,GAAMG,qBAAA;AAAA,IACR,MAAA;AAAA,IACA,QAAA;AAAA,IACAN,qBAAAA,CAAW,iBAAA,GAAoB,IAAA,CAAK,SAAS,CAAA;AAAA,IAC7C;AAAA,GACJ;AACA,EAAA,MAAM,SAAA,GAAYG,oBAAA,CAAU,IAAA,CAAK,SAAS,CAAA;AAC1C,EAAA,MAAM,OAAA,GAAUQ,uBAAA,CAAa,IAAA,CAAK,WAAW,CAAA;AAE7C,EAAA,IAAI,WAAA;AACJ,EAAA,IAAI;AACA,IAAA,WAAA,GAAcC,yBAAc,GAAA,EAAK,SAAA,EAAW,SAASZ,qBAAAA,CAAW,IAAA,CAAK,SAAS,CAAC,CAAA;AAAA,EACnF,SAAS,CAAA,EAAG;AACR,IAAA,MAAM,SAAA,CAAU,aAAa,+BAA+B,CAAA;AAAA,EAChE,CAAA,SAAE;AACE,IAAAQ,kBAAA,CAAQ,MAAM,CAAA;AACd,IAAAA,kBAAA,CAAQ,GAAG,CAAA;AAAA,EACf;AAGA,EAAA,MAAM,UAAA,GAAaG,uBAAA,CAAa,GAAA,CAAI,UAAU,CAAA;AAC9C,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI;AACA,IAAA,OAAA,GAAUC,wBAAA,CAAc,WAAA,EAAa,QAAA,EAAU,UAAA,EAAY,OAAO,CAAA;AAAA,EACtE,SAAS,CAAA,EAAG;AACR,IAAAJ,kBAAA,CAAQ,WAAW,CAAA;AACnB,IAAA,MAAM,SAAA,CAAU,aAAa,8BAA8B,CAAA;AAAA,EAC/D;AACA,EAAAA,kBAAA,CAAQ,WAAW,CAAA;AAEnB,EAAA,OAAO;AAAA,IACH,OAAA;AAAA,IACA,YAAY,GAAA,CAAI,EAAA;AAAA,IAChB,QAAQ,GAAA,CAAI,IAAA;AAAA,IACZ,iBAAiB,IAAA,CAAK;AAAA,GAC1B;AACJ;AAEO,IAAM,SAAA,GAAN,cAAwB,KAAA,CAAM;AAAA,EAEjC,WAAA,CAAY,MAAc,OAAA,EAAiB;AACvC,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,WAAA;AAAA,EAChB;AACJ;AAEA,SAAS,SAAA,CAAU,MAAc,OAAA,EAA4B;AACzD,EAAA,OAAO,IAAI,SAAA,CAAU,IAAA,EAAM,OAAO,CAAA;AACtC","file":"seal.js","sourcesContent":["// RFC 8785 JSON Canonicalization Scheme with one addition: within the\n// envelope, `recipients` entries are sorted by `device_id` ascending before\n// canonicalization. See SPEC.md §5.\n//\n// We implement a small, self-contained canonicalizer. Inputs are plain JSON\n// values (objects, arrays, strings, numbers, booleans, null). Undefined and\n// functions are rejected.\n\nimport { utf8Encode } from '@orangecheck/lock-crypto';\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nconst JSON_RECIPIENTS_SORT_PATH = ['recipients'] as const;\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value, []);\n}\n\nexport function canonicalBytes(value: JsonValue): Uint8Array {\n return utf8Encode(canonicalize(value) + '\\n');\n}\n\nfunction encode(v: JsonValue, path: string[]): 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 let items = v;\n // Sort recipients by device_id when we're at path ['recipients'].\n if (\n path.length === JSON_RECIPIENTS_SORT_PATH.length &&\n path.every((p, i) => p === JSON_RECIPIENTS_SORT_PATH[i]) &&\n items.every(\n (it) =>\n it &&\n typeof it === 'object' &&\n !Array.isArray(it) &&\n typeof (it as Record<string, JsonValue>).device_id === 'string'\n )\n ) {\n items = [...items].sort((a, b) => {\n const aid = (a as Record<string, JsonValue>).device_id as string;\n const bid = (b as Record<string, JsonValue>).device_id as string;\n return aid < bid ? -1 : aid > bid ? 1 : 0;\n });\n }\n for (let i = 0; i < items.length; i++) {\n parts.push(encode(items[i]!, path.concat(String(i))));\n }\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, path.concat(k)));\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 // Follow JSON.stringify for non-integers; it produces canonical-ish output\n // that matches IEEE 754 round-tripping for our use case (we never emit\n // floats ourselves).\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","// Wire types for OC Lock v2 envelopes. See SPEC.md §4.\n\nexport const ENVELOPE_VERSION = 2 as const;\n\nexport type EnvelopeKind = 'identity' | 'payment';\n\nexport interface EnvelopeAlg {\n kem: 'x25519';\n aead: 'aes-256-gcm';\n kdf: 'hkdf-sha256';\n}\n\nexport interface EnvelopeFrom {\n address: string;\n attestation_id?: string;\n}\n\nexport interface EnvelopeRecipient {\n address: string;\n device_id: string;\n device_pk: string; // 32-byte hex X25519 pubkey\n eph_pk: string; // 32-byte hex X25519 ephemeral pubkey\n wrapped_key: string; // base64url(aead(content_key, kek, nonce_kek))\n nonce_kek: string; // 12-byte hex\n}\n\nexport interface EnvelopePayment {\n amount_sats: number;\n address: string;\n confirmations: number;\n relay: string;\n}\n\nexport interface EnvelopeSignature {\n alg: 'bip322';\n pubkey: string; // sender btc address\n value: string; // base64 BIP-322\n}\n\nexport interface LockEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: EnvelopeKind;\n id: string; // 32-byte hex\n alg: EnvelopeAlg;\n from: EnvelopeFrom;\n recipients: EnvelopeRecipient[];\n ciphertext: string; // base64url\n nonce_ct: string; // 12-byte hex\n hint?: string;\n created_at: string; // iso8601 utc\n expires_at: string | null;\n payment: EnvelopePayment | null;\n sig: EnvelopeSignature;\n}\n\n// Inputs\n\nexport interface DeviceRecord {\n address: string;\n device_id: string;\n device_pk: string; // hex\n}\n\nexport interface SealInput {\n payload: Uint8Array;\n sender: {\n address: string;\n attestation_id?: string;\n /**\n * Returns a BIP-322 base64 signature of the given message bytes as UTF-8.\n * Exactly one call per seal (over the envelope id).\n */\n signMessage: (msg: string) => Promise<string>;\n };\n recipients: DeviceRecord[];\n kind?: EnvelopeKind;\n hint?: string;\n expiresAt?: Date | null;\n payment?: EnvelopePayment | null;\n}\n\nexport interface UnsealInput {\n envelope: LockEnvelope;\n device: {\n device_id: string;\n /** 32-byte X25519 private key for this device. */\n secretKey: Uint8Array;\n };\n /**\n * Called to verify the sender's BIP-322 signature. Returns true if valid.\n * The verifier is injected because verification libraries differ between\n * Node and browser contexts; consumers plug their own.\n */\n verifyBip322?: (msg: string, signatureB64: string, address: string) => Promise<boolean>;\n /** Skip sender signature verification. Default false; true only for self-seals. */\n skipSenderVerification?: boolean;\n /** Current time; defaults to Date.now(). Used for expiry enforcement. */\n now?: () => Date;\n}\n\nexport interface UnsealResult {\n payload: Uint8Array;\n envelopeId: string;\n sender: EnvelopeFrom;\n matchedDeviceId: string;\n}\n","// Seal and unseal OC Lock envelopes. See SPEC.md §4.\n\nimport {\n aesGcmDecrypt,\n aesGcmEncrypt,\n b64urlDecode,\n b64urlEncode,\n generateX25519KeyPair,\n hexDecode,\n hexEncode,\n hkdfSha256,\n randomBytesN,\n sha256Bytes,\n utf8Encode,\n x25519Shared,\n zeroize,\n} from '@orangecheck/lock-crypto';\n\nimport { canonicalBytes, canonicalize, type JsonValue } from './canonical.js';\nimport {\n ENVELOPE_VERSION,\n type EnvelopeAlg,\n type EnvelopeKind,\n type EnvelopeRecipient,\n type LockEnvelope,\n type SealInput,\n type UnsealInput,\n type UnsealResult,\n} from './types.js';\n\nconst DEFAULT_ALG: EnvelopeAlg = {\n kem: 'x25519',\n aead: 'aes-256-gcm',\n kdf: 'hkdf-sha256',\n};\n\nfunction envelopeToJson(env: LockEnvelope): JsonValue {\n return env as unknown as JsonValue;\n}\n\n/**\n * Compute the draft envelope id used as AAD during AEAD operations. This is\n * the canonical envelope with `ciphertext`, `sig`, and each\n * `recipients[*].wrapped_key` elided per SPEC §4.2 step 3.\n */\nfunction computeDraftId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n ciphertext: '',\n sig: { alg: 'bip322', pubkey: env.from.address, value: '' },\n recipients: env.recipients.map((r) => ({ ...r, wrapped_key: '' })),\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\n/**\n * Compute the final envelope id: SHA-256 of the canonical envelope with\n * `sig.value` stripped to empty. The `id` field itself is excluded from the\n * hash input (we set it to empty before hashing).\n */\nfunction computeEnvelopeId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n sig: { alg: env.sig.alg, pubkey: env.sig.pubkey, value: '' },\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\nexport async function seal(input: SealInput): Promise<LockEnvelope> {\n const kind: EnvelopeKind = input.kind ?? (input.payment ? 'payment' : 'identity');\n const now = new Date().toISOString();\n const nonce_ct = randomBytesN(12);\n\n const content_key = randomBytesN(32);\n\n // Build recipients with wrapped keys.\n const recipients: EnvelopeRecipient[] = [];\n for (const r of input.recipients) {\n const device_pk_bytes = hexDecode(r.device_pk);\n const ephemeral = generateX25519KeyPair();\n const shared = x25519Shared(ephemeral.secret, device_pk_bytes);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + r.device_id),\n 32\n );\n const nonce_kek = randomBytesN(12);\n const wrapped = aesGcmEncrypt(kek, nonce_kek, content_key, utf8Encode(r.device_id));\n zeroize(ephemeral.secret);\n zeroize(shared);\n zeroize(kek);\n recipients.push({\n address: r.address,\n device_id: r.device_id,\n device_pk: r.device_pk,\n eph_pk: hexEncode(ephemeral.public),\n wrapped_key: b64urlEncode(wrapped),\n nonce_kek: hexEncode(nonce_kek),\n });\n }\n\n // Draft envelope (id, sig, ciphertext empty) for AAD computation.\n const draftEnv: LockEnvelope = {\n v: ENVELOPE_VERSION,\n kind,\n id: '',\n alg: DEFAULT_ALG,\n from: {\n address: input.sender.address,\n ...(input.sender.attestation_id\n ? { attestation_id: input.sender.attestation_id }\n : {}),\n },\n recipients,\n ciphertext: '',\n nonce_ct: hexEncode(nonce_ct),\n ...(input.hint !== undefined ? { hint: input.hint } : {}),\n created_at: now,\n expires_at: input.expiresAt ? input.expiresAt.toISOString() : null,\n payment: input.payment ?? null,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: '' },\n };\n\n const draftId = computeDraftId(draftEnv);\n const ciphertext = aesGcmEncrypt(content_key, nonce_ct, input.payload, draftId);\n zeroize(content_key);\n\n const withCt: LockEnvelope = { ...draftEnv, ciphertext: b64urlEncode(ciphertext) };\n const envelopeIdBytes = computeEnvelopeId(withCt);\n const envelopeId = hexEncode(envelopeIdBytes);\n\n const signatureB64 = await input.sender.signMessage(envelopeId);\n\n return {\n ...withCt,\n id: envelopeId,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: signatureB64 },\n };\n}\n\nexport async function unseal(input: UnsealInput): Promise<UnsealResult> {\n const env = input.envelope;\n\n // Expiry.\n if (env.expires_at) {\n const now = input.now ? input.now() : new Date();\n if (now.getTime() > new Date(env.expires_at).getTime()) {\n throw makeError('E_EXPIRED', 'envelope is expired');\n }\n }\n\n // Recompute envelope id.\n const idBytes = computeEnvelopeId(env);\n const idHex = hexEncode(idBytes);\n if (idHex !== env.id) {\n throw makeError('E_BAD_SIG', 'envelope id mismatch');\n }\n\n // Verify sender signature unless caller opts out (self-seal).\n if (!input.skipSenderVerification) {\n if (!input.verifyBip322) {\n throw makeError('E_BAD_SIG', 'no bip322 verifier supplied');\n }\n const ok = await input.verifyBip322(env.id, env.sig.value, env.sig.pubkey);\n if (!ok) throw makeError('E_BAD_SIG', 'sender signature did not verify');\n }\n\n // Find our recipient entry.\n const mine = env.recipients.find((r) => r.device_id === input.device.device_id);\n if (!mine) throw makeError('E_NOT_ADDRESSED', 'no matching device_id in recipients');\n\n // Unwrap content key.\n const draftId = computeDraftId({ ...env, id: env.id });\n const eph_pk = hexDecode(mine.eph_pk);\n const shared = x25519Shared(input.device.secretKey, eph_pk);\n const nonce_ct = hexDecode(env.nonce_ct);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + mine.device_id),\n 32\n );\n const nonce_kek = hexDecode(mine.nonce_kek);\n const wrapped = b64urlDecode(mine.wrapped_key);\n\n let content_key: Uint8Array;\n try {\n content_key = aesGcmDecrypt(kek, nonce_kek, wrapped, utf8Encode(mine.device_id));\n } catch (e) {\n throw makeError('E_BAD_TAG', 'wrapped key failed to decrypt');\n } finally {\n zeroize(shared);\n zeroize(kek);\n }\n\n // Decrypt ciphertext.\n const ciphertext = b64urlDecode(env.ciphertext);\n let payload: Uint8Array;\n try {\n payload = aesGcmDecrypt(content_key, nonce_ct, ciphertext, draftId);\n } catch (e) {\n zeroize(content_key);\n throw makeError('E_BAD_TAG', 'ciphertext failed to decrypt');\n }\n zeroize(content_key);\n\n return {\n payload,\n envelopeId: env.id,\n sender: env.from,\n matchedDeviceId: mine.device_id,\n };\n}\n\nexport class LockError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.code = code;\n this.name = 'LockError';\n }\n}\n\nfunction makeError(code: string, message: string): LockError {\n return new LockError(code, message);\n}\n\nexport { canonicalize, canonicalBytes };\n"]}
{"version":3,"sources":["../src/canonical.ts","../src/types.ts","../src/seal.ts"],"names":["utf8Encode","sha256Bytes","randomBytesN","hexDecode","generateX25519KeyPair","x25519Shared","hkdfSha256","aesGcmEncrypt","zeroize","hexEncode","b64urlEncode","b64urlDecode","aesGcmDecrypt"],"mappings":"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","file":"seal.js","sourcesContent":["// RFC 8785 JSON Canonicalization Scheme with one addition: within the\n// envelope, `recipients` entries are sorted by `device_id` ascending before\n// canonicalization. See SPEC.md §5.\n//\n// We implement a small, self-contained canonicalizer. Inputs are plain JSON\n// values (objects, arrays, strings, numbers, booleans, null). Undefined and\n// functions are rejected.\n\nimport { utf8Encode } from '@orangecheck/lock-crypto';\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nconst JSON_RECIPIENTS_SORT_PATH = ['recipients'] as const;\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value, []);\n}\n\nexport function canonicalBytes(value: JsonValue): Uint8Array {\n return utf8Encode(canonicalize(value) + '\\n');\n}\n\nfunction encode(v: JsonValue, path: string[]): 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 let items = v;\n // Sort recipients by device_id when we're at path ['recipients'].\n if (\n path.length === JSON_RECIPIENTS_SORT_PATH.length &&\n path.every((p, i) => p === JSON_RECIPIENTS_SORT_PATH[i]) &&\n items.every(\n (it) =>\n it &&\n typeof it === 'object' &&\n !Array.isArray(it) &&\n typeof (it as Record<string, JsonValue>).device_id === 'string'\n )\n ) {\n items = [...items].sort((a, b) => {\n const aid = (a as Record<string, JsonValue>).device_id as string;\n const bid = (b as Record<string, JsonValue>).device_id as string;\n return aid < bid ? -1 : aid > bid ? 1 : 0;\n });\n }\n for (let i = 0; i < items.length; i++) {\n parts.push(encode(items[i]!, path.concat(String(i))));\n }\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, path.concat(k)));\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 // Follow JSON.stringify for non-integers; it produces canonical-ish output\n // that matches IEEE 754 round-tripping for our use case (we never emit\n // floats ourselves).\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","// Wire types for OC Lock v2 envelopes. See SPEC.md §4.\n\nexport const ENVELOPE_VERSION = 2 as const;\n\nexport type EnvelopeKind = 'identity' | 'payment';\n\nexport interface EnvelopeAlg {\n kem: 'x25519';\n aead: 'aes-256-gcm';\n kdf: 'hkdf-sha256';\n}\n\nexport interface EnvelopeFrom {\n address: string;\n attestation_id?: string;\n}\n\nexport interface EnvelopeRecipient {\n address: string;\n device_id: string;\n device_pk: string; // 32-byte hex X25519 pubkey\n eph_pk: string; // 32-byte hex X25519 ephemeral pubkey\n wrapped_key: string; // base64url(aead(content_key, kek, nonce_kek))\n nonce_kek: string; // 12-byte hex\n}\n\nexport interface EnvelopePayment {\n amount_sats: number;\n address: string;\n confirmations: number;\n relay: string;\n}\n\nexport interface EnvelopeSignature {\n alg: 'bip322';\n pubkey: string; // sender btc address\n value: string; // base64 BIP-322\n}\n\nexport interface LockEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: EnvelopeKind;\n id: string; // 32-byte hex\n alg: EnvelopeAlg;\n from: EnvelopeFrom;\n recipients: EnvelopeRecipient[];\n ciphertext: string; // base64url\n nonce_ct: string; // 12-byte hex\n hint?: string;\n created_at: string; // iso8601 utc\n expires_at: string | null;\n payment: EnvelopePayment | null;\n sig: EnvelopeSignature;\n}\n\n// Inputs\n\nexport interface DeviceRecord {\n address: string;\n device_id: string;\n device_pk: string; // hex\n}\n\nexport interface SealInput {\n payload: Uint8Array;\n sender: {\n address: string;\n attestation_id?: string;\n /**\n * Returns a BIP-322 base64 signature of the given message bytes as UTF-8.\n * Exactly one call per seal (over the envelope id).\n */\n signMessage: (msg: string) => Promise<string>;\n };\n recipients: DeviceRecord[];\n kind?: EnvelopeKind;\n hint?: string;\n expiresAt?: Date | null;\n payment?: EnvelopePayment | null;\n}\n\nexport interface UnsealInput {\n envelope: LockEnvelope;\n device: {\n device_id: string;\n /** 32-byte X25519 private key for this device. */\n secretKey: Uint8Array;\n };\n /**\n * Called to verify the sender's BIP-322 signature. Returns true if valid.\n * The verifier is injected because verification libraries differ between\n * Node and browser contexts; consumers plug their own.\n */\n verifyBip322?: (msg: string, signatureB64: string, address: string) => Promise<boolean>;\n /** Skip sender signature verification. Default false; true only for self-seals. */\n skipSenderVerification?: boolean;\n /** Current time; defaults to Date.now(). Used for expiry enforcement. */\n now?: () => Date;\n}\n\nexport interface UnsealResult {\n payload: Uint8Array;\n envelopeId: string;\n sender: EnvelopeFrom;\n matchedDeviceId: string;\n}\n","// Seal and unseal OC Lock envelopes. See SPEC.md §4.\n\nimport {\n aesGcmDecrypt,\n aesGcmEncrypt,\n b64urlDecode,\n b64urlEncode,\n generateX25519KeyPair,\n hexDecode,\n hexEncode,\n hkdfSha256,\n randomBytesN,\n sha256Bytes,\n utf8Encode,\n x25519Shared,\n zeroize,\n} from '@orangecheck/lock-crypto';\n\nimport { canonicalBytes, canonicalize, type JsonValue } from './canonical.js';\nimport {\n ENVELOPE_VERSION,\n type EnvelopeAlg,\n type EnvelopeKind,\n type EnvelopeRecipient,\n type LockEnvelope,\n type SealInput,\n type UnsealInput,\n type UnsealResult,\n} from './types.js';\n\nconst DEFAULT_ALG: EnvelopeAlg = {\n kem: 'x25519',\n aead: 'aes-256-gcm',\n kdf: 'hkdf-sha256',\n};\n\nfunction envelopeToJson(env: LockEnvelope): JsonValue {\n return env as unknown as JsonValue;\n}\n\n/**\n * Compute the draft envelope id used as AAD during AEAD operations. This is\n * the canonical envelope with `ciphertext`, `sig`, and each\n * `recipients[*].wrapped_key` elided per SPEC §4.2 step 3.\n */\nfunction computeDraftId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n ciphertext: '',\n sig: { alg: 'bip322', pubkey: env.from.address, value: '' },\n recipients: env.recipients.map((r) => ({ ...r, wrapped_key: '' })),\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\n/**\n * Compute the final envelope id: SHA-256 of the canonical envelope with\n * `sig.value` stripped to empty. The `id` field itself is excluded from the\n * hash input (we set it to empty before hashing).\n */\nfunction computeEnvelopeId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n sig: { alg: env.sig.alg, pubkey: env.sig.pubkey, value: '' },\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\nexport async function seal(input: SealInput): Promise<LockEnvelope> {\n // An envelope with zero recipients is unreadable by anyone — the\n // content key is only wrapped per-recipient, so the caller would\n // produce a silently-unusable ciphertext. Reject upfront with a\n // clear error rather than emitting junk.\n if (!input.recipients || input.recipients.length === 0) {\n throw new LockError(\n 'E_NO_RECIPIENTS',\n 'seal() requires at least one recipient'\n );\n }\n const kind: EnvelopeKind = input.kind ?? (input.payment ? 'payment' : 'identity');\n const now = new Date().toISOString();\n const nonce_ct = randomBytesN(12);\n\n const content_key = randomBytesN(32);\n\n // Build recipients with wrapped keys.\n const recipients: EnvelopeRecipient[] = [];\n for (const r of input.recipients) {\n const device_pk_bytes = hexDecode(r.device_pk);\n const ephemeral = generateX25519KeyPair();\n const shared = x25519Shared(ephemeral.secret, device_pk_bytes);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + r.device_id),\n 32\n );\n const nonce_kek = randomBytesN(12);\n const wrapped = aesGcmEncrypt(kek, nonce_kek, content_key, utf8Encode(r.device_id));\n zeroize(ephemeral.secret);\n zeroize(shared);\n zeroize(kek);\n recipients.push({\n address: r.address,\n device_id: r.device_id,\n device_pk: r.device_pk,\n eph_pk: hexEncode(ephemeral.public),\n wrapped_key: b64urlEncode(wrapped),\n nonce_kek: hexEncode(nonce_kek),\n });\n }\n\n // Draft envelope (id, sig, ciphertext empty) for AAD computation.\n const draftEnv: LockEnvelope = {\n v: ENVELOPE_VERSION,\n kind,\n id: '',\n alg: DEFAULT_ALG,\n from: {\n address: input.sender.address,\n ...(input.sender.attestation_id\n ? { attestation_id: input.sender.attestation_id }\n : {}),\n },\n recipients,\n ciphertext: '',\n nonce_ct: hexEncode(nonce_ct),\n ...(input.hint !== undefined ? { hint: input.hint } : {}),\n created_at: now,\n expires_at: input.expiresAt ? input.expiresAt.toISOString() : null,\n payment: input.payment ?? null,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: '' },\n };\n\n const draftId = computeDraftId(draftEnv);\n const ciphertext = aesGcmEncrypt(content_key, nonce_ct, input.payload, draftId);\n zeroize(content_key);\n\n const withCt: LockEnvelope = { ...draftEnv, ciphertext: b64urlEncode(ciphertext) };\n const envelopeIdBytes = computeEnvelopeId(withCt);\n const envelopeId = hexEncode(envelopeIdBytes);\n\n const signatureB64 = await input.sender.signMessage(envelopeId);\n\n return {\n ...withCt,\n id: envelopeId,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: signatureB64 },\n };\n}\n\nexport async function unseal(input: UnsealInput): Promise<UnsealResult> {\n const env = input.envelope;\n\n // Expiry.\n if (env.expires_at) {\n const now = input.now ? input.now() : new Date();\n if (now.getTime() > new Date(env.expires_at).getTime()) {\n throw makeError('E_EXPIRED', 'envelope is expired');\n }\n }\n\n // Recompute envelope id.\n const idBytes = computeEnvelopeId(env);\n const idHex = hexEncode(idBytes);\n if (idHex !== env.id) {\n throw makeError('E_BAD_SIG', 'envelope id mismatch');\n }\n\n // Verify sender signature unless caller opts out (self-seal).\n if (!input.skipSenderVerification) {\n if (!input.verifyBip322) {\n throw makeError('E_BAD_SIG', 'no bip322 verifier supplied');\n }\n const ok = await input.verifyBip322(env.id, env.sig.value, env.sig.pubkey);\n if (!ok) throw makeError('E_BAD_SIG', 'sender signature did not verify');\n }\n\n // Find our recipient entry.\n const mine = env.recipients.find((r) => r.device_id === input.device.device_id);\n if (!mine) throw makeError('E_NOT_ADDRESSED', 'no matching device_id in recipients');\n\n // Unwrap content key.\n const draftId = computeDraftId({ ...env, id: env.id });\n const eph_pk = hexDecode(mine.eph_pk);\n const shared = x25519Shared(input.device.secretKey, eph_pk);\n const nonce_ct = hexDecode(env.nonce_ct);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + mine.device_id),\n 32\n );\n const nonce_kek = hexDecode(mine.nonce_kek);\n const wrapped = b64urlDecode(mine.wrapped_key);\n\n let content_key: Uint8Array;\n try {\n content_key = aesGcmDecrypt(kek, nonce_kek, wrapped, utf8Encode(mine.device_id));\n } catch (e) {\n throw makeError('E_BAD_TAG', 'wrapped key failed to decrypt');\n } finally {\n zeroize(shared);\n zeroize(kek);\n }\n\n // Decrypt ciphertext.\n const ciphertext = b64urlDecode(env.ciphertext);\n let payload: Uint8Array;\n try {\n payload = aesGcmDecrypt(content_key, nonce_ct, ciphertext, draftId);\n } catch (e) {\n zeroize(content_key);\n throw makeError('E_BAD_TAG', 'ciphertext failed to decrypt');\n }\n zeroize(content_key);\n\n return {\n payload,\n envelopeId: env.id,\n sender: env.from,\n matchedDeviceId: mine.device_id,\n };\n}\n\nexport class LockError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.code = code;\n this.name = 'LockError';\n }\n}\n\nfunction makeError(code: string, message: string): LockError {\n return new LockError(code, message);\n}\n\nexport { canonicalize, canonicalBytes };\n"]}

@@ -112,2 +112,8 @@ import { utf8Encode, randomBytesN, hexDecode, generateX25519KeyPair, x25519Shared, hkdfSha256, aesGcmEncrypt, zeroize, hexEncode, b64urlEncode, b64urlDecode, aesGcmDecrypt, sha256Bytes } from '@orangecheck/lock-crypto';

async function seal(input) {
if (!input.recipients || input.recipients.length === 0) {
throw new LockError(
"E_NO_RECIPIENTS",
"seal() requires at least one recipient"
);
}
const kind = input.kind ?? (input.payment ? "payment" : "identity");

@@ -114,0 +120,0 @@ const now = (/* @__PURE__ */ new Date()).toISOString();

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

{"version":3,"sources":["../src/canonical.ts","../src/types.ts","../src/seal.ts"],"names":["utf8Encode"],"mappings":"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","file":"seal.mjs","sourcesContent":["// RFC 8785 JSON Canonicalization Scheme with one addition: within the\n// envelope, `recipients` entries are sorted by `device_id` ascending before\n// canonicalization. See SPEC.md §5.\n//\n// We implement a small, self-contained canonicalizer. Inputs are plain JSON\n// values (objects, arrays, strings, numbers, booleans, null). Undefined and\n// functions are rejected.\n\nimport { utf8Encode } from '@orangecheck/lock-crypto';\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nconst JSON_RECIPIENTS_SORT_PATH = ['recipients'] as const;\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value, []);\n}\n\nexport function canonicalBytes(value: JsonValue): Uint8Array {\n return utf8Encode(canonicalize(value) + '\\n');\n}\n\nfunction encode(v: JsonValue, path: string[]): 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 let items = v;\n // Sort recipients by device_id when we're at path ['recipients'].\n if (\n path.length === JSON_RECIPIENTS_SORT_PATH.length &&\n path.every((p, i) => p === JSON_RECIPIENTS_SORT_PATH[i]) &&\n items.every(\n (it) =>\n it &&\n typeof it === 'object' &&\n !Array.isArray(it) &&\n typeof (it as Record<string, JsonValue>).device_id === 'string'\n )\n ) {\n items = [...items].sort((a, b) => {\n const aid = (a as Record<string, JsonValue>).device_id as string;\n const bid = (b as Record<string, JsonValue>).device_id as string;\n return aid < bid ? -1 : aid > bid ? 1 : 0;\n });\n }\n for (let i = 0; i < items.length; i++) {\n parts.push(encode(items[i]!, path.concat(String(i))));\n }\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, path.concat(k)));\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 // Follow JSON.stringify for non-integers; it produces canonical-ish output\n // that matches IEEE 754 round-tripping for our use case (we never emit\n // floats ourselves).\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","// Wire types for OC Lock v2 envelopes. See SPEC.md §4.\n\nexport const ENVELOPE_VERSION = 2 as const;\n\nexport type EnvelopeKind = 'identity' | 'payment';\n\nexport interface EnvelopeAlg {\n kem: 'x25519';\n aead: 'aes-256-gcm';\n kdf: 'hkdf-sha256';\n}\n\nexport interface EnvelopeFrom {\n address: string;\n attestation_id?: string;\n}\n\nexport interface EnvelopeRecipient {\n address: string;\n device_id: string;\n device_pk: string; // 32-byte hex X25519 pubkey\n eph_pk: string; // 32-byte hex X25519 ephemeral pubkey\n wrapped_key: string; // base64url(aead(content_key, kek, nonce_kek))\n nonce_kek: string; // 12-byte hex\n}\n\nexport interface EnvelopePayment {\n amount_sats: number;\n address: string;\n confirmations: number;\n relay: string;\n}\n\nexport interface EnvelopeSignature {\n alg: 'bip322';\n pubkey: string; // sender btc address\n value: string; // base64 BIP-322\n}\n\nexport interface LockEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: EnvelopeKind;\n id: string; // 32-byte hex\n alg: EnvelopeAlg;\n from: EnvelopeFrom;\n recipients: EnvelopeRecipient[];\n ciphertext: string; // base64url\n nonce_ct: string; // 12-byte hex\n hint?: string;\n created_at: string; // iso8601 utc\n expires_at: string | null;\n payment: EnvelopePayment | null;\n sig: EnvelopeSignature;\n}\n\n// Inputs\n\nexport interface DeviceRecord {\n address: string;\n device_id: string;\n device_pk: string; // hex\n}\n\nexport interface SealInput {\n payload: Uint8Array;\n sender: {\n address: string;\n attestation_id?: string;\n /**\n * Returns a BIP-322 base64 signature of the given message bytes as UTF-8.\n * Exactly one call per seal (over the envelope id).\n */\n signMessage: (msg: string) => Promise<string>;\n };\n recipients: DeviceRecord[];\n kind?: EnvelopeKind;\n hint?: string;\n expiresAt?: Date | null;\n payment?: EnvelopePayment | null;\n}\n\nexport interface UnsealInput {\n envelope: LockEnvelope;\n device: {\n device_id: string;\n /** 32-byte X25519 private key for this device. */\n secretKey: Uint8Array;\n };\n /**\n * Called to verify the sender's BIP-322 signature. Returns true if valid.\n * The verifier is injected because verification libraries differ between\n * Node and browser contexts; consumers plug their own.\n */\n verifyBip322?: (msg: string, signatureB64: string, address: string) => Promise<boolean>;\n /** Skip sender signature verification. Default false; true only for self-seals. */\n skipSenderVerification?: boolean;\n /** Current time; defaults to Date.now(). Used for expiry enforcement. */\n now?: () => Date;\n}\n\nexport interface UnsealResult {\n payload: Uint8Array;\n envelopeId: string;\n sender: EnvelopeFrom;\n matchedDeviceId: string;\n}\n","// Seal and unseal OC Lock envelopes. See SPEC.md §4.\n\nimport {\n aesGcmDecrypt,\n aesGcmEncrypt,\n b64urlDecode,\n b64urlEncode,\n generateX25519KeyPair,\n hexDecode,\n hexEncode,\n hkdfSha256,\n randomBytesN,\n sha256Bytes,\n utf8Encode,\n x25519Shared,\n zeroize,\n} from '@orangecheck/lock-crypto';\n\nimport { canonicalBytes, canonicalize, type JsonValue } from './canonical.js';\nimport {\n ENVELOPE_VERSION,\n type EnvelopeAlg,\n type EnvelopeKind,\n type EnvelopeRecipient,\n type LockEnvelope,\n type SealInput,\n type UnsealInput,\n type UnsealResult,\n} from './types.js';\n\nconst DEFAULT_ALG: EnvelopeAlg = {\n kem: 'x25519',\n aead: 'aes-256-gcm',\n kdf: 'hkdf-sha256',\n};\n\nfunction envelopeToJson(env: LockEnvelope): JsonValue {\n return env as unknown as JsonValue;\n}\n\n/**\n * Compute the draft envelope id used as AAD during AEAD operations. This is\n * the canonical envelope with `ciphertext`, `sig`, and each\n * `recipients[*].wrapped_key` elided per SPEC §4.2 step 3.\n */\nfunction computeDraftId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n ciphertext: '',\n sig: { alg: 'bip322', pubkey: env.from.address, value: '' },\n recipients: env.recipients.map((r) => ({ ...r, wrapped_key: '' })),\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\n/**\n * Compute the final envelope id: SHA-256 of the canonical envelope with\n * `sig.value` stripped to empty. The `id` field itself is excluded from the\n * hash input (we set it to empty before hashing).\n */\nfunction computeEnvelopeId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n sig: { alg: env.sig.alg, pubkey: env.sig.pubkey, value: '' },\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\nexport async function seal(input: SealInput): Promise<LockEnvelope> {\n const kind: EnvelopeKind = input.kind ?? (input.payment ? 'payment' : 'identity');\n const now = new Date().toISOString();\n const nonce_ct = randomBytesN(12);\n\n const content_key = randomBytesN(32);\n\n // Build recipients with wrapped keys.\n const recipients: EnvelopeRecipient[] = [];\n for (const r of input.recipients) {\n const device_pk_bytes = hexDecode(r.device_pk);\n const ephemeral = generateX25519KeyPair();\n const shared = x25519Shared(ephemeral.secret, device_pk_bytes);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + r.device_id),\n 32\n );\n const nonce_kek = randomBytesN(12);\n const wrapped = aesGcmEncrypt(kek, nonce_kek, content_key, utf8Encode(r.device_id));\n zeroize(ephemeral.secret);\n zeroize(shared);\n zeroize(kek);\n recipients.push({\n address: r.address,\n device_id: r.device_id,\n device_pk: r.device_pk,\n eph_pk: hexEncode(ephemeral.public),\n wrapped_key: b64urlEncode(wrapped),\n nonce_kek: hexEncode(nonce_kek),\n });\n }\n\n // Draft envelope (id, sig, ciphertext empty) for AAD computation.\n const draftEnv: LockEnvelope = {\n v: ENVELOPE_VERSION,\n kind,\n id: '',\n alg: DEFAULT_ALG,\n from: {\n address: input.sender.address,\n ...(input.sender.attestation_id\n ? { attestation_id: input.sender.attestation_id }\n : {}),\n },\n recipients,\n ciphertext: '',\n nonce_ct: hexEncode(nonce_ct),\n ...(input.hint !== undefined ? { hint: input.hint } : {}),\n created_at: now,\n expires_at: input.expiresAt ? input.expiresAt.toISOString() : null,\n payment: input.payment ?? null,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: '' },\n };\n\n const draftId = computeDraftId(draftEnv);\n const ciphertext = aesGcmEncrypt(content_key, nonce_ct, input.payload, draftId);\n zeroize(content_key);\n\n const withCt: LockEnvelope = { ...draftEnv, ciphertext: b64urlEncode(ciphertext) };\n const envelopeIdBytes = computeEnvelopeId(withCt);\n const envelopeId = hexEncode(envelopeIdBytes);\n\n const signatureB64 = await input.sender.signMessage(envelopeId);\n\n return {\n ...withCt,\n id: envelopeId,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: signatureB64 },\n };\n}\n\nexport async function unseal(input: UnsealInput): Promise<UnsealResult> {\n const env = input.envelope;\n\n // Expiry.\n if (env.expires_at) {\n const now = input.now ? input.now() : new Date();\n if (now.getTime() > new Date(env.expires_at).getTime()) {\n throw makeError('E_EXPIRED', 'envelope is expired');\n }\n }\n\n // Recompute envelope id.\n const idBytes = computeEnvelopeId(env);\n const idHex = hexEncode(idBytes);\n if (idHex !== env.id) {\n throw makeError('E_BAD_SIG', 'envelope id mismatch');\n }\n\n // Verify sender signature unless caller opts out (self-seal).\n if (!input.skipSenderVerification) {\n if (!input.verifyBip322) {\n throw makeError('E_BAD_SIG', 'no bip322 verifier supplied');\n }\n const ok = await input.verifyBip322(env.id, env.sig.value, env.sig.pubkey);\n if (!ok) throw makeError('E_BAD_SIG', 'sender signature did not verify');\n }\n\n // Find our recipient entry.\n const mine = env.recipients.find((r) => r.device_id === input.device.device_id);\n if (!mine) throw makeError('E_NOT_ADDRESSED', 'no matching device_id in recipients');\n\n // Unwrap content key.\n const draftId = computeDraftId({ ...env, id: env.id });\n const eph_pk = hexDecode(mine.eph_pk);\n const shared = x25519Shared(input.device.secretKey, eph_pk);\n const nonce_ct = hexDecode(env.nonce_ct);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + mine.device_id),\n 32\n );\n const nonce_kek = hexDecode(mine.nonce_kek);\n const wrapped = b64urlDecode(mine.wrapped_key);\n\n let content_key: Uint8Array;\n try {\n content_key = aesGcmDecrypt(kek, nonce_kek, wrapped, utf8Encode(mine.device_id));\n } catch (e) {\n throw makeError('E_BAD_TAG', 'wrapped key failed to decrypt');\n } finally {\n zeroize(shared);\n zeroize(kek);\n }\n\n // Decrypt ciphertext.\n const ciphertext = b64urlDecode(env.ciphertext);\n let payload: Uint8Array;\n try {\n payload = aesGcmDecrypt(content_key, nonce_ct, ciphertext, draftId);\n } catch (e) {\n zeroize(content_key);\n throw makeError('E_BAD_TAG', 'ciphertext failed to decrypt');\n }\n zeroize(content_key);\n\n return {\n payload,\n envelopeId: env.id,\n sender: env.from,\n matchedDeviceId: mine.device_id,\n };\n}\n\nexport class LockError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.code = code;\n this.name = 'LockError';\n }\n}\n\nfunction makeError(code: string, message: string): LockError {\n return new LockError(code, message);\n}\n\nexport { canonicalize, canonicalBytes };\n"]}
{"version":3,"sources":["../src/canonical.ts","../src/types.ts","../src/seal.ts"],"names":["utf8Encode"],"mappings":"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","file":"seal.mjs","sourcesContent":["// RFC 8785 JSON Canonicalization Scheme with one addition: within the\n// envelope, `recipients` entries are sorted by `device_id` ascending before\n// canonicalization. See SPEC.md §5.\n//\n// We implement a small, self-contained canonicalizer. Inputs are plain JSON\n// values (objects, arrays, strings, numbers, booleans, null). Undefined and\n// functions are rejected.\n\nimport { utf8Encode } from '@orangecheck/lock-crypto';\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nconst JSON_RECIPIENTS_SORT_PATH = ['recipients'] as const;\n\nexport function canonicalize(value: JsonValue): string {\n return encode(value, []);\n}\n\nexport function canonicalBytes(value: JsonValue): Uint8Array {\n return utf8Encode(canonicalize(value) + '\\n');\n}\n\nfunction encode(v: JsonValue, path: string[]): 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 let items = v;\n // Sort recipients by device_id when we're at path ['recipients'].\n if (\n path.length === JSON_RECIPIENTS_SORT_PATH.length &&\n path.every((p, i) => p === JSON_RECIPIENTS_SORT_PATH[i]) &&\n items.every(\n (it) =>\n it &&\n typeof it === 'object' &&\n !Array.isArray(it) &&\n typeof (it as Record<string, JsonValue>).device_id === 'string'\n )\n ) {\n items = [...items].sort((a, b) => {\n const aid = (a as Record<string, JsonValue>).device_id as string;\n const bid = (b as Record<string, JsonValue>).device_id as string;\n return aid < bid ? -1 : aid > bid ? 1 : 0;\n });\n }\n for (let i = 0; i < items.length; i++) {\n parts.push(encode(items[i]!, path.concat(String(i))));\n }\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, path.concat(k)));\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 // Follow JSON.stringify for non-integers; it produces canonical-ish output\n // that matches IEEE 754 round-tripping for our use case (we never emit\n // floats ourselves).\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","// Wire types for OC Lock v2 envelopes. See SPEC.md §4.\n\nexport const ENVELOPE_VERSION = 2 as const;\n\nexport type EnvelopeKind = 'identity' | 'payment';\n\nexport interface EnvelopeAlg {\n kem: 'x25519';\n aead: 'aes-256-gcm';\n kdf: 'hkdf-sha256';\n}\n\nexport interface EnvelopeFrom {\n address: string;\n attestation_id?: string;\n}\n\nexport interface EnvelopeRecipient {\n address: string;\n device_id: string;\n device_pk: string; // 32-byte hex X25519 pubkey\n eph_pk: string; // 32-byte hex X25519 ephemeral pubkey\n wrapped_key: string; // base64url(aead(content_key, kek, nonce_kek))\n nonce_kek: string; // 12-byte hex\n}\n\nexport interface EnvelopePayment {\n amount_sats: number;\n address: string;\n confirmations: number;\n relay: string;\n}\n\nexport interface EnvelopeSignature {\n alg: 'bip322';\n pubkey: string; // sender btc address\n value: string; // base64 BIP-322\n}\n\nexport interface LockEnvelope {\n v: typeof ENVELOPE_VERSION;\n kind: EnvelopeKind;\n id: string; // 32-byte hex\n alg: EnvelopeAlg;\n from: EnvelopeFrom;\n recipients: EnvelopeRecipient[];\n ciphertext: string; // base64url\n nonce_ct: string; // 12-byte hex\n hint?: string;\n created_at: string; // iso8601 utc\n expires_at: string | null;\n payment: EnvelopePayment | null;\n sig: EnvelopeSignature;\n}\n\n// Inputs\n\nexport interface DeviceRecord {\n address: string;\n device_id: string;\n device_pk: string; // hex\n}\n\nexport interface SealInput {\n payload: Uint8Array;\n sender: {\n address: string;\n attestation_id?: string;\n /**\n * Returns a BIP-322 base64 signature of the given message bytes as UTF-8.\n * Exactly one call per seal (over the envelope id).\n */\n signMessage: (msg: string) => Promise<string>;\n };\n recipients: DeviceRecord[];\n kind?: EnvelopeKind;\n hint?: string;\n expiresAt?: Date | null;\n payment?: EnvelopePayment | null;\n}\n\nexport interface UnsealInput {\n envelope: LockEnvelope;\n device: {\n device_id: string;\n /** 32-byte X25519 private key for this device. */\n secretKey: Uint8Array;\n };\n /**\n * Called to verify the sender's BIP-322 signature. Returns true if valid.\n * The verifier is injected because verification libraries differ between\n * Node and browser contexts; consumers plug their own.\n */\n verifyBip322?: (msg: string, signatureB64: string, address: string) => Promise<boolean>;\n /** Skip sender signature verification. Default false; true only for self-seals. */\n skipSenderVerification?: boolean;\n /** Current time; defaults to Date.now(). Used for expiry enforcement. */\n now?: () => Date;\n}\n\nexport interface UnsealResult {\n payload: Uint8Array;\n envelopeId: string;\n sender: EnvelopeFrom;\n matchedDeviceId: string;\n}\n","// Seal and unseal OC Lock envelopes. See SPEC.md §4.\n\nimport {\n aesGcmDecrypt,\n aesGcmEncrypt,\n b64urlDecode,\n b64urlEncode,\n generateX25519KeyPair,\n hexDecode,\n hexEncode,\n hkdfSha256,\n randomBytesN,\n sha256Bytes,\n utf8Encode,\n x25519Shared,\n zeroize,\n} from '@orangecheck/lock-crypto';\n\nimport { canonicalBytes, canonicalize, type JsonValue } from './canonical.js';\nimport {\n ENVELOPE_VERSION,\n type EnvelopeAlg,\n type EnvelopeKind,\n type EnvelopeRecipient,\n type LockEnvelope,\n type SealInput,\n type UnsealInput,\n type UnsealResult,\n} from './types.js';\n\nconst DEFAULT_ALG: EnvelopeAlg = {\n kem: 'x25519',\n aead: 'aes-256-gcm',\n kdf: 'hkdf-sha256',\n};\n\nfunction envelopeToJson(env: LockEnvelope): JsonValue {\n return env as unknown as JsonValue;\n}\n\n/**\n * Compute the draft envelope id used as AAD during AEAD operations. This is\n * the canonical envelope with `ciphertext`, `sig`, and each\n * `recipients[*].wrapped_key` elided per SPEC §4.2 step 3.\n */\nfunction computeDraftId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n ciphertext: '',\n sig: { alg: 'bip322', pubkey: env.from.address, value: '' },\n recipients: env.recipients.map((r) => ({ ...r, wrapped_key: '' })),\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\n/**\n * Compute the final envelope id: SHA-256 of the canonical envelope with\n * `sig.value` stripped to empty. The `id` field itself is excluded from the\n * hash input (we set it to empty before hashing).\n */\nfunction computeEnvelopeId(env: LockEnvelope): Uint8Array {\n const clone: LockEnvelope = {\n ...env,\n id: '',\n sig: { alg: env.sig.alg, pubkey: env.sig.pubkey, value: '' },\n };\n const bytes = canonicalBytes(envelopeToJson(clone));\n return sha256Bytes(bytes);\n}\n\nexport async function seal(input: SealInput): Promise<LockEnvelope> {\n // An envelope with zero recipients is unreadable by anyone — the\n // content key is only wrapped per-recipient, so the caller would\n // produce a silently-unusable ciphertext. Reject upfront with a\n // clear error rather than emitting junk.\n if (!input.recipients || input.recipients.length === 0) {\n throw new LockError(\n 'E_NO_RECIPIENTS',\n 'seal() requires at least one recipient'\n );\n }\n const kind: EnvelopeKind = input.kind ?? (input.payment ? 'payment' : 'identity');\n const now = new Date().toISOString();\n const nonce_ct = randomBytesN(12);\n\n const content_key = randomBytesN(32);\n\n // Build recipients with wrapped keys.\n const recipients: EnvelopeRecipient[] = [];\n for (const r of input.recipients) {\n const device_pk_bytes = hexDecode(r.device_pk);\n const ephemeral = generateX25519KeyPair();\n const shared = x25519Shared(ephemeral.secret, device_pk_bytes);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + r.device_id),\n 32\n );\n const nonce_kek = randomBytesN(12);\n const wrapped = aesGcmEncrypt(kek, nonce_kek, content_key, utf8Encode(r.device_id));\n zeroize(ephemeral.secret);\n zeroize(shared);\n zeroize(kek);\n recipients.push({\n address: r.address,\n device_id: r.device_id,\n device_pk: r.device_pk,\n eph_pk: hexEncode(ephemeral.public),\n wrapped_key: b64urlEncode(wrapped),\n nonce_kek: hexEncode(nonce_kek),\n });\n }\n\n // Draft envelope (id, sig, ciphertext empty) for AAD computation.\n const draftEnv: LockEnvelope = {\n v: ENVELOPE_VERSION,\n kind,\n id: '',\n alg: DEFAULT_ALG,\n from: {\n address: input.sender.address,\n ...(input.sender.attestation_id\n ? { attestation_id: input.sender.attestation_id }\n : {}),\n },\n recipients,\n ciphertext: '',\n nonce_ct: hexEncode(nonce_ct),\n ...(input.hint !== undefined ? { hint: input.hint } : {}),\n created_at: now,\n expires_at: input.expiresAt ? input.expiresAt.toISOString() : null,\n payment: input.payment ?? null,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: '' },\n };\n\n const draftId = computeDraftId(draftEnv);\n const ciphertext = aesGcmEncrypt(content_key, nonce_ct, input.payload, draftId);\n zeroize(content_key);\n\n const withCt: LockEnvelope = { ...draftEnv, ciphertext: b64urlEncode(ciphertext) };\n const envelopeIdBytes = computeEnvelopeId(withCt);\n const envelopeId = hexEncode(envelopeIdBytes);\n\n const signatureB64 = await input.sender.signMessage(envelopeId);\n\n return {\n ...withCt,\n id: envelopeId,\n sig: { alg: 'bip322', pubkey: input.sender.address, value: signatureB64 },\n };\n}\n\nexport async function unseal(input: UnsealInput): Promise<UnsealResult> {\n const env = input.envelope;\n\n // Expiry.\n if (env.expires_at) {\n const now = input.now ? input.now() : new Date();\n if (now.getTime() > new Date(env.expires_at).getTime()) {\n throw makeError('E_EXPIRED', 'envelope is expired');\n }\n }\n\n // Recompute envelope id.\n const idBytes = computeEnvelopeId(env);\n const idHex = hexEncode(idBytes);\n if (idHex !== env.id) {\n throw makeError('E_BAD_SIG', 'envelope id mismatch');\n }\n\n // Verify sender signature unless caller opts out (self-seal).\n if (!input.skipSenderVerification) {\n if (!input.verifyBip322) {\n throw makeError('E_BAD_SIG', 'no bip322 verifier supplied');\n }\n const ok = await input.verifyBip322(env.id, env.sig.value, env.sig.pubkey);\n if (!ok) throw makeError('E_BAD_SIG', 'sender signature did not verify');\n }\n\n // Find our recipient entry.\n const mine = env.recipients.find((r) => r.device_id === input.device.device_id);\n if (!mine) throw makeError('E_NOT_ADDRESSED', 'no matching device_id in recipients');\n\n // Unwrap content key.\n const draftId = computeDraftId({ ...env, id: env.id });\n const eph_pk = hexDecode(mine.eph_pk);\n const shared = x25519Shared(input.device.secretKey, eph_pk);\n const nonce_ct = hexDecode(env.nonce_ct);\n const kek = hkdfSha256(\n shared,\n nonce_ct,\n utf8Encode('oc-lock/v2/kek:' + mine.device_id),\n 32\n );\n const nonce_kek = hexDecode(mine.nonce_kek);\n const wrapped = b64urlDecode(mine.wrapped_key);\n\n let content_key: Uint8Array;\n try {\n content_key = aesGcmDecrypt(kek, nonce_kek, wrapped, utf8Encode(mine.device_id));\n } catch (e) {\n throw makeError('E_BAD_TAG', 'wrapped key failed to decrypt');\n } finally {\n zeroize(shared);\n zeroize(kek);\n }\n\n // Decrypt ciphertext.\n const ciphertext = b64urlDecode(env.ciphertext);\n let payload: Uint8Array;\n try {\n payload = aesGcmDecrypt(content_key, nonce_ct, ciphertext, draftId);\n } catch (e) {\n zeroize(content_key);\n throw makeError('E_BAD_TAG', 'ciphertext failed to decrypt');\n }\n zeroize(content_key);\n\n return {\n payload,\n envelopeId: env.id,\n sender: env.from,\n matchedDeviceId: mine.device_id,\n };\n}\n\nexport class LockError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.code = code;\n this.name = 'LockError';\n }\n}\n\nfunction makeError(code: string, message: string): LockError {\n return new LockError(code, message);\n}\n\nexport { canonicalize, canonicalBytes };\n"]}
{
"name": "@orangecheck/lock-core",
"version": "0.1.0",
"description": "OC Lock envelope format, canonicalization, seal, and unseal. See https://github.com/orangecheck/oc-lock-protocol.",
"keywords": [
"bitcoin",
"encryption",
"end-to-end",
"bip322",
"oc-lock",
"orangecheck"
],
"author": "OrangeCheck",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/orangecheck/oc-packages.git",
"directory": "lock-core"
"name": "@orangecheck/lock-core",
"version": "1.0.0",
"description": "OC Lock envelope format, canonicalization, seal, and unseal. See https://github.com/orangecheck/oc-lock-protocol.",
"keywords": [
"bitcoin",
"encryption",
"end-to-end",
"bip322",
"oc-lock",
"orangecheck"
],
"author": "OrangeCheck",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/orangecheck/oc-packages.git",
"directory": "lock-core"
},
"homepage": "https://github.com/orangecheck/oc-lock-protocol",
"bugs": {
"url": "https://github.com/orangecheck/oc-lock-protocol/issues"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"homepage": "https://github.com/orangecheck/oc-lock-protocol",
"bugs": {
"url": "https://github.com/orangecheck/oc-lock-protocol/issues"
"./canonical": {
"types": "./dist/canonical.d.ts",
"import": "./dist/canonical.mjs",
"require": "./dist/canonical.js"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./canonical": {
"types": "./dist/canonical.d.ts",
"import": "./dist/canonical.mjs",
"require": "./dist/canonical.js"
},
"./seal": {
"types": "./dist/seal.d.ts",
"import": "./dist/seal.mjs",
"require": "./dist/seal.js"
},
"./types": {
"types": "./dist/types.d.ts",
"import": "./dist/types.mjs",
"require": "./dist/types.js"
}
"./seal": {
"types": "./dist/seal.d.ts",
"import": "./dist/seal.mjs",
"require": "./dist/seal.js"
},
"files": [
"dist",
"src",
"README.md",
"LICENSE"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"clean": "rm -rf dist",
"prepublishOnly": "npm run clean && npm run build"
},
"dependencies": {
"@orangecheck/lock-crypto": "^0.1.0"
},
"devDependencies": {
"@types/node": "^22.10.2",
"tsup": "^8.3.5",
"typescript": "^5.7.2",
"vitest": "^3.2.4"
"./types": {
"types": "./dist/types.d.ts",
"import": "./dist/types.mjs",
"require": "./dist/types.js"
}
},
"files": [
"dist",
"src",
"README.md",
"LICENSE"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"clean": "rm -rf dist",
"prepublishOnly": "npm run clean && npm run build"
},
"dependencies": {
"@orangecheck/lock-crypto": "file:../lock-crypto"
},
"devDependencies": {
"@types/node": "^22.10.2",
"tsup": "^8.3.5",
"typescript": "^5.7.2",
"vitest": "^3.2.4"
},
"publishConfig": {
"access": "public"
}
}

@@ -97,6 +97,11 @@ import { describe, expect, it } from 'vitest';

});
// Tamper with the id (it will not recompute correctly)
// Tamper with the id by flipping the last hex digit so the recompute
// will mismatch. Naively appending a fixed suffix like '00' silently
// turned the test into a 1/256 flake when seal() randomly produced
// an id already ending in '00'.
const last = envelope.id[envelope.id.length - 1];
const flipped = last === '0' ? 'f' : '0';
const tampered = {
...envelope,
id: envelope.id.slice(0, -2) + '00',
id: envelope.id.slice(0, -1) + flipped,
};

@@ -181,2 +186,12 @@ await expect(

});
it('rejects seal with zero recipients (would produce unreadable envelope)', async () => {
await expect(
seal({
payload: utf8Encode('hi'),
sender: { address: 'bc1qalice', signMessage: fakeSign },
recipients: [],
})
).rejects.toBeInstanceOf(LockError);
});
});

@@ -74,2 +74,12 @@ // Seal and unseal OC Lock envelopes. See SPEC.md §4.

export async function seal(input: SealInput): Promise<LockEnvelope> {
// An envelope with zero recipients is unreadable by anyone — the
// content key is only wrapped per-recipient, so the caller would
// produce a silently-unusable ciphertext. Reject upfront with a
// clear error rather than emitting junk.
if (!input.recipients || input.recipients.length === 0) {
throw new LockError(
'E_NO_RECIPIENTS',
'seal() requires at least one recipient'
);
}
const kind: EnvelopeKind = input.kind ?? (input.payment ? 'payment' : 'identity');

@@ -76,0 +86,0 @@ const now = new Date().toISOString();

@@ -8,2 +8,3 @@ // Verify every committed test vector in oc-lock-protocol/test-vectors/.

import { existsSync } from 'node:fs';
import { readdir, readFile } from 'node:fs/promises';

@@ -15,4 +16,5 @@ import { dirname, join, resolve } from 'node:path';

import { hexDecode, utf8Decode } from '@orangecheck/lock-crypto';
import { hexDecode, hexEncode, sha256Bytes, utf8Decode } from '@orangecheck/lock-crypto';
import { canonicalBytes } from './canonical.js';
import { unseal } from './seal.js';

@@ -22,4 +24,23 @@ import type { LockEnvelope } from './types.js';

const __dirname = dirname(fileURLToPath(import.meta.url));
const VECTORS_DIR = resolve(__dirname, '..', '..', '..', 'oc-lock-protocol', 'test-vectors');
/**
* Three resolution paths in priority order:
* 1. OC_LOCK_VECTORS_DIR env (CI conformance job).
* 2. Sibling-clone of oc-lock-protocol (monorepo-shaped local dev).
* 3. User-home fallback for one-off local checkouts.
* 4. null — graceful skip; the describe-block below emits an it.skip().
*/
function locateVectorsDir(): string | null {
if (process.env.OC_LOCK_VECTORS_DIR && existsSync(process.env.OC_LOCK_VECTORS_DIR)) {
return process.env.OC_LOCK_VECTORS_DIR;
}
const sibling = resolve(__dirname, '..', '..', '..', 'oc-lock-protocol', 'test-vectors');
if (existsSync(sibling)) return sibling;
const userHome = '/Users/wilneeley/Projects/ochk/oc-lock-protocol/test-vectors';
if (existsSync(userHome)) return userHome;
return null;
}
const VECTORS_DIR = locateVectorsDir();
interface VectorRecipient {

@@ -45,2 +66,3 @@ address: string;

async function loadVectors(): Promise<{ name: string; data: Vector }[]> {
if (VECTORS_DIR === null) return [];
try {

@@ -86,3 +108,23 @@ const files = await readdir(VECTORS_DIR);

});
// Cross-implementation canonical-bytes promise: sha256(canonical(envelope
// with id='' and sig.value='')) MUST equal the envelope's id field.
// The unseal tests above already rely on this via AEAD AAD binding,
// but an explicit assertion catches canonicalizer drift (key sort,
// recipient sort, number formatting, LF termination) that might
// otherwise slip through silently when payload happens to decrypt
// correctly for unrelated reasons.
it(`${name} — canonical bytes produce the declared envelope id`, () => {
const env = data.expected.envelope;
const clone: LockEnvelope = {
...env,
id: '',
sig: { alg: env.sig.alg, pubkey: env.sig.pubkey, value: '' },
};
const computedId = hexEncode(
sha256Bytes(canonicalBytes(clone as never))
);
expect(computedId).toBe(env.id);
});
}
});