OverviewFilesCommitsIssuesDocsReleasesPeers

pearbrowser-desktop / backend / nip19.cjs

66 lines · javascript ref:
// NIP-19 npub encoding — bech32 (BIP-173) over the x-only Nostr pubkey, for
// human-facing display ONLY (npub1…). Never on the wire (events use raw hex).
// Encode-only; PURE, CommonJS (Bare requires it, Node tests it).

const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
const HEX64 = /^[0-9a-f]{64}$/i

function polymod (values) {
  const GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
  let chk = 1
  for (const v of values) {
    const top = chk >>> 25
    chk = ((chk & 0x1ffffff) << 5) ^ v
    for (let i = 0; i < 5; i++) if ((top >> i) & 1) chk ^= GEN[i]
  }
  return chk
}

function hrpExpand (hrp) {
  const out = []
  for (let i = 0; i < hrp.length; i++) out.push(hrp.charCodeAt(i) >> 5)
  out.push(0)
  for (let i = 0; i < hrp.length; i++) out.push(hrp.charCodeAt(i) & 31)
  return out
}

function createChecksum (hrp, data) {
  const values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0])
  const mod = polymod(values) ^ 1
  const out = []
  for (let i = 0; i < 6; i++) out.push((mod >> (5 * (5 - i))) & 31)
  return out
}

function bech32Encode (hrp, data) {
  const combined = data.concat(createChecksum(hrp, data))
  let s = hrp + '1'
  for (const d of combined) s += CHARSET[d]
  return s
}

// 8-bit bytes → 5-bit groups (with padding, as bech32 data requires).
function convertBits (bytes, from, to, pad) {
  let acc = 0
  let bits = 0
  const out = []
  const maxv = (1 << to) - 1
  for (const b of bytes) {
    if (b < 0 || (b >> from) !== 0) throw new Error('invalid byte for convertBits')
    acc = (acc << from) | b
    bits += from
    while (bits >= to) { bits -= to; out.push((acc >> bits) & maxv) }
  }
  if (pad && bits > 0) out.push((acc << (to - bits)) & maxv)
  return out
}

function npubEncode (pubkeyHex) {
  if (typeof pubkeyHex !== 'string' || !HEX64.test(pubkeyHex)) throw new Error('npubEncode expects a 64-hex x-only pubkey')
  const bytes = []
  for (let i = 0; i < 64; i += 2) bytes.push(parseInt(pubkeyHex.substr(i, 2), 16))
  return bech32Encode('npub', convertBits(bytes, 8, 5, true))
}

module.exports = { npubEncode, bech32Encode, convertBits }