Skip to content

Instantly share code, notes, and snippets.

@junderw
Last active February 17, 2021 15:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save junderw/3de76b6c203f4cfbe62ed06bd83adfe3 to your computer and use it in GitHub Desktop.
Save junderw/3de76b6c203f4cfbe62ed06bd83adfe3 to your computer and use it in GitHub Desktop.
Bitcoin Output Descriptor Checksum algorithm in JavaScript
/*
* input: "wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)"
* output: "wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)#qwlqgth7"
* (This has been checked to match bitcoin-core)
*/
function descriptorChecksum(desc) {
if (!(typeof desc === 'string' || desc instanceof String)) throw new Error('desc must be string')
const descParts = desc.match(/^(.*?)(?:#([qpzry9x8gf2tvdw0s3jn54khce6mua7l]{8}))?$/);
if (descParts[1] === '') throw new Error('desc string must not be empty')
const INPUT_CHARSET = '0123456789()[],\'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#"\\ ';
const CHECKSUM_CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
const MOD_CONSTS = [
0xf5dee51989,
0xa9fdca3312,
0x1bab10e32d,
0x3706b1677a,
0x644d626ffd,
];
const BIT35 = 0x800000000; // Math.pow(2, 35)
const BIT31 = 0x80000000; // Math.pow(2, 31)
const BIT5 = 0x20; // Math.pow(2, 5)
function polyMod(c, val) {
const c0 = Math.floor(c / BIT35);
let ret = xor5Byte((c % BIT35) * BIT5, val)
if (c0 & 1) ret = xor5Byte(ret, MOD_CONSTS[0])
if (c0 & 2) ret = xor5Byte(ret, MOD_CONSTS[1])
if (c0 & 4) ret = xor5Byte(ret, MOD_CONSTS[2])
if (c0 & 8) ret = xor5Byte(ret, MOD_CONSTS[3])
if (c0 & 16) ret = xor5Byte(ret, MOD_CONSTS[4])
return ret
}
function xor5Byte(a, b) {
const a1 = Math.floor(a / BIT31);
const a2 = a % BIT31
const b1 = Math.floor(b / BIT31);
const b2 = b % BIT31
return (a1 ^ b1) * BIT31 + (a2 ^ b2)
}
let c = 1
let cls = 0
let clscount = 0
for (const ch of descParts[1]) {
const pos = INPUT_CHARSET.indexOf(ch)
if (pos === -1) return ''
c = polyMod(c, pos & 31)
cls = cls * 3 + (pos >> 5)
clscount++
if (clscount === 3) {
c = polyMod(c, cls)
cls = 0
clscount = 0
}
}
if (clscount > 0) {
c = polyMod(c, cls)
}
for (let i = 0; i < 8; i++) {
c = polyMod(c, 0)
}
c = xor5Byte(c, 1)
const arr = []
for (let i = 0; i < 8; i++) {
arr.push(CHECKSUM_CHARSET.charAt(Math.floor(c / Math.pow(2, (5 * (7 - i)))) % BIT5))
}
const checksum = arr.join('')
if (descParts[2] !== undefined && descParts[2] !== checksum) throw new Error('Checksum Mismatch')
return `${descParts[1]}#${checksum}`
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment