UNPKG

@ngraveio/ur-blockchain-commons

Version:

A JS implementation of Uniform Resources(UR) Registry specification from Blockchain Commons.

183 lines 7.02 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.decodeAddress = decodeAddress; exports.encodeAddress = encodeAddress; const base_1 = require("@scure/base"); const sha256_1 = require("@noble/hashes/sha256"); const sha3_1 = require("@noble/hashes/sha3"); /** * Decode a Bitcoin or Ethereum address into its components. * @param address - The address to decode. * @param network - Optional network for Ethereum (default: 'mainnet'). * @returns The decoded address components. */ function decodeAddress(address, network) { if (address.startsWith('1') || address.startsWith('3') || address.startsWith('m') || address.startsWith('2')) { // Bitcoin P2PKH or P2SH (Base58Check) const decoded = base_1.base58.decode(address); const version = decoded[0]; const payload = decoded.slice(1, -4); const checksum = decoded.slice(-4); const calculatedChecksum = (0, sha256_1.sha256)((0, sha256_1.sha256)(decoded.slice(0, -4))).slice(0, 4); if (!equalBytes(checksum, calculatedChecksum)) { throw new Error('Invalid checksum'); } let scriptType; let addressNetwork; switch (version) { case 0x00: scriptType = 'P2PKH'; addressNetwork = 'mainnet'; break; case 0x6F: scriptType = 'P2PKH'; addressNetwork = 'testnet'; break; case 0x05: scriptType = 'P2SH'; addressNetwork = 'mainnet'; break; case 0xC4: scriptType = 'P2SH'; addressNetwork = 'testnet'; break; default: throw new Error('Unknown version byte'); } if (network !== undefined && network !== addressNetwork) { throw new Error(`Address network mismatch: expected ${network}, got ${addressNetwork}`); } return { type: 0, // BIP44 coin type for Bitcoin scriptType, network: addressNetwork, payload, checksum: toUInt32BE(checksum), }; } else if (/^(bc1|tb1)[a-z0-9]{25,}$/i.test(address)) { // Bitcoin P2WPKH, P2WSH, or P2TR (Bech32 or Bech32m) const isBech32m = address.startsWith('bc1p') || address.startsWith('tb1p'); const { prefix, words } = isBech32m ? base_1.bech32m.decode(address) : base_1.bech32.decode(address); const version = words[0]; const payload = (isBech32m ? base_1.bech32m : base_1.bech32).fromWords(words.slice(1)); let scriptType; let addressNetwork; switch (prefix) { case 'bc': addressNetwork = 'mainnet'; break; case 'tb': addressNetwork = 'testnet'; break; default: throw new Error('Unknown HRP'); } switch (payload.length) { case 20: scriptType = 'P2WPKH'; break; case 32: scriptType = version === 0x01 ? 'P2TR' : 'P2WSH'; break; default: throw new Error('Unknown witness program length'); } if (network !== undefined && network !== addressNetwork) { throw new Error(`Address network mismatch: expected ${network}, got ${addressNetwork}`); } return { type: 0, // BIP44 coin type for Bitcoin scriptType, network: addressNetwork, payload: new Uint8Array(payload), }; } else if (/^0x[a-fA-F0-9]{40}$/.test(address)) { // Ethereum address (Hex) const payload = address.slice(2).toLowerCase(); const payloadBuffer = new Uint8Array(payload.match(/.{1,2}/g).map(byte => parseInt(byte, 16))); const checksummedAddress = toChecksumAddress(address); return { type: 60, // BIP44 coin type for Ethereum network, payload: payloadBuffer, checksum: calculateEthereumChecksum(checksummedAddress), }; } else { throw new Error(`Unsupported address format ${address}`); } } /** * Encode a Bitcoin or Ethereum address from its components. * @param type - The BIP44 coin type. * @param scriptType - The script type for Bitcoin addresses. * @param network - The network type (mainnet or testnet). * @param payload - The payload to encode. * @returns The encoded address. */ function encodeAddress(type, scriptType, network, payload) { if (type === 0) { // Bitcoin address encoding let version; switch (scriptType) { case 'P2PKH': version = network === 'mainnet' ? 0x00 : 0x6F; break; case 'P2SH': version = network === 'mainnet' ? 0x05 : 0xC4; break; case 'P2WPKH': case 'P2WSH': case 'P2TR': { const prefix = network === 'mainnet' ? 'bc' : 'tb'; const words = (scriptType === 'P2TR' ? base_1.bech32m : base_1.bech32).toWords(payload); words.unshift(scriptType === 'P2TR' ? 0x01 : 0x00); return (scriptType === 'P2TR' ? base_1.bech32m : base_1.bech32).encode(prefix, words); } default: throw new Error('Unknown script type'); } const checksum = (0, sha256_1.sha256)((0, sha256_1.sha256)(new Uint8Array([version, ...payload]))).slice(0, 4); return base_1.base58.encode(new Uint8Array([version, ...payload, ...checksum])); } else if (type === 60) { // Ethereum address encoding return toChecksumAddress(`0x${base_1.base16.encode(payload)}`); } else { throw new Error(`Unsupported coin type: ${type}`); } } /** * Helper functions remain unchanged */ function calculateEthereumChecksum(address) { const addressHash = (0, sha3_1.keccak_256)(address.slice(2).toLowerCase()); return parseInt(base_1.base16.encode(addressHash.slice(0, 4)), 16); } function toChecksumAddress(address) { const addressHash = base_1.base16.encode((0, sha3_1.keccak_256)(address.slice(2).toLowerCase())); let checksumAddress = '0x'; for (let i = 0; i < 40; i++) { checksumAddress += parseInt(addressHash[i], 16) > 7 ? address[i + 2].toUpperCase() : address[i + 2].toLowerCase(); } return checksumAddress; } function equalBytes(a, b) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false; } return true; } function toUInt32BE(bytes) { if (bytes.length !== 4) { throw new Error('Invalid byte length for UInt32BE conversion.'); } return ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0; } //# sourceMappingURL=AddressHelpers.js.map