Skip to content

Instantly share code, notes, and snippets.

@bellbind
Last active April 27, 2022 21:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bellbind/1f07f94e5ce31557ef23dc2a9b3cc2e1 to your computer and use it in GitHub Desktop.
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
// 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 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
}
@bellbind
Copy link
Author

bellbind commented Oct 2, 2019

NOTE: Base58 and Base58Check are different now
.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment