{"version":3,"file":"index.cjs","names":["result: number[]","checksum: number[]","amountStr: string","data: number[]","secp256k1"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * BOLT11 Fake Invoice Generator\n *\n * Generates fake but spec-compliant BOLT11 invoices for testing purposes.\n * Based on BOLT #11: Invoice Protocol for Lightning Payments\n * https://github.com/lightning/bolts/blob/master/11-payment-encoding.md\n */\n\nimport { secp256k1 } from \"@noble/curves/secp256k1.js\";\nimport { sha256 } from \"@noble/hashes/sha2.js\";\n\n// Bech32 character set\nconst BECH32_CHARSET = \"qpzry9x8gf2tvdw0s3jn54khce6mua7l\";\n\n// Generator polynomial for bech32\nconst BECH32_GENERATOR = [\n  0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3,\n];\n\n/**\n * Compute bech32 checksum\n */\nfunction bech32Polymod(values: number[]): number {\n  let chk = 1;\n  for (const v of values) {\n    const top = chk >> 25;\n    chk = ((chk & 0x1ffffff) << 5) ^ v;\n    for (let i = 0; i < 5; i++) {\n      if ((top >> i) & 1) {\n        chk ^= BECH32_GENERATOR[i]!;\n      }\n    }\n  }\n  return chk;\n}\n\n/**\n * Expand the human-readable part for checksum calculation\n */\nfunction bech32HrpExpand(hrp: string): number[] {\n  const result: number[] = [];\n  for (let i = 0; i < hrp.length; i++) {\n    result.push(hrp.charCodeAt(i) >> 5);\n  }\n  result.push(0);\n  for (let i = 0; i < hrp.length; i++) {\n    result.push(hrp.charCodeAt(i) & 31);\n  }\n  return result;\n}\n\n/**\n * Create bech32 checksum\n */\nfunction bech32CreateChecksum(hrp: string, data: number[]): number[] {\n  const values = bech32HrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);\n  const polymod = bech32Polymod(values) ^ 1;\n  const checksum: number[] = [];\n  for (let i = 0; i < 6; i++) {\n    checksum.push((polymod >> (5 * (5 - i))) & 31);\n  }\n  return checksum;\n}\n\n/**\n * Encode data to bech32 string\n */\nfunction bech32Encode(hrp: string, data: number[]): string {\n  const checksum = bech32CreateChecksum(hrp, data);\n  const combined = data.concat(checksum);\n  let result = hrp + \"1\";\n  for (const d of combined) {\n    result += BECH32_CHARSET[d];\n  }\n  return result;\n}\n\n/**\n * Convert a byte array to 5-bit groups\n */\nfunction convertBits(\n  data: Uint8Array,\n  fromBits: number,\n  toBits: number,\n  pad: boolean\n): number[] {\n  let acc = 0;\n  let bits = 0;\n  const result: number[] = [];\n  const maxv = (1 << toBits) - 1;\n\n  for (const value of data) {\n    acc = (acc << fromBits) | value;\n    bits += fromBits;\n    while (bits >= toBits) {\n      bits -= toBits;\n      result.push((acc >> bits) & maxv);\n    }\n  }\n\n  if (pad) {\n    if (bits > 0) {\n      result.push((acc << (toBits - bits)) & maxv);\n    }\n  } else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv) !== 0) {\n    throw new Error(\"Invalid bit conversion\");\n  }\n\n  return result;\n}\n\n/**\n * Convert 5-bit groups back to bytes\n * Per BOLT11: pads remaining bits with zeros to byte boundary\n */\nfunction convertBitsToBytes(data: number[]): Uint8Array {\n  let acc = 0;\n  let bits = 0;\n  const result: number[] = [];\n\n  for (const value of data) {\n    acc = (acc << 5) | value;\n    bits += 5;\n    while (bits >= 8) {\n      bits -= 8;\n      result.push((acc >> bits) & 0xff);\n    }\n  }\n\n  // Pad remaining bits with zeros to byte boundary (required by BOLT11)\n  if (bits > 0) {\n    result.push((acc << (8 - bits)) & 0xff);\n  }\n\n  return new Uint8Array(result);\n}\n\n/**\n * Encode a number to 5-bit groups with specified length\n */\nfunction encodeNumber(num: number, length: number): number[] {\n  const result: number[] = [];\n  for (let i = length - 1; i >= 0; i--) {\n    result.push((num >> (i * 5)) & 31);\n  }\n  return result;\n}\n\n/**\n * Generate a random hex string of specified length (in bytes)\n */\nfunction randomHex(bytes: number): string {\n  const array = new Uint8Array(bytes);\n  crypto.getRandomValues(array);\n  return Array.from(array)\n    .map((b) => b.toString(16).padStart(2, \"0\"))\n    .join(\"\");\n}\n\n/**\n * Convert hex string to Uint8Array\n */\nfunction hexToBytes(hex: string): Uint8Array {\n  const bytes = new Uint8Array(hex.length / 2);\n  for (let i = 0; i < hex.length; i += 2) {\n    bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);\n  }\n  return bytes;\n}\n\n/**\n * Encode a tagged field\n * Format: type (5 bits) + data_length (10 bits) + data\n */\nfunction encodeTaggedField(type: string, data: number[]): number[] {\n  const typeValue = BECH32_CHARSET.indexOf(type);\n  const length = data.length;\n  // Length is encoded as two 5-bit values (10 bits total)\n  const lengthHigh = (length >> 5) & 31;\n  const lengthLow = length & 31;\n  return [typeValue, lengthHigh, lengthLow, ...data];\n}\n\nexport type FakeInvoiceOptions = {\n  /** Network prefix: 'bc' for mainnet, 'tb' for testnet, 'bcrt' for regtest. Default: 'bcrt' */\n  network?: \"bc\" | \"tb\" | \"bcrt\" | \"tbs\";\n  /** Description string. Default: 'Test invoice' */\n  description?: string;\n  /** Expiry in seconds. Default: 3600 (1 hour) */\n  expiry?: number;\n  /** Payment hash (32 bytes hex). Default: random */\n  paymentHash?: string;\n  /** Payment secret (32 bytes hex). Default: random */\n  paymentSecret?: string;\n  /** Timestamp in seconds since epoch. Default: current time */\n  timestamp?: number;\n  /** Private key for signing (32 bytes hex). Default: random */\n  privateKey?: string;\n};\n\n/**\n * Creates a fake but spec-compliant BOLT11 invoice for testing purposes.\n *\n * The generated invoice follows the BOLT11 specification with a valid cryptographic\n * signature. It can be used for testing melt quote creation and payment flows.\n *\n * Note: While the invoice has a valid signature, it cannot actually be paid on a\n * real Lightning Network since the destination node doesn't exist.\n *\n * @param amountSats - Amount in satoshis\n * @param options - Optional configuration\n * @returns A BOLT11 encoded invoice string\n *\n * @example\n * ```ts\n * const invoice = createFakeInvoice(100);\n * // Returns: lnbcrt1u1p...\n * ```\n */\nexport function createFakeInvoice(\n  amountSats: number,\n  options: FakeInvoiceOptions = {}\n): string {\n  const {\n    network = \"bc\",\n    description = \"Test invoice\",\n    expiry = 3600,\n    paymentHash = randomHex(32),\n    paymentSecret = randomHex(32),\n    timestamp = Math.floor(Date.now() / 1000),\n    privateKey = randomHex(32),\n  } = options;\n\n  // Build human-readable part: ln + network + amount\n  // Amount encoding: use the smallest multiplier that results in an integer\n  let amountStr: string;\n  const amountMsat = amountSats * 1000;\n\n  if (amountMsat % 100000000000 === 0) {\n    // Can express in BTC\n    amountStr = String(amountMsat / 100000000000);\n  } else if (amountMsat % 100000000 === 0) {\n    // Can express in milli-BTC\n    amountStr = String(amountMsat / 100000000) + \"m\";\n  } else if (amountMsat % 100000 === 0) {\n    // Can express in micro-BTC\n    amountStr = String(amountMsat / 100000) + \"u\";\n  } else if (amountMsat % 100 === 0) {\n    // Can express in nano-BTC\n    amountStr = String(amountMsat / 100) + \"n\";\n  } else {\n    // Must use pico-BTC (msat * 10)\n    amountStr = String(amountMsat * 10) + \"p\";\n  }\n\n  const hrp = `ln${network}${amountStr}`;\n\n  // Build data part (without signature)\n  const data: number[] = [];\n\n  // Timestamp: 35 bits (7 x 5-bit groups)\n  data.push(...encodeNumber(timestamp, 7));\n\n  // Tagged fields:\n\n  // Payment hash (p): 52 x 5-bit groups = 260 bits = 32.5 bytes, we use 32 bytes\n  const paymentHashBytes = hexToBytes(paymentHash);\n  const paymentHashData = convertBits(paymentHashBytes, 8, 5, true);\n  data.push(...encodeTaggedField(\"p\", paymentHashData));\n\n  // Payment secret (s): 52 x 5-bit groups\n  const paymentSecretBytes = hexToBytes(paymentSecret);\n  const paymentSecretData = convertBits(paymentSecretBytes, 8, 5, true);\n  data.push(...encodeTaggedField(\"s\", paymentSecretData));\n\n  // Description (d): variable length\n  const descriptionBytes = new TextEncoder().encode(description);\n  const descriptionData = convertBits(descriptionBytes, 8, 5, true);\n  data.push(...encodeTaggedField(\"d\", descriptionData));\n\n  // Expiry (x): variable length\n  // Find minimum number of 5-bit groups needed\n  let expiryBits = 5;\n  while (1 << expiryBits <= expiry) {\n    expiryBits += 5;\n  }\n  const expiryGroups = Math.ceil(expiryBits / 5);\n  const expiryData = encodeNumber(expiry, expiryGroups);\n  data.push(...encodeTaggedField(\"x\", expiryData));\n\n  // Feature bits (9): basic features\n  // Set bit 8 (var_onion_optin) and bit 14 (payment_secret) as required\n  // Binary: 0100000100000000 (reading right to left, bits 8 and 14)\n  // Encoded as 5-bit groups: [0, 16, 8, 0]\n  const featureData = [0, 16, 8, 0];\n  data.push(...encodeTaggedField(\"9\", featureData));\n\n  // Create the message to sign\n  // From BOLT11 spec: signature is over SHA256 of (hrp as UTF-8 bytes + data part as bytes)\n  // The data is converted from 5-bit groups to 8-bit bytes, with 0 bits padding if needed\n  const hrpBytes = new TextEncoder().encode(hrp);\n  const dataBytes = convertBitsToBytes(data);\n\n  // Concatenate hrp and data bytes\n  const messageBytes = new Uint8Array(hrpBytes.length + dataBytes.length);\n  messageBytes.set(hrpBytes, 0);\n  messageBytes.set(dataBytes, hrpBytes.length);\n\n  // Hash the message\n  const messageHash = sha256(messageBytes);\n\n  // Sign with secp256k1\n  const privKeyBytes = hexToBytes(privateKey);\n  const sigWithRecovery = secp256k1.sign(messageHash, privKeyBytes, {\n    prehash: false,\n    format: \"recovered\",\n  });\n\n  // Noble outputs: recovery || R || S (65 bytes)\n  // BOLT11 needs:  R || S || recovery (65 bytes)\n  // Rearrange bytes to match BOLT11 spec\n  const sigBytes = new Uint8Array(65);\n  sigBytes.set(sigWithRecovery.subarray(1, 65), 0); // R || S\n  sigBytes[64] = sigWithRecovery[0]!; // recovery byte at end\n\n  // Convert signature to 5-bit groups and append to data\n  const signatureData = convertBits(sigBytes, 8, 5, true);\n  data.push(...signatureData);\n\n  return bech32Encode(hrp, data);\n}\n"],"mappings":";;;;;;;;;;;AAYA,MAAM,iBAAiB;AAGvB,MAAM,mBAAmB;CACvB;CAAY;CAAY;CAAY;CAAY;CACjD;;;;AAKD,SAAS,cAAc,QAA0B;CAC/C,IAAI,MAAM;AACV,MAAK,MAAM,KAAK,QAAQ;EACtB,MAAM,MAAM,OAAO;AACnB,SAAQ,MAAM,aAAc,IAAK;AACjC,OAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IACrB,KAAK,OAAO,IAAK,EACf,QAAO,iBAAiB;;AAI9B,QAAO;;;;;AAMT,SAAS,gBAAgB,KAAuB;CAC9C,MAAMA,SAAmB,EAAE;AAC3B,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,IAC9B,QAAO,KAAK,IAAI,WAAW,EAAE,IAAI,EAAE;AAErC,QAAO,KAAK,EAAE;AACd,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,IAC9B,QAAO,KAAK,IAAI,WAAW,EAAE,GAAG,GAAG;AAErC,QAAO;;;;;AAMT,SAAS,qBAAqB,KAAa,MAA0B;CAEnE,MAAM,UAAU,cADD,gBAAgB,IAAI,CAAC,OAAO,KAAK,CAAC,OAAO;EAAC;EAAG;EAAG;EAAG;EAAG;EAAG;EAAE,CAAC,CACtC,GAAG;CACxC,MAAMC,WAAqB,EAAE;AAC7B,MAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IACrB,UAAS,KAAM,WAAY,KAAK,IAAI,KAAO,GAAG;AAEhD,QAAO;;;;;AAMT,SAAS,aAAa,KAAa,MAAwB;CACzD,MAAM,WAAW,qBAAqB,KAAK,KAAK;CAChD,MAAM,WAAW,KAAK,OAAO,SAAS;CACtC,IAAI,SAAS,MAAM;AACnB,MAAK,MAAM,KAAK,SACd,WAAU,eAAe;AAE3B,QAAO;;;;;AAMT,SAAS,YACP,MACA,UACA,QACA,KACU;CACV,IAAI,MAAM;CACV,IAAI,OAAO;CACX,MAAMD,SAAmB,EAAE;CAC3B,MAAM,QAAQ,KAAK,UAAU;AAE7B,MAAK,MAAM,SAAS,MAAM;AACxB,QAAO,OAAO,WAAY;AAC1B,UAAQ;AACR,SAAO,QAAQ,QAAQ;AACrB,WAAQ;AACR,UAAO,KAAM,OAAO,OAAQ,KAAK;;;AAIrC,KAAI,KACF;MAAI,OAAO,EACT,QAAO,KAAM,OAAQ,SAAS,OAAS,KAAK;YAErC,QAAQ,aAAc,OAAQ,SAAS,OAAS,UAAU,EACnE,OAAM,IAAI,MAAM,yBAAyB;AAG3C,QAAO;;;;;;AAOT,SAAS,mBAAmB,MAA4B;CACtD,IAAI,MAAM;CACV,IAAI,OAAO;CACX,MAAMA,SAAmB,EAAE;AAE3B,MAAK,MAAM,SAAS,MAAM;AACxB,QAAO,OAAO,IAAK;AACnB,UAAQ;AACR,SAAO,QAAQ,GAAG;AAChB,WAAQ;AACR,UAAO,KAAM,OAAO,OAAQ,IAAK;;;AAKrC,KAAI,OAAO,EACT,QAAO,KAAM,OAAQ,IAAI,OAAS,IAAK;AAGzC,QAAO,IAAI,WAAW,OAAO;;;;;AAM/B,SAAS,aAAa,KAAa,QAA0B;CAC3D,MAAMA,SAAmB,EAAE;AAC3B,MAAK,IAAI,IAAI,SAAS,GAAG,KAAK,GAAG,IAC/B,QAAO,KAAM,OAAQ,IAAI,IAAM,GAAG;AAEpC,QAAO;;;;;AAMT,SAAS,UAAU,OAAuB;CACxC,MAAM,QAAQ,IAAI,WAAW,MAAM;AACnC,QAAO,gBAAgB,MAAM;AAC7B,QAAO,MAAM,KAAK,MAAM,CACrB,KAAK,MAAM,EAAE,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,CAC3C,KAAK,GAAG;;;;;AAMb,SAAS,WAAW,KAAyB;CAC3C,MAAM,QAAQ,IAAI,WAAW,IAAI,SAAS,EAAE;AAC5C,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK,EACnC,OAAM,IAAI,KAAK,SAAS,IAAI,MAAM,GAAG,IAAI,EAAE,EAAE,GAAG;AAElD,QAAO;;;;;;AAOT,SAAS,kBAAkB,MAAc,MAA0B;CACjE,MAAM,YAAY,eAAe,QAAQ,KAAK;CAC9C,MAAM,SAAS,KAAK;AAIpB,QAAO;EAAC;EAFY,UAAU,IAAK;EACjB,SAAS;EACe,GAAG;EAAK;;;;;;;;;;;;;;;;;;;;;AAuCpD,SAAgB,kBACd,YACA,UAA8B,EAAE,EACxB;CACR,MAAM,EACJ,UAAU,MACV,cAAc,gBACd,SAAS,MACT,cAAc,UAAU,GAAG,EAC3B,gBAAgB,UAAU,GAAG,EAC7B,YAAY,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,EACzC,aAAa,UAAU,GAAG,KACxB;CAIJ,IAAIE;CACJ,MAAM,aAAa,aAAa;AAEhC,KAAI,aAAa,SAAiB,EAEhC,aAAY,OAAO,aAAa,KAAa;UACpC,aAAa,QAAc,EAEpC,aAAY,OAAO,aAAa,IAAU,GAAG;UACpC,aAAa,QAAW,EAEjC,aAAY,OAAO,aAAa,IAAO,GAAG;UACjC,aAAa,QAAQ,EAE9B,aAAY,OAAO,aAAa,IAAI,GAAG;KAGvC,aAAY,OAAO,aAAa,GAAG,GAAG;CAGxC,MAAM,MAAM,KAAK,UAAU;CAG3B,MAAMC,OAAiB,EAAE;AAGzB,MAAK,KAAK,GAAG,aAAa,WAAW,EAAE,CAAC;CAMxC,MAAM,kBAAkB,YADC,WAAW,YAAY,EACM,GAAG,GAAG,KAAK;AACjE,MAAK,KAAK,GAAG,kBAAkB,KAAK,gBAAgB,CAAC;CAIrD,MAAM,oBAAoB,YADC,WAAW,cAAc,EACM,GAAG,GAAG,KAAK;AACrE,MAAK,KAAK,GAAG,kBAAkB,KAAK,kBAAkB,CAAC;CAIvD,MAAM,kBAAkB,YADC,IAAI,aAAa,CAAC,OAAO,YAAY,EACR,GAAG,GAAG,KAAK;AACjE,MAAK,KAAK,GAAG,kBAAkB,KAAK,gBAAgB,CAAC;CAIrD,IAAI,aAAa;AACjB,QAAO,KAAK,cAAc,OACxB,eAAc;CAGhB,MAAM,aAAa,aAAa,QADX,KAAK,KAAK,aAAa,EAAE,CACO;AACrD,MAAK,KAAK,GAAG,kBAAkB,KAAK,WAAW,CAAC;AAOhD,MAAK,KAAK,GAAG,kBAAkB,KADX;EAAC;EAAG;EAAI;EAAG;EAAE,CACe,CAAC;CAKjD,MAAM,WAAW,IAAI,aAAa,CAAC,OAAO,IAAI;CAC9C,MAAM,YAAY,mBAAmB,KAAK;CAG1C,MAAM,eAAe,IAAI,WAAW,SAAS,SAAS,UAAU,OAAO;AACvE,cAAa,IAAI,UAAU,EAAE;AAC7B,cAAa,IAAI,WAAW,SAAS,OAAO;CAG5C,MAAM,iDAAqB,aAAa;CAGxC,MAAM,eAAe,WAAW,WAAW;CAC3C,MAAM,kBAAkBC,sCAAU,KAAK,aAAa,cAAc;EAChE,SAAS;EACT,QAAQ;EACT,CAAC;CAKF,MAAM,WAAW,IAAI,WAAW,GAAG;AACnC,UAAS,IAAI,gBAAgB,SAAS,GAAG,GAAG,EAAE,EAAE;AAChD,UAAS,MAAM,gBAAgB;CAG/B,MAAM,gBAAgB,YAAY,UAAU,GAAG,GAAG,KAAK;AACvD,MAAK,KAAK,GAAG,cAAc;AAE3B,QAAO,aAAa,KAAK,KAAK"}