UNPKG

xecaddrjs

Version:

eCash general purpose address translation.

564 lines (527 loc) 15.4 kB
/*** * @license * https://github.com/ealmansi/bchaddrjs * Copyright (c) 2018-2020 Emilio Almansi * Distributed under the MIT software license, see the accompanying * file LICENSE or http://www.opensource.org/licenses/mit-license.php. */ const bs58check = require('bs58check') const cashaddr = require('ecashaddrjs') const Buffer = require('buffer/').Buffer /** * General purpose Bitcoin Cash address detection and translation.<br /> * Supports all major Bitcoin Cash address formats.<br /> * Currently: * <ul> * <li> Legacy format </li> * <li> Bitpay format </li> * <li> Cashaddr format </li> * </ul> * @module xecaddr */ /** * @static * Supported Bitcoin Cash address formats. */ const Format = {} Format.Legacy = 'legacy' Format.Bitpay = 'bitpay' Format.Cashaddr = 'cashaddr' Format.Xecaddr = 'xecaddr' /** * @static * Supported networks. */ const Network = {} Network.Mainnet = 'mainnet' Network.Testnet = 'testnet' /** * @static * Supported address types. */ const Type = {} Type.P2PKH = 'p2pkh' Type.P2SH = 'p2sh' /** * Returns a boolean indicating whether the given input is a valid Bitcoin Cash address. * @static * @param {*} input - Any input to check for validity. * @returns {boolean} */ function isValidAddress (input) { try { decodeAddress(input) return true } catch (error) { return false } } /** * Detects what is the given address' format. * @static * @param {string} address - A valid Bitcoin Cash address in any format. * @return {string} * @throws {InvalidAddressError} */ function detectAddressFormat (address) { return decodeAddress(address).format } /** * Detects what is the given address' network. * @static * @param {string} address - A valid Bitcoin Cash address in any format. * @return {string} * @throws {InvalidAddressError} */ function detectAddressNetwork (address) { return decodeAddress(address).network } /** * Detects what is the given address' type. * @static * @param {string} address - A valid Bitcoin Cash address in any format. * @return {string} * @throws {InvalidAddressError} */ function detectAddressType (address) { return decodeAddress(address).type } /** * Translates the given address into legacy format. * @static * @param {string} address - A valid Bitcoin Cash address in any format. * @return {string} * @throws {InvalidAddressError} */ function toLegacyAddress (address) { const decoded = decodeAddress(address) if (decoded.format === Format.Legacy) { return address } return encodeAsLegacy(decoded) } /** * Translates the given address into bitpay format. * @static * @param {string} address - A valid Bitcoin Cash address in any format. * @return {string} * @throws {InvalidAddressError} */ function toBitpayAddress (address) { const decoded = decodeAddress(address) if (decoded.format === Format.Bitpay) { return address } return encodeAsBitpay(decoded) } /** * Translates the given address into cashaddr format. * @static * @param {string} address - A valid Bitcoin Cash address in any format. * @return {string} * @throws {InvalidAddressError} */ function toCashAddress (address) { const decoded = decodeAddress(address) return encodeAsCashaddr(decoded) } /** * Translates the given address into xecaddr format. * @static * @param {string} address - A valid Bitcoin Cash address in any format. * @return {string} * @throws {InvalidAddressError} */ function toXecAddress (address) { const decoded = decodeAddress(address) return encodeAsXecaddr(decoded) } /** * Version byte table for base58 formats. * @private */ const VERSION_BYTE = {} VERSION_BYTE[Format.Legacy] = {} VERSION_BYTE[Format.Legacy][Network.Mainnet] = {} VERSION_BYTE[Format.Legacy][Network.Mainnet][Type.P2PKH] = 0 VERSION_BYTE[Format.Legacy][Network.Mainnet][Type.P2SH] = 5 VERSION_BYTE[Format.Legacy][Network.Testnet] = {} VERSION_BYTE[Format.Legacy][Network.Testnet][Type.P2PKH] = 111 VERSION_BYTE[Format.Legacy][Network.Testnet][Type.P2SH] = 196 VERSION_BYTE[Format.Bitpay] = {} VERSION_BYTE[Format.Bitpay][Network.Mainnet] = {} VERSION_BYTE[Format.Bitpay][Network.Mainnet][Type.P2PKH] = 28 VERSION_BYTE[Format.Bitpay][Network.Mainnet][Type.P2SH] = 40 VERSION_BYTE[Format.Bitpay][Network.Testnet] = {} VERSION_BYTE[Format.Bitpay][Network.Testnet][Type.P2PKH] = 111 VERSION_BYTE[Format.Bitpay][Network.Testnet][Type.P2SH] = 196 /** * Decodes the given address into its constituting hash, format, network and type. * @private * @param {string} address - A valid Bitcoin Cash address in any format. * @return {object} * @throws {InvalidAddressError} */ function decodeAddress (address) { try { return decodeBase58Address(address) } catch (error) { } try { return decodeXecAddress(address) } catch (error) { } try { return decodeCashAddress(address) } catch (error) { } throw new InvalidAddressError() } /** * Length of a valid base58check encoding payload: 1 byte for * the version byte plus 20 bytes for a RIPEMD-160 hash. * @private */ const BASE_58_CHECK_PAYLOAD_LENGTH = 21 /** * Attempts to decode the given address assuming it is a base58 address. * @private * @param {string} address - A valid Bitcoin Cash address in any format. * @return {object} * @throws {InvalidAddressError} */ function decodeBase58Address (address) { try { const payload = bs58check.decode(address) if (payload.length !== BASE_58_CHECK_PAYLOAD_LENGTH) { throw new InvalidAddressError() } const versionByte = payload[0] const hash = Array.prototype.slice.call(payload, 1) switch (versionByte) { case VERSION_BYTE[Format.Legacy][Network.Mainnet][Type.P2PKH]: return { hash: hash, format: Format.Legacy, network: Network.Mainnet, type: Type.P2PKH } case VERSION_BYTE[Format.Legacy][Network.Mainnet][Type.P2SH]: return { hash: hash, format: Format.Legacy, network: Network.Mainnet, type: Type.P2SH } case VERSION_BYTE[Format.Legacy][Network.Testnet][Type.P2PKH]: return { hash: hash, format: Format.Legacy, network: Network.Testnet, type: Type.P2PKH } case VERSION_BYTE[Format.Legacy][Network.Testnet][Type.P2SH]: return { hash: hash, format: Format.Legacy, network: Network.Testnet, type: Type.P2SH } case VERSION_BYTE[Format.Bitpay][Network.Mainnet][Type.P2PKH]: return { hash: hash, format: Format.Bitpay, network: Network.Mainnet, type: Type.P2PKH } case VERSION_BYTE[Format.Bitpay][Network.Mainnet][Type.P2SH]: return { hash: hash, format: Format.Bitpay, network: Network.Mainnet, type: Type.P2SH } } } catch (error) { } throw new InvalidAddressError() } /** * Attempts to decode the given address assuming it is a cashaddr address. * @private * @param {string} address - A valid Bitcoin Cash address in any format. * @return {object} * @throws {InvalidAddressError} */ function decodeCashAddress (address) { if (address.indexOf(':') !== -1) { try { return decodeCashAddressWithPrefix(address) } catch (error) { } } else { const prefixes = ['bitcoincash', 'bchtest', 'bchreg'] for (let i = 0; i < prefixes.length; ++i) { try { const prefix = prefixes[i] return decodeCashAddressWithPrefix(prefix + ':' + address) } catch (error) { } } } throw new InvalidAddressError() } /** * Attempts to decode the given address assuming it is a cashaddr address. * @private * @param {string} address - A valid Bitcoin Cash address in any format. * @return {object} * @throws {InvalidAddressError} */ function decodeXecAddress (address) { if (address.indexOf(':') !== -1) { try { return decodeXecAddressWithPrefix(address) } catch (error) { } } else { const prefixes = ['ecash', 'ectest'] for (let i = 0; i < prefixes.length; ++i) { try { const prefix = prefixes[i] return decodeXecAddressWithPrefix(prefix + ':' + address) } catch (error) { } } } throw new InvalidAddressError() } /** * Attempts to decode the given address assuming it is a cashaddr address with explicit prefix. * @private * @param {string} address - A valid Bitcoin Cash address in any format. * @return {object} * @throws {InvalidAddressError} */ function decodeCashAddressWithPrefix (address) { try { const decoded = cashaddr.decode(address) const hash = Array.prototype.slice.call(decoded.hash, 0) const type = decoded.type === 'P2PKH' ? Type.P2PKH : Type.P2SH switch (decoded.prefix) { case 'bitcoincash': return { hash: hash, format: Format.Cashaddr, network: Network.Mainnet, type: type } case 'bchtest': case 'bchreg': return { hash: hash, format: Format.Cashaddr, network: Network.Testnet, type: type } } } catch (error) { } throw new InvalidAddressError() } /** * Attempts to decode the given address assuming it is a cashaddr address with explicit prefix. * @private * @param {string} address - A valid eCash (XEC) address in any format. * @return {object} * @throws {InvalidAddressError} */ function decodeXecAddressWithPrefix (address) { try { const decoded = cashaddr.decode(address) const hash = Array.prototype.slice.call(decoded.hash, 0) const type = decoded.type === 'P2PKH' ? Type.P2PKH : Type.P2SH switch (decoded.prefix) { case 'ecash': return { hash: hash, format: Format.Xecaddr, network: Network.Mainnet, type: type } case 'ectest': return { hash: hash, format: Format.Xecaddr, network: Network.Testnet, type: type } } } catch (error) { } throw new InvalidAddressError() } /** * Encodes the given decoded address into legacy format. * @private * @param {object} decoded * @returns {string} */ function encodeAsLegacy (decoded) { const versionByte = VERSION_BYTE[Format.Legacy][decoded.network][decoded.type] const buffer = Buffer.alloc(1 + decoded.hash.length) buffer[0] = versionByte buffer.set(decoded.hash, 1) return bs58check.encode(buffer) } /** * Encodes the given decoded address into bitpay format. * @private * @param {object} decoded * @returns {string} */ function encodeAsBitpay (decoded) { const versionByte = VERSION_BYTE[Format.Bitpay][decoded.network][decoded.type] const buffer = Buffer.alloc(1 + decoded.hash.length) buffer[0] = versionByte buffer.set(decoded.hash, 1) return bs58check.encode(buffer) } /** * Encodes the given decoded address into cashaddr format. * @private * @param {object} decoded * @returns {string} */ function encodeAsCashaddr (decoded) { const prefix = decoded.network === Network.Mainnet ? 'bitcoincash' : 'bchtest' const type = decoded.type === Type.P2PKH ? 'P2PKH' : 'P2SH' const hash = new Uint8Array(decoded.hash) return cashaddr.encode(prefix, type, hash) } /** * Encodes the given decoded address into xecaddr format. * @private * @param {object} decoded * @returns {string} */ function encodeAsXecaddr (decoded) { const prefix = decoded.network === Network.Mainnet ? 'ecash' : 'ectest' const type = decoded.type === Type.P2PKH ? 'P2PKH' : 'P2SH' const hash = new Uint8Array(decoded.hash) return cashaddr.encode(prefix, type, hash) } /** * Returns a boolean indicating whether the address is in legacy format. * @static * @param {string} address - A valid Bitcoin Cash address in any format. * @returns {boolean} * @throws {InvalidAddressError} */ function isLegacyAddress (address) { return detectAddressFormat(address) === Format.Legacy } /** * Returns a boolean indicating whether the address is in bitpay format. * @static * @param {string} address - A valid Bitcoin Cash address in any format. * @returns {boolean} * @throws {InvalidAddressError} */ function isBitpayAddress (address) { return detectAddressFormat(address) === Format.Bitpay } /** * Returns a boolean indicating whether the address is in cashaddr format. * @static * @param {string} address - A valid Bitcoin Cash address in any format. * @returns {boolean} * @throws {InvalidAddressError} */ function isCashAddress (address) { return detectAddressFormat(address) === Format.Cashaddr } /** * Returns a boolean indicating whether the address is in xecaddr format. * @static * @param {string} address - A valid XEC address in any format. * @returns {boolean} * @throws {InvalidAddressError} */ function isXecAddress (address) { return detectAddressFormat(address) === Format.Xecaddr } /** * Returns a boolean indicating whether the address is a mainnet address. * @static * @param {string} address - A valid Bitcoin Cash address in any format. * @returns {boolean} * @throws {InvalidAddressError} */ function isMainnetAddress (address) { return detectAddressNetwork(address) === Network.Mainnet } /** * Returns a boolean indicating whether the address is a testnet address. * @static * @param {string} address - A valid Bitcoin Cash address in any format. * @returns {boolean} * @throws {InvalidAddressError} */ function isTestnetAddress (address) { return detectAddressNetwork(address) === Network.Testnet } /** * Returns a boolean indicating whether the address is a p2pkh address. * @static * @param {string} address - A valid Bitcoin Cash address in any format. * @returns {boolean} * @throws {InvalidAddressError} */ function isP2PKHAddress (address) { return detectAddressType(address) === Type.P2PKH } /** * Returns a boolean indicating whether the address is a p2sh address. * @static * @param {string} address - A valid Bitcoin Cash address in any format. * @returns {boolean} * @throws {InvalidAddressError} */ function isP2SHAddress (address) { return detectAddressType(address) === Type.P2SH } /** * Error thrown when the address given as input is not a valid Bitcoin Cash address. * @constructor * InvalidAddressError */ function InvalidAddressError () { const error = new Error() this.name = error.name = 'InvalidAddressError' this.message = error.message = 'Received an invalid Bitcoin Cash address as input.' this.stack = error.stack } InvalidAddressError.prototype = Object.create(Error.prototype) module.exports = { Format: Format, Network: Network, Type: Type, isValidAddress: isValidAddress, detectAddressFormat: detectAddressFormat, detectAddressNetwork: detectAddressNetwork, detectAddressType: detectAddressType, toLegacyAddress: toLegacyAddress, toBitpayAddress: toBitpayAddress, toCashAddress: toCashAddress, toXecAddress: toXecAddress, isLegacyAddress: isLegacyAddress, isBitpayAddress: isBitpayAddress, isCashAddress: isCashAddress, isXecAddress: isXecAddress, isMainnetAddress: isMainnetAddress, isTestnetAddress: isTestnetAddress, isP2PKHAddress: isP2PKHAddress, isP2SHAddress: isP2SHAddress, InvalidAddressError: InvalidAddressError }