|
1 | 1 | import { always, applySpec, cond, equals, ifElse, is, isNil, multiply, path, pathSatisfies, pipe, prop, propSatisfies, T } from 'ramda' |
2 | | -import { bech32 } from 'bech32' |
3 | 2 |
|
4 | 3 | import { Invoice, InvoiceStatus, InvoiceUnit } from '../@types/invoice' |
5 | 4 | import { User } from '../@types/user' |
6 | 5 |
|
| 6 | +const BECH32_ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' |
| 7 | +const BECH32_ALPHABET_MAP: Record<string, number> = {} |
| 8 | +for (let i = 0; i < BECH32_ALPHABET.length; i++) { BECH32_ALPHABET_MAP[BECH32_ALPHABET[i]] = i } |
| 9 | + |
| 10 | +function bech32PolymodStep(pre: number): number { |
| 11 | + const b = pre >> 25 |
| 12 | + return (((pre & 0x1ffffff) << 5) ^ |
| 13 | + (-((b >> 0) & 1) & 0x3b6a57b2) ^ |
| 14 | + (-((b >> 1) & 1) & 0x26508e6d) ^ |
| 15 | + (-((b >> 2) & 1) & 0x1ea119fa) ^ |
| 16 | + (-((b >> 3) & 1) & 0x3d4233dd) ^ |
| 17 | + (-((b >> 4) & 1) & 0x2a1462b3)) |
| 18 | +} |
| 19 | + |
| 20 | +function bech32PrefixChk(prefix: string): number { |
| 21 | + let chk = 1 |
| 22 | + for (let i = 0; i < prefix.length; ++i) { |
| 23 | + const c = prefix.charCodeAt(i) |
| 24 | + chk = bech32PolymodStep(chk) ^ (c >> 5) |
| 25 | + } |
| 26 | + chk = bech32PolymodStep(chk) |
| 27 | + for (let i = 0; i < prefix.length; ++i) { |
| 28 | + chk = bech32PolymodStep(chk) ^ (prefix.charCodeAt(i) & 0x1f) |
| 29 | + } |
| 30 | + return chk |
| 31 | +} |
| 32 | + |
| 33 | +function bech32Convert(data: number[], inBits: number, outBits: number, pad: boolean): number[] { |
| 34 | + let value = 0, bits = 0 |
| 35 | + const maxV = (1 << outBits) - 1 |
| 36 | + const result: number[] = [] |
| 37 | + for (const byte of data) { |
| 38 | + value = (value << inBits) | byte |
| 39 | + bits += inBits |
| 40 | + while (bits >= outBits) { |
| 41 | + bits -= outBits |
| 42 | + result.push((value >> bits) & maxV) |
| 43 | + } |
| 44 | + } |
| 45 | + if (pad && bits > 0) { result.push((value << (outBits - bits)) & maxV) } |
| 46 | + return result |
| 47 | +} |
| 48 | + |
| 49 | +function bech32Decode(str: string): { prefix: string; words: number[] } { |
| 50 | + const lower = str.toLowerCase() |
| 51 | + const split = lower.lastIndexOf('1') |
| 52 | + if (split < 1 || split + 7 > str.length) { throw new Error(`Invalid bech32: ${str}`) } |
| 53 | + const prefix = lower.slice(0, split) |
| 54 | + const wordChars = lower.slice(split + 1) |
| 55 | + let chk = bech32PrefixChk(prefix) |
| 56 | + const words: number[] = [] |
| 57 | + for (let i = 0; i < wordChars.length; ++i) { |
| 58 | + const v = BECH32_ALPHABET_MAP[wordChars[i]] |
| 59 | + if (v === undefined) { throw new Error(`Unknown bech32 character: ${wordChars[i]}`) } |
| 60 | + chk = bech32PolymodStep(chk) ^ v |
| 61 | + if (i + 6 < wordChars.length) { words.push(v) } |
| 62 | + } |
| 63 | + if (chk !== 1) { throw new Error('Invalid bech32 checksum') } |
| 64 | + return { prefix, words } |
| 65 | +} |
| 66 | + |
| 67 | +function bech32Encode(prefix: string, words: number[]): string { |
| 68 | + prefix = prefix.toLowerCase() |
| 69 | + let chk = bech32PrefixChk(prefix) |
| 70 | + let result = prefix + '1' |
| 71 | + for (const w of words) { |
| 72 | + chk = bech32PolymodStep(chk) ^ w |
| 73 | + result += BECH32_ALPHABET[w] |
| 74 | + } |
| 75 | + for (let i = 0; i < 6; ++i) { chk = bech32PolymodStep(chk) } |
| 76 | + chk ^= 1 |
| 77 | + for (let i = 0; i < 6; ++i) { result += BECH32_ALPHABET[(chk >> ((5 - i) * 5)) & 0x1f] } |
| 78 | + return result |
| 79 | +} |
| 80 | + |
7 | 81 | export const toJSON = (input: any) => JSON.stringify(input) |
8 | 82 |
|
9 | 83 | export const toBuffer = (input: any) => Buffer.from(input, 'hex') |
@@ -46,18 +120,18 @@ export const fromDBUser = applySpec<User>({ |
46 | 120 | }) |
47 | 121 |
|
48 | 122 | export const fromBech32 = (input: string) => { |
49 | | - const { prefix, words } = bech32.decode(input) |
| 123 | + const { prefix, words } = bech32Decode(input) |
50 | 124 | if (!input.startsWith(prefix)) { |
51 | 125 | throw new Error(`Bech32 invalid prefix: ${prefix}`) |
52 | 126 | } |
53 | 127 |
|
54 | 128 | return Buffer.from( |
55 | | - bech32.fromWords(words).slice(0, 32) |
| 129 | + bech32Convert(words, 5, 8, false).slice(0, 32) |
56 | 130 | ).toString('hex') |
57 | 131 | } |
58 | 132 |
|
59 | 133 | export const toBech32 = (prefix: string) => (input: string): string => { |
60 | | - return bech32.encode(prefix, bech32.toWords(Buffer.from(input, 'hex'))) |
| 134 | + return bech32Encode(prefix, bech32Convert(Array.from(Buffer.from(input, 'hex')), 8, 5, true)) |
61 | 135 | } |
62 | 136 |
|
63 | 137 | export const toDate = (input: string | number) => new Date(input) |
|
0 commit comments