Last active
April 27, 2022 21:01
-
-
Save bellbind/1f07f94e5ce31557ef23dc2a9b3cc2e1 to your computer and use it in GitHub Desktop.
[ECMAScript][BigInt] base58 encoder/decoder algorithm on JavaScript and base58check on node.js
This file contains 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
// Bitcoin Base58 encoder/decoder algorithm | |
const btcTable = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; | |
console.assert(btcTable.length === 58); | |
// Base58 decoder/encoder for BigInt | |
function b58ToBi(chars, table = btcTable) { | |
const carry = BigInt(table.length); | |
let total = 0n, base = 1n; | |
for (let i = chars.length - 1; i >= 0; i--) { | |
const n = table.indexOf(chars[i]); | |
if (n < 0) throw TypeError(`invalid letter contained: '${chars[i]}'`); | |
total += base * BigInt(n); | |
base *= carry; | |
} | |
return total; | |
} | |
function biToB58(num, table = btcTable) { | |
const carry = BigInt(table.length); | |
let r = []; | |
while (num > 0n) { | |
r.unshift(table[num % carry]); | |
num /= carry; | |
} | |
return r; | |
} | |
// Base58 decoder/encoder for bytes | |
function b58decode(str, table = btcTable) { | |
const chars = [...str]; | |
const trails = chars.findIndex(c => c !== table[0]); | |
const head0s = Array(trails).fill(0); | |
if (trails === chars.length) return Uint8Array.from(head0s); | |
const beBytes = []; | |
let num = b58ToBi(chars.slice(trails), table); | |
while (num > 0n) { | |
beBytes.unshift(Number(num % 256n)); | |
num /= 256n; | |
} | |
return Uint8Array.from(head0s.concat(beBytes)); | |
} | |
function b58encode(beBytes, table = btcTable) { | |
if (!(beBytes instanceof Uint8Array)) throw TypeError(`must be Uint8Array`); | |
const trails = beBytes.findIndex(n => n !== 0); | |
const head0s = table[0].repeat(trails); | |
if (trails === beBytes.length) return head0s; | |
const num = beBytes.slice(trails).reduce((r, n) => r * 256n + BigInt(n), 0n); | |
return head0s + biToB58(num, table).join(""); | |
} | |
// example | |
{ | |
//BASE58: heading byte value 0s are converted to "1"s (letter of table[0]) | |
console.log(b58encode(new Uint8Array([0, 1, 2]))); // => "15T" | |
console.log(b58decode("15T")); // => Uint8Array([0, 1, 2]) | |
} |
This file contains 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
// Bitcoin Base58 encoder/decoder algorithm | |
const btcTable = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; | |
console.assert(btcTable.length === 58); | |
// Base58 decoder/encoder for BigInt | |
function b58ToBi(chars, table = btcTable) { | |
const carry = BigInt(table.length); | |
let total = 0n, base = 1n; | |
for (let i = chars.length - 1; i >= 0; i--) { | |
const n = table.indexOf(chars[i]); | |
if (n < 0) throw TypeError(`invalid letter contained: '${chars[i]}'`); | |
total += base * BigInt(n); | |
base *= carry; | |
} | |
return total; | |
} | |
function biToB58(num, table = btcTable) { | |
const carry = BigInt(table.length); | |
let r = []; | |
while (num > 0n) { | |
r.unshift(table[num % carry]); | |
num /= carry; | |
} | |
return r; | |
} | |
// Base58 decoder/encoder for bytes | |
function b58decode(str, table = btcTable) { | |
const chars = [...str]; | |
const trails = chars.findIndex(c => c !== table[0]); | |
const head0s = Array(trails).fill(0); | |
if (trails === chars.length) return Uint8Array.from(head0s); | |
const beBytes = []; | |
let num = b58ToBi(chars.slice(trails), table); | |
while (num > 0n) { | |
beBytes.unshift(Number(num % 256n)); | |
num /= 256n; | |
} | |
return Uint8Array.from(head0s.concat(beBytes)); | |
} | |
function b58encode(beBytes, table = btcTable) { | |
if (!(beBytes instanceof Uint8Array)) throw TypeError(`must be Uint8Array`); | |
const trails = beBytes.findIndex(n => n !== 0); | |
const head0s = table[0].repeat(trails); | |
if (trails === beBytes.length) return head0s; | |
const num = beBytes.slice(trails).reduce((r, n) => r * 256n + BigInt(n), 0n); | |
return head0s + biToB58(num, table).join(""); | |
} | |
// example | |
{ | |
//BASE58: heading byte value 0s are converted to "1"s (letter of table[0]) | |
console.log(b58encode(new Uint8Array([0, 1, 2]))); // => "15T" | |
console.log(b58decode("15T")); // => Uint8Array([0, 1, 2]) | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// bitcoin's base58check: base58 with prefix and checksum | |
const crypto = require("crypto"); // hash with node.js builtin crypto | |
function toBase58Check(bytes, prefix = 0, table = btcTable) { | |
if (!(bytes instanceof Uint8Array)) throw TypeError(`bytes must be Uint8Array`); | |
if (!(0 <= prefix && prefix < 256)) throw TypeError(`prefix must be 0-255`); | |
const beBytes = new Uint8Array(5 + bytes.length); | |
beBytes[0] = prefix; | |
beBytes.set(bytes, 1); | |
const view = beBytes.subarray(0, beBytes.length - 4); | |
const hash = crypto.createHash("sha256").update(view).digest(); | |
const dhash = crypto.createHash("sha256").update(hash).digest(); | |
beBytes.set(dhash.subarray(0, 4), beBytes.length - 4); | |
return b58encode(beBytes, table); | |
} | |
function fromBase58Check(str, table = btcTable) { | |
const beBytes = b58decode(str, table); | |
const view = beBytes.subarray(0, beBytes.length - 4); | |
const check = beBytes.subarray(beBytes.length - 4); | |
const hash = crypto.createHash("sha256").update(view).digest(); | |
const dhash = crypto.createHash("sha256").update(hash).digest(); | |
if (check.some((b, i) => b !== dhash[i])) throw TypeError("invalid check"); | |
const prefix = view[0], bytes = view.slice(1); | |
return [bytes, prefix]; | |
} | |
// example | |
{ | |
console.log(toBase58Check(new Uint8Array([0, 1, 2]))); // => "11WARUd14" | |
console.log(fromBase58Check("11WARUd14")); // => Uint8Array([0, 1, 2]), 0 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
NOTE: Base58 and Base58Check are different now
.
Base58 is a encode/decode part of Base58Check without prefix and checksum.
https://en.bitcoin.it/wiki/Base58Check_encoding
https://en.wikipedia.org/wiki/Base58