/// Self-contained bech32 encode (BIP-173 checksum), enough for NIP-19 /// `npub`/`nsec`. Written inline to avoid a dependency's version quirks; NIP-19 /// is plain bech32 (checksum constant 1), not bech32m. const _charset = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; int _polymod(List values) { const gen = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; var chk = 1; for (final v in values) { final top = chk >> 25; chk = ((chk & 0x1ffffff) << 5) ^ v; for (var i = 0; i < 5; i++) { if ((top >> i) & 1 == 1) chk ^= gen[i]; } } return chk; } List _hrpExpand(String hrp) => [ ...hrp.codeUnits.map((c) => c >> 5), 0, ...hrp.codeUnits.map((c) => c & 31), ]; List _createChecksum(String hrp, List data) { final values = [..._hrpExpand(hrp), ...data, 0, 0, 0, 0, 0, 0]; final mod = _polymod(values) ^ 1; return List.generate(6, (i) => (mod >> (5 * (5 - i))) & 31); } /// Converts 8-bit bytes to 5-bit groups (bech32 payload), padding with zeros. List _convertBits8to5(List data) { var acc = 0; var bits = 0; final out = []; for (final value in data) { acc = (acc << 8) | value; bits += 8; while (bits >= 5) { bits -= 5; out.add((acc >> bits) & 31); } } if (bits > 0) out.add((acc << (5 - bits)) & 31); return out; } /// bech32-encodes [data] (raw bytes) under [hrp] (e.g. `npub`). String bech32Encode(String hrp, List data) { final five = _convertBits8to5(data); final checksum = _createChecksum(hrp, five); final combined = [...five, ...checksum]; return '${hrp}1${combined.map((v) => _charset[v]).join()}'; }