chaingate
Version:
Multi-chain cryptocurrency SDK for TypeScript — unified API for Bitcoin, Ethereum, Litecoin, Dogecoin, Bitcoin Cash, Polygon, Arbitrum, and any EVM-compatible chain. Create wallets, query balances, send transactions, and manage tokens and NFTs across UTXO
182 lines (181 loc) • 6.32 kB
JavaScript
"use strict";
/**
* CashAddr codec for Bitcoin Cash addresses.
*
* Provides conversion between CashAddr and legacy Base58Check formats.
* @internal
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.toLegacyAddress = toLegacyAddress;
exports.toCashAddress = toCashAddress;
exports.hashToCashAddress = hashToCashAddress;
const sha2_js_1 = require("@noble/hashes/sha2.js");
const base_1 = require("@scure/base");
// Base58Check codec for legacy address encode/decode.
const base58check = (0, base_1.createBase58check)(sha2_js_1.sha256);
// CashAddr base32 alphabet (same as Bech32).
const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
const CHARSET_INV = {};
for (let i = 0; i < CHARSET.length; i++)
CHARSET_INV[CHARSET[i]] = i;
// Version byte constants.
const TYPE_P2KH = 0;
const TYPE_P2SH = 1;
// Legacy Base58Check version bytes (mainnet).
const LEGACY_P2KH = 0x00;
const LEGACY_P2SH = 0x05;
const DEFAULT_PREFIX = 'bitcoincash';
// ---------------------------------------------------------------------------
// Polymod (40-bit BCH code, 8 checksum characters)
// ---------------------------------------------------------------------------
function polymod(values) {
let c = 1n;
for (const d of values) {
const c0 = c >> 35n;
c = ((c & 0x07ffffffffn) << 5n) ^ BigInt(d);
if (c0 & 0x01n)
c ^= 0x98f2bc8e61n;
if (c0 & 0x02n)
c ^= 0x79b76d99e2n;
if (c0 & 0x04n)
c ^= 0xf33e5fb3c4n;
if (c0 & 0x08n)
c ^= 0xae2eabe2a8n;
if (c0 & 0x10n)
c ^= 0x1e4f43e470n;
}
return c ^ 1n;
}
/** Encode the prefix as lower-5-bit values for the checksum input. */
function prefixData(prefix) {
const result = [];
for (let i = 0; i < prefix.length; i++)
result.push(prefix.charCodeAt(i) & 0x1f);
result.push(0); // separator
return result;
}
// ---------------------------------------------------------------------------
// 8-bit <-> 5-bit conversion
// ---------------------------------------------------------------------------
function to5Bits(data) {
const out = [];
let bits = 0;
let value = 0;
for (const byte of data) {
value = (value << 8) | byte;
bits += 8;
while (bits >= 5) {
bits -= 5;
out.push((value >> bits) & 0x1f);
}
}
if (bits > 0)
out.push((value << (5 - bits)) & 0x1f);
return out;
}
function from5Bits(data) {
const out = [];
let bits = 0;
let value = 0;
for (const d of data) {
value = (value << 5) | d;
bits += 5;
while (bits >= 8) {
bits -= 8;
out.push((value >> bits) & 0xff);
}
}
return new Uint8Array(out);
}
// ---------------------------------------------------------------------------
// CashAddr encode / decode
// ---------------------------------------------------------------------------
function encodeCashAddr(prefix, type, hash) {
// Version byte: type in bits 6-3, hash size indicator in bits 2-0.
// For 20-byte hashes, size indicator = 0.
const versionByte = (type << 3) | 0;
const payload = new Uint8Array([versionByte, ...hash]);
const words = to5Bits(payload);
const tmpl = prefixData(prefix).concat(words).concat([0, 0, 0, 0, 0, 0, 0, 0]);
const checksum = polymod(tmpl);
const checksumWords = [];
for (let i = 0; i < 8; i++)
checksumWords.push(Number((checksum >> BigInt(5 * (7 - i))) & 0x1fn));
let result = prefix + ':';
for (const w of words)
result += CHARSET[w];
for (const w of checksumWords)
result += CHARSET[w];
return result;
}
function decodeCashAddr(address) {
// Normalize: add default prefix if missing.
let addr = address.toLowerCase();
let prefix;
let colonIdx = addr.indexOf(':');
if (colonIdx === -1) {
prefix = DEFAULT_PREFIX;
addr = prefix + ':' + addr;
colonIdx = prefix.length;
}
else {
prefix = addr.substring(0, colonIdx);
}
const payload = addr.substring(colonIdx + 1);
const words = [];
for (const ch of payload) {
const val = CHARSET_INV[ch];
if (val === undefined)
throw new Error(`Invalid CashAddr character: ${ch}`);
words.push(val);
}
// Verify checksum (last 8 words are checksum).
const check = prefixData(prefix).concat(words);
if (polymod(check) !== 0n)
throw new Error('Invalid CashAddr checksum');
// Separate data words from checksum.
const dataWords = words.slice(0, words.length - 8);
const payloadBytes = from5Bits(dataWords);
const versionByte = payloadBytes[0];
const type = (versionByte >> 3) & 0x0f;
const hash = payloadBytes.slice(1);
return { prefix, type, hash };
}
// ---------------------------------------------------------------------------
// Public API — drop-in replacement for bchaddrjs
// ---------------------------------------------------------------------------
/**
* Converts a CashAddr address to legacy Base58Check format.
*
* @param address - CashAddr address (e.g. `bitcoincash:qq...`).
* @returns Legacy Base58Check address (e.g. `1...`).
*/
function toLegacyAddress(address) {
const { type, hash } = decodeCashAddr(address);
const version = type === TYPE_P2SH ? LEGACY_P2SH : LEGACY_P2KH;
const payload = new Uint8Array([version, ...hash]);
return base58check.encode(payload);
}
/**
* Converts a legacy Base58Check address to CashAddr format.
*
* @param address - Legacy Base58Check address (e.g. `1...`).
* @returns CashAddr address (e.g. `bitcoincash:qq...`).
*/
function toCashAddress(address) {
const decoded = base58check.decode(address);
const version = decoded[0];
const hash = decoded.slice(1);
const type = version === LEGACY_P2SH ? TYPE_P2SH : TYPE_P2KH;
return encodeCashAddr(DEFAULT_PREFIX, type, hash);
}
/**
* Encodes a public key hash as a CashAddr address.
*
* @param hash160 - The 20-byte hash of the public key.
* @param type - Address type: `'p2pkh'` or `'p2sh'`.
* @returns CashAddr address string.
*/
function hashToCashAddress(hash160, type = 'p2pkh') {
return encodeCashAddr(DEFAULT_PREFIX, type === 'p2sh' ? TYPE_P2SH : TYPE_P2KH, hash160);
}