{"version":3,"file":"parseFundingOutpoints-CIO9Ok1L.cjs","sources":["../src/tbv/core/vault-secrets/context.ts","../src/tbv/core/vault-secrets/deriveVaultRoot.ts","../src/tbv/core/vault-secrets/parseFundingOutpoints.ts"],"sourcesContent":["/**\n * Canonical `vaultContext` byte encoding per\n * `derive-vault-secrets.md` §2.3.\n *\n * ```\n * vaultContext :=\n *     I2OSP(32, 4) || depositorBtcPubkey            // 32B x-only\n *  || I2OSP(32, 4) || fundingOutpointsCommitment    // 32B SHA-256\n * ```\n *\n * `fundingOutpointsCommitment` is SHA-256 over the canonically-sorted\n * funding outpoints of the Pre-PegIn transaction, serialized as\n * `txid (32B display/RPC order) || vout (4B u32 big-endian)` per\n * outpoint. Sorting by 36-byte lex order makes the commitment\n * invariant under tx-level input reordering, so same-inputs RBF and\n * reorg rebroadcasts yield the same context.\n *\n * @module vault-secrets/context\n */\n\nimport { sha256 } from \"@noble/hashes/sha2.js\";\n\nconst DEPOSITOR_PUBKEY_SIZE = 32;\nconst TXID_SIZE = 32;\nconst OUTPOINT_SIZE = 36;\nconst COMMITMENT_SIZE = 32;\nconst FIELD_LEN_PREFIX_SIZE = 4;\nconst VAULT_CONTEXT_SIZE =\n  FIELD_LEN_PREFIX_SIZE +\n  DEPOSITOR_PUBKEY_SIZE +\n  FIELD_LEN_PREFIX_SIZE +\n  COMMITMENT_SIZE;\n\nexport interface FundingOutpoint {\n  /**\n   * Bitcoin txid in **display / RPC order** (byte-reversed from the\n   * internal little-endian wire form used when hashing a raw tx).\n   */\n  txid: Uint8Array;\n  /** Output index within the referenced transaction (u32). */\n  vout: number;\n}\n\nexport interface VaultContextInput {\n  /** Depositor's x-only BTC public key (32 bytes). */\n  depositorBtcPubkey: Uint8Array;\n  /** Funding outpoints of the Pre-PegIn transaction. MUST be non-empty. */\n  fundingOutpoints: readonly FundingOutpoint[];\n}\n\nfunction writeUint32BE(out: Uint8Array, offset: number, value: number): void {\n  out[offset] = (value >>> 24) & 0xff;\n  out[offset + 1] = (value >>> 16) & 0xff;\n  out[offset + 2] = (value >>> 8) & 0xff;\n  out[offset + 3] = value & 0xff;\n}\n\nfunction serializeOutpoint(outpoint: FundingOutpoint): Uint8Array {\n  if (outpoint.txid.length !== TXID_SIZE) {\n    throw new Error(\n      `outpoint.txid must be exactly ${TXID_SIZE} bytes, got ${outpoint.txid.length}`,\n    );\n  }\n  if (\n    !Number.isInteger(outpoint.vout) ||\n    outpoint.vout < 0 ||\n    outpoint.vout > 0xffffffff\n  ) {\n    throw new Error(`outpoint.vout must be a u32, got ${outpoint.vout}`);\n  }\n  const out = new Uint8Array(OUTPOINT_SIZE);\n  out.set(outpoint.txid, 0);\n  writeUint32BE(out, TXID_SIZE, outpoint.vout);\n  return out;\n}\n\nfunction compareBytes(a: Uint8Array, b: Uint8Array): number {\n  const len = Math.min(a.length, b.length);\n  for (let i = 0; i < len; i++) {\n    if (a[i] !== b[i]) return a[i] - b[i];\n  }\n  return a.length - b.length;\n}\n\n/**\n * Compute SHA-256 over canonically-sorted funding outpoints.\n *\n * Outpoints are serialized as 36-byte `txid || vout_BE`, sorted\n * ascending lexicographically, concatenated, then hashed.\n *\n * @stability frozen — on-chain-binding. Any change to layout, sort\n * order, or serialization is a hard fork; existing deposits would no\n * longer match their committed `depositorWotsPkHash`.\n *\n * @throws If `outpoints` is empty or contains duplicates.\n */\nexport function buildFundingOutpointsCommitment(\n  outpoints: readonly FundingOutpoint[],\n): Uint8Array {\n  if (outpoints.length === 0) {\n    throw new Error(\n      \"buildFundingOutpointsCommitment: outpoints must be non-empty\",\n    );\n  }\n  const serialized = outpoints.map(serializeOutpoint);\n  serialized.sort(compareBytes);\n\n  for (let i = 1; i < serialized.length; i++) {\n    if (compareBytes(serialized[i - 1], serialized[i]) === 0) {\n      throw new Error(\n        \"buildFundingOutpointsCommitment: duplicate outpoint detected\",\n      );\n    }\n  }\n\n  const flat = new Uint8Array(serialized.length * OUTPOINT_SIZE);\n  for (let i = 0; i < serialized.length; i++) {\n    flat.set(serialized[i], i * OUTPOINT_SIZE);\n  }\n  return sha256(flat);\n}\n\n/**\n * Build the canonical `vaultContext` byte string fed into the wallet's\n * `deriveContextHash` (or a locally-implemented equivalent on the\n * app side).\n *\n * Output length is always 72 bytes.\n *\n * @stability frozen — on-chain-binding. The 72-byte layout is the\n * input to `deriveContextHash`; any change rotates the vault root and\n * therefore every WOTS key, hashlock secret, and auth anchor derived\n * from it. Existing deposits cannot be recovered after a layout change.\n */\nexport function buildVaultContext(input: VaultContextInput): Uint8Array {\n  if (input.depositorBtcPubkey.length !== DEPOSITOR_PUBKEY_SIZE) {\n    throw new Error(\n      `vaultContext: depositorBtcPubkey must be exactly ${DEPOSITOR_PUBKEY_SIZE} bytes, got ${input.depositorBtcPubkey.length}`,\n    );\n  }\n  const commitment = buildFundingOutpointsCommitment(input.fundingOutpoints);\n\n  const out = new Uint8Array(VAULT_CONTEXT_SIZE);\n  let offset = 0;\n\n  writeUint32BE(out, offset, DEPOSITOR_PUBKEY_SIZE);\n  offset += FIELD_LEN_PREFIX_SIZE;\n  out.set(input.depositorBtcPubkey, offset);\n  offset += DEPOSITOR_PUBKEY_SIZE;\n\n  writeUint32BE(out, offset, COMMITMENT_SIZE);\n  offset += FIELD_LEN_PREFIX_SIZE;\n  out.set(commitment, offset);\n\n  return out;\n}\n","/**\n * Vault-root derivation via the wallet's `deriveContextHash` API.\n *\n * Implements the canonical root source from `derive-vault-secrets.md`\n * §2.2:\n *\n * ```\n * rootDerivation = deriveContextHash(\"babylon-btc-vault\", hex(vaultContext))\n * ```\n *\n * The 32-byte output is fed directly into the {@link expandAuthAnchor},\n * {@link expandHashlockSecret}, and {@link expandWotsSeed} functions in\n * this module.\n *\n * @module vault-secrets/deriveVaultRoot\n */\n\nimport { hexToUint8Array, uint8ArrayToHex } from \"../primitives/utils/bitcoin\";\n\nimport { buildVaultContext, type VaultContextInput } from \"./context\";\n\n/**\n * The fixed `appName` passed to the wallet's `deriveContextHash` for\n * Babylon vault derivations. The wallet displays this in its approval\n * dialog. Defined by `derive-vault-secrets.md` §2.2 — must not be\n * changed without coordinating a spec revision and a downstream\n * migration plan, as it provides app-level domain separation across\n * applications using the same wallet.\n */\nexport const VAULT_APP_NAME = \"babylon-btc-vault\";\n\n/** Expected length of the wallet output in bytes per spec §2.1. */\nconst ROOT_OUTPUT_BYTES = 32;\n\n/** Expected length of the wallet output in lowercase hex chars. */\nconst ROOT_OUTPUT_HEX_LEN = ROOT_OUTPUT_BYTES * 2;\n\nconst LOWERCASE_HEX_RE = /^[0-9a-f]+$/;\n\n/**\n * Minimal structural shape for the wallet capability needed by this\n * helper. Typed against the method directly so callers can pass any\n * value that implements `deriveContextHash` — `BitcoinWallet` from\n * this SDK, `IBTCProvider` from `@babylonlabs-io/wallet-connector`,\n * or a test mock — without depending on the rest of either interface.\n */\nexport interface DeriveContextHashCapableWallet {\n  deriveContextHash(appName: string, context: string): Promise<string>;\n}\n\n/** Forward the deriveContextHash capability only when the wallet actually has it,\n * so seam guards (ensurePrePeginTermsApproval) can fire their typed error instead\n * of a mid-ceremony TypeError. */\nexport function forwardDeriveContextHash(\n  wallet: Partial<DeriveContextHashCapableWallet>,\n): Partial<DeriveContextHashCapableWallet> {\n  return typeof wallet.deriveContextHash === \"function\"\n    ? {\n        deriveContextHash: (appName, context) =>\n          wallet.deriveContextHash!(appName, context),\n      }\n    : {};\n}\n\n/**\n * Derive the 32-byte vault root from a wallet by encoding the\n * canonical {@link VaultContextInput} and forwarding to\n * `wallet.deriveContextHash`.\n *\n * Validates the wallet's output strictly: must be exactly 64\n * lowercase hex characters per `derive-context-hash.md` §2.1. A\n * conformant wallet always satisfies this, but we re-check at the\n * SDK boundary so a non-conformant wallet (or a wallet returning a\n * malformed value through a buggy adapter) fails loud here rather\n * than producing silently-wrong derived secrets downstream.\n *\n * The helper itself produces only valid spec inputs (`appName` is\n * the hardcoded `VAULT_APP_NAME`; `context` is hex of the 72-byte\n * `vaultContext`, always 144 chars lowercase), so input-side\n * validation is unnecessary.\n *\n * @param wallet - Any value implementing `deriveContextHash`.\n * @param input  - The canonical {@link VaultContextInput} that\n *                  uniquely identifies the vault. Encoded by\n *                  {@link buildVaultContext} into a 72-byte structure\n *                  before being hex-encoded for the wallet.\n * @stability frozen — on-chain-binding. The pair (`VAULT_APP_NAME`,\n * `vaultContext` encoding) is the wallet's input space; changing\n * either rotates the root and invalidates every secret derived from\n * it. `VAULT_APP_NAME` is fixed by `derive-vault-secrets.md` §2.2\n * and must never change without a coordinated spec revision.\n *\n * @returns 32-byte root suitable for {@link expandAuthAnchor},\n *          {@link expandHashlockSecret}, {@link expandWotsSeed}.\n * @throws If the wallet returns a non-64-char or non-lowercase-hex\n *         string. Errors from the wallet (user rejection,\n *         method-not-supported, etc.) propagate unchanged.\n */\nexport async function deriveVaultRoot(\n  wallet: DeriveContextHashCapableWallet,\n  input: VaultContextInput,\n): Promise<Uint8Array> {\n  const vaultContext = buildVaultContext(input);\n  const contextHex = uint8ArrayToHex(vaultContext);\n\n  const rootHex = await wallet.deriveContextHash(VAULT_APP_NAME, contextHex);\n\n  if (typeof rootHex !== \"string\") {\n    throw new Error(\n      `deriveVaultRoot: wallet must return a string, got ${typeof rootHex}`,\n    );\n  }\n  if (rootHex.length !== ROOT_OUTPUT_HEX_LEN) {\n    throw new Error(\n      `deriveVaultRoot: wallet must return a ${ROOT_OUTPUT_HEX_LEN}-character hex string (${ROOT_OUTPUT_BYTES} bytes), got length ${rootHex.length}`,\n    );\n  }\n  if (!LOWERCASE_HEX_RE.test(rootHex)) {\n    throw new Error(\n      \"deriveVaultRoot: wallet must return lowercase hex per derive-context-hash.md §2.1; got value with non-lowercase or non-hex characters\",\n    );\n  }\n\n  return hexToUint8Array(rootHex);\n}\n","/**\n * Parse a Pre-PegIn transaction's inputs into the vault-context\n * `fundingOutpoints` shape consumed by `deriveVaultRoot`. Reverses\n * the prev-txid bytes from wire-internal little-endian to display\n * order so the derivation is byte-for-byte identical to the\n * deposit-time computation.\n *\n * @module vault-secrets/parseFundingOutpoints\n */\n\nimport { Transaction } from \"bitcoinjs-lib\";\nimport { Buffer } from \"buffer\";\n\nimport type { FundingOutpoint } from \"./context\";\n\nexport function parseFundingOutpointsFromTx(\n  unsignedTxHex: string,\n): FundingOutpoint[] {\n  if (!unsignedTxHex) {\n    throw new Error(\"Pre-pegin transaction hex is empty\");\n  }\n  const cleanHex = unsignedTxHex.startsWith(\"0x\")\n    ? unsignedTxHex.slice(2)\n    : unsignedTxHex;\n  const tx = Transaction.fromHex(cleanHex);\n  if (tx.ins.length === 0) {\n    throw new Error(\"Pre-pegin transaction has no inputs\");\n  }\n  return tx.ins.map((input) => ({\n    txid: Uint8Array.from(Buffer.from(input.hash).reverse()),\n    vout: input.index,\n  }));\n}\n"],"names":["DEPOSITOR_PUBKEY_SIZE","TXID_SIZE","OUTPOINT_SIZE","COMMITMENT_SIZE","FIELD_LEN_PREFIX_SIZE","VAULT_CONTEXT_SIZE","writeUint32BE","out","offset","value","serializeOutpoint","outpoint","compareBytes","a","b","len","i","buildFundingOutpointsCommitment","outpoints","serialized","flat","sha256","buildVaultContext","input","commitment","VAULT_APP_NAME","ROOT_OUTPUT_BYTES","ROOT_OUTPUT_HEX_LEN","LOWERCASE_HEX_RE","forwardDeriveContextHash","wallet","appName","context","deriveVaultRoot","vaultContext","contextHex","uint8ArrayToHex","rootHex","hexToUint8Array","parseFundingOutpointsFromTx","unsignedTxHex","cleanHex","tx","Transaction","Buffer"],"mappings":"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"}