Last active
February 17, 2021 15:20
-
-
Save junderw/3de76b6c203f4cfbe62ed06bd83adfe3 to your computer and use it in GitHub Desktop.
Bitcoin Output Descriptor Checksum algorithm in JavaScript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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