UNPKG

j-bitcoin

Version:

Comprehensive JavaScript cryptocurrency wallet library for Bitcoin (BTC), Bitcoin Cash (BCH), and Bitcoin SV (BSV) with custodial and non-custodial wallet support, threshold signatures, and multiple address formats

369 lines (319 loc) 15.7 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: src/altAddress/BCH/cash_addr.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: src/altAddress/BCH/cash_addr.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>/** * @fileoverview Bitcoin Cash CashAddr address format implementation * * This module implements the CashAddr address format for Bitcoin Cash, * providing conversion from legacy Base58Check addresses to the newer * CashAddr format with improved error detection and user experience. * * @see {@link https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md|CashAddr Specification} * @see {@link https://reference.cash/protocol/blockchain/encoding/cashaddr|CashAddr Reference} * @author yfbsei * @version 1.0.0 */ import BN from 'bn.js'; import { base58_to_binary } from 'base58-js'; import base32_encode from '../../utilities/Base32.js'; /** * @typedef {Array&lt;string>} DecodedAddress * @description Array containing [network prefix, hex hash] * @example ["bitcoincash", "76a04053bda0a88bda5177b86a15c3b29f559873"] */ /** * Bitcoin Cash CashAddr address format utilities * * Provides comprehensive support for converting legacy Bitcoin addresses * to the CashAddr format used by Bitcoin Cash. Features include: * - Legacy address decoding and validation * - Network prefix determination (bitcoincash/bchtest) * - Polynomial checksum generation and validation * - Base32 encoding with custom alphabet * - Support for both P2PKH and P2SH address types * * @namespace CASH_ADDR * @example * // Convert legacy address to CashAddr format * const legacy = "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2"; * const cashAddr = CASH_ADDR.to_cashAddr(legacy, "p2pkh"); * // Returns: "bitcoincash:qztxx64w20kmy5y9sskjwtgxp3j8dc20ksvef26ssu" * * // Convert testnet address * const testLegacy = "mgRpP3zP1hmxyoeYJgfbcmN3c2Qsurw48D"; * const testCashAddr = CASH_ADDR.to_cashAddr(testLegacy, "p2pkh"); * // Returns: "bchtest:qqyl7uye7t0rjq6vrtqjedcyudy8hj0rzvnwwa5c5g" */ const CASH_ADDR = { /** * Converts a legacy Bitcoin address to CashAddr format * * The conversion process: * 1. Decodes the legacy Base58Check address to extract hash and network * 2. Prepends version byte based on address type and hash length * 3. Converts from 8-bit to 5-bit representation for Base32 encoding * 4. Computes CashAddr checksum using polynomial algorithm * 5. Combines all components into final CashAddr format * * @param {string} [legacy_address=""] - Legacy Base58Check address to convert * @param {string} [type="p2pkh"] - Address type: "p2pkh" or "p2sh" * @returns {string} CashAddr formatted address with network prefix * @throws {Error} If legacy address is invalid or unsupported * @example * // Convert P2PKH address * const p2pkh = CASH_ADDR.to_cashAddr("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", "p2pkh"); * // Returns: "bitcoincash:qztxx64w20kmy5y9sskjwtgxp3j8dc20ksvef26ssu" * * // Convert P2SH address * const p2sh = CASH_ADDR.to_cashAddr("3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy", "p2sh"); * // Returns: "bitcoincash:pztxx64w20kmy5y9sskjwtgxp3j8dc20ksvef26ssu" */ to_cashAddr(legacy_address = "", type = "p2pkh") { // Decode legacy address to get network prefix and hash let [prefix, hash] = this.decode_legacy_address(legacy_address); // Convert hash from hex string to buffer hash = Buffer.from(hash, 'hex'); // Prepend version byte (type + hash size information) hash = Buffer.concat([this.versionByte(type, hash), hash]); // Convert to 5-bit representation for Base32 encoding const payload = this.convertBits(hash, 8, 5); // Compute checksum: polymod(prefix + separator + payload + 8-byte template) const checksum = this.polymod( Buffer.concat([ this.prefix_5bit(prefix), // Network prefix in 5-bit format Buffer.alloc(1), // Zero separator payload, // Version + hash in 5-bit format Buffer.alloc(8)]) // 8-byte zero template for checksum ); // Return complete CashAddr: prefix + ':' + base32(payload) + base32(checksum) return prefix.toLowerCase() + ':' + base32_encode(payload) + base32_encode(this.checksum_5bit(checksum)); }, /** * Decodes a legacy Base58Check address to extract network and hash information * * Validates the address format and extracts: * - Network type from version byte (0x00 = mainnet, 0x6f = testnet) * - Hash160 value (20 bytes) from the address payload * - Checksum validation through Base58Check decoding * * @param {string} [legacy_addr=""] - Legacy address to decode * @returns {DecodedAddress} Tuple of [network prefix, hex-encoded hash] * @throws {Error} If address format is invalid or unsupported * @example * // Decode mainnet address * const [prefix, hash] = CASH_ADDR.decode_legacy_address("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2"); * // Returns: ["bitcoincash", "76a04053bda0a88bda5177b86a15c3b29f559873"] * * // Decode testnet address * const [testPrefix, testHash] = CASH_ADDR.decode_legacy_address("mgRpP3zP1hmxyoeYJgfbcmN3c2Qsurw48D"); * // Returns: ["bchtest", "0e7c6e0e0b2c07d6a7b5b8b4d8b5b8b4d8b5b8b4"] */ decode_legacy_address(legacy_addr = "") { // Decode Base58Check address to binary let legacy_addr_bytes = base58_to_binary(legacy_addr); // Determine network from version byte const prefix = legacy_addr_bytes[0] === 0 ? "bitcoincash" : "bchtest"; // Validate address format: P2PKH addresses have version 0 or 111 and are 25 bytes total if ((legacy_addr_bytes[0] === 0 || legacy_addr_bytes[0] === 111) &amp;&amp; legacy_addr_bytes.length === 25) { // Extract hash160 (bytes 1-20, excluding version byte and 4-byte checksum) const legacy_addr_hash = legacy_addr_bytes.filter((_, i) => i > 0 &amp;&amp; i &lt; 21); return [prefix, Buffer.from(legacy_addr_hash).toString('hex')]; } else { throw new Error("Invalid legacy address"); } }, /** * Computes CashAddr polynomial checksum using the generator polynomial * * Implements the CashAddr checksum algorithm with a 40-bit generator polynomial. * The algorithm processes 5-bit values and maintains a 40-bit state, applying * the generator when specific bits are set. * * Generator constants: * - 0x98f2bc8e61, 0x79b76d99e2, 0xf33e5fb3c4, 0xae2eabe2a8, 0x1e4f43e470 * * @param {Buffer|Uint8Array} v - Array of 5-bit values to process * @returns {number} 40-bit polynomial checksum result * @see {@link https://reference.cash/protocol/blockchain/encoding/cashaddr|CashAddr Checksum Algorithm} * @example * const data = Buffer.from([1, 2, 3, 4, 5]); // 5-bit values * const checksum = CASH_ADDR.polymod(data); * console.log(checksum.toString(16)); // Hex representation */ polymod(v) { let c = BigInt(1); for (let d of v) { let c0 = c >> BigInt(35); // Extract top 5 bits c = ((c &amp; BigInt("0x07ffffffff")) &lt;&lt; BigInt(5)) ^ BigInt(d); // Apply generator polynomial based on extracted bits if (c0 &amp; 0x01n) c ^= BigInt("0x98f2bc8e61"); if (c0 &amp; 0x02n) c ^= BigInt("0x79b76d99e2"); if (c0 &amp; 0x04n) c ^= BigInt("0xf33e5fb3c4"); if (c0 &amp; 0x08n) c ^= BigInt("0xae2eabe2a8"); if (c0 &amp; 0x10n) c ^= BigInt("0x1e4f43e470"); } return Number(c ^ BigInt(1)); }, /** * Generates version byte for CashAddr encoding based on address type and hash size * * The version byte encodes both the address type and hash length: * - Bits 3-7: Hash size bits (mapping hash length to predefined values) * - Bits 0-2: Type bits (0 for P2PKH, 8 for P2SH) * * Supported hash sizes: 160, 192, 224, 256, 320, 384, 448, 512 bits * * @param {string} [type="p2pkh"] - Address type: "p2pkh" or "p2sh" * @param {Buffer} hash - Hash buffer to determine size * @returns {Buffer} Single-byte buffer containing version information * @throws {Error} If hash size is unsupported or type is invalid * @example * const hash160 = Buffer.alloc(20); // 160-bit hash * const versionByte = CASH_ADDR.versionByte("p2pkh", hash160); * console.log(versionByte[0]); // 0 (P2PKH with 160-bit hash) * * const versionP2SH = CASH_ADDR.versionByte("p2sh", hash160); * console.log(versionP2SH[0]); // 8 (P2SH with 160-bit hash) */ versionByte(type = "p2pkh", hash = Buffer) { // Map hash bit length to size bits (0-7) const hashSizeBits = [160, 192, 224, 256, 320, 384, 448, 512] .map((x, i) => x === hash.length * 8 ? i : null) .filter(x => Number.isInteger(x))[0]; // Map address type to type bits const typeBits = type.toLowerCase() === "p2pkh" ? 0 : // Pay to Public Key Hash type.toLowerCase() === "p2sh" ? 8 : // Pay to Script Hash null; if (hashSizeBits === undefined || typeBits === null) { throw new Error("Invalid hash size or invalid type"); } else { const ver_byte = Buffer.alloc(1); ver_byte.writeUInt8(typeBits + hashSizeBits); // Combine type and size bits return ver_byte; } }, /** * Converts network prefix string to 5-bit representation * * Extracts the lower 5 bits of each character in the prefix for use * in checksum calculation. This ensures the network prefix is properly * incorporated into the address validation. * * @param {string} [prefix='bitcoincash'] - Network prefix to convert * @returns {Uint8Array} Array of 5-bit values representing the prefix * @example * const prefix5bit = CASH_ADDR.prefix_5bit('bitcoincash'); * // Returns array of lower 5 bits: [2, 9, 20, 3, 15, 9, 14, 3, 1, 19, 8] * * const testPrefix = CASH_ADDR.prefix_5bit('bchtest'); * // Returns array: [2, 3, 8, 20, 5, 19, 20] */ prefix_5bit(prefix = 'bitcoincash') { return new Uint8Array(prefix.length).map((_, i) => prefix[i].charCodeAt() &amp; 31); }, /** * Converts data between different bit-width representations * * Performs bit-packing conversion between arbitrary bit widths, commonly * used to convert from 8-bit bytes to 5-bit groups for Base32 encoding. * The conversion handles padding and ensures no data loss. * * @param {Uint8Array|Buffer} data - Input data to convert * @param {number} from - Source bit width (e.g., 8 for bytes) * @param {number} to - Target bit width (e.g., 5 for Base32) * @returns {Uint8Array} Converted data in target bit width * @example * // Convert bytes to 5-bit groups for Base32 * const bytes = new Uint8Array([0xFF, 0x80, 0x00]); * const fiveBit = CASH_ADDR.convertBits(bytes, 8, 5); * // Returns: [31, 30, 0, 0, 0] (0xFF80 in 5-bit groups) * * // Convert back from 5-bit to 8-bit * const backToBytes = CASH_ADDR.convertBits(fiveBit, 5, 8); */ convertBits(data, from, to) { let [mask, result, index, accumulator, bits] = [ (1 &lt;&lt; to) - 1, // Bit mask for target width new Uint8Array(Math.ceil((data.length * from) / to)), // Output array 0, // Output index 0, // Bit accumulator 0 // Current bit count ]; for (let i = 0; i &lt; data.length; ++i) { let value = data[i]; accumulator = (accumulator &lt;&lt; from) | value; // Add new bits bits += from; // Extract complete target-width values while (bits >= to) { bits -= to; result[index] = (accumulator >> bits) &amp; mask; ++index; } } // Handle remaining bits with padding if (bits > 0) { result[index] = (accumulator &lt;&lt; (to - bits)) &amp; mask; ++index; } return result; }, /** * Converts a numeric checksum to 5-bit representation for Base32 encoding * * Takes a 40-bit checksum value and converts it to an array of eight 5-bit values * for inclusion in the final CashAddr string. The conversion extracts 5 bits * at a time from least significant to most significant. * * @param {number} [checksum=19310] - 40-bit checksum value to convert * @returns {Uint8Array} Array of 8 values, each containing 5 bits * @example * const checksum = 0x1234567890; // Example 40-bit checksum * const fiveBitChecksum = CASH_ADDR.checksum_5bit(checksum); * // Returns: [16, 18, 6, 22, 15, 4, 18, 0] (8 five-bit values) * * // The values can be directly used with Base32 encoding * const checksumString = base32_encode(fiveBitChecksum); */ checksum_5bit(checksum = 19310) { checksum = new BN(checksum); let result = new Uint8Array(8); // Extract 5 bits at a time, from least to most significant for (let i = 0; i &lt; 8; i++) { result[7 - i] = checksum.and(new BN(31)) // Extract lower 5 bits (31 = 0x1F) checksum = checksum.ushrn(5); // Shift right by 5 bits } return result; } } export default CASH_ADDR;</code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Namespaces</h3><ul><li><a href="AddressFormats.html">AddressFormats</a></li><li><a href="BECH32.html">BECH32</a></li><li><a href="BIP32.html">BIP32</a></li><li><a href="BIP39.html">BIP39</a></li><li><a href="CASH_ADDR.html">CASH_ADDR</a></li><li><a href="ECDSA.html">ECDSA</a></li><li><a href="KeyDecoding.html">KeyDecoding</a></li><li><a href="Signatures.html">Signatures</a></li><li><a href="ThresholdCrypto.html">ThresholdCrypto</a></li><li><a href="Utilities.html">Utilities</a></li><li><a href="Wallets.html">Wallets</a></li><li><a href="schnorr_sig.html">schnorr_sig</a></li></ul><h3>Classes</h3><ul><li><a href="Custodial_Wallet.html">Custodial_Wallet</a></li><li><a href="Non_Custodial_Wallet.html">Non_Custodial_Wallet</a></li><li><a href="Polynomial.html">Polynomial</a></li><li><a href="ThresholdSignature.html">ThresholdSignature</a></li></ul><h3>Global</h3><ul><li><a href="global.html#CHARSET">CHARSET</a></li><li><a href="global.html#FEATURES">FEATURES</a></li><li><a href="global.html#NETWORKS">NETWORKS</a></li><li><a href="global.html#address">address</a></li><li><a href="global.html#b58encode">b58encode</a></li><li><a href="global.html#base32_encode">base32_encode</a></li><li><a href="global.html#derive">derive</a></li><li><a href="global.html#fromSeed">fromSeed</a></li><li><a href="global.html#hdKey">hdKey</a></li><li><a href="global.html#legacyAddress_decode">legacyAddress_decode</a></li><li><a href="global.html#privateKey_decode">privateKey_decode</a></li><li><a href="global.html#rmd160">rmd160</a></li><li><a href="global.html#standardKey">standardKey</a></li><li><a href="global.html#table">table</a></li></ul> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 04 2025 02:28:50 GMT-0400 (Eastern Daylight Time) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>