Source: bchaddr.js

/***
 * @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.
 */

var bs58check = require('bs58check')
var cashaddr = require('cashaddrjs')
var 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 bchaddr
 */

/**
 * @static
 * Supported Bitcoin Cash address formats.
 */
var Format = {}
Format.Legacy = 'legacy'
Format.Bitpay = 'bitpay'
Format.Cashaddr = 'cashaddr'

/**
 * @static
 * Supported networks.
 */
var Network = {}
Network.Mainnet = 'mainnet'
Network.Testnet = 'testnet'

/**
 * @static
 * Supported address types.
 */
var 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) {
  var 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) {
  var 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) {
  var decoded = decodeAddress(address)
  return encodeAsCashaddr(decoded)
}

/**
 * Version byte table for base58 formats.
 * @private
 */
var 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 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
 */
var 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 {
    var payload = bs58check.decode(address)
    if (payload.length !== BASE_58_CHECK_PAYLOAD_LENGTH) {
      throw new InvalidAddressError()
    }
    var versionByte = payload[0]
    var 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 {
    var prefixes = ['bitcoincash', 'bchtest', 'bchreg']
    for (var i = 0; i < prefixes.length; ++i) {
      try {
        var prefix = prefixes[i]
        return decodeCashAddressWithPrefix(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 {
    var decoded = cashaddr.decode(address)
    var hash = Array.prototype.slice.call(decoded.hash, 0)
    var 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()
}

/**
 * Encodes the given decoded address into legacy format.
 * @private
 * @param {object} decoded
 * @returns {string}
 */
function encodeAsLegacy (decoded) {
  var versionByte = VERSION_BYTE[Format.Legacy][decoded.network][decoded.type]
  var 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) {
  var versionByte = VERSION_BYTE[Format.Bitpay][decoded.network][decoded.type]
  var 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) {
  var prefix = decoded.network === Network.Mainnet ? 'bitcoincash' : 'bchtest'
  var type = decoded.type === Type.P2PKH ? 'P2PKH' : 'P2SH'
  var 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 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 () {
  var 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,
  isLegacyAddress: isLegacyAddress,
  isBitpayAddress: isBitpayAddress,
  isCashAddress: isCashAddress,
  isMainnetAddress: isMainnetAddress,
  isTestnetAddress: isTestnetAddress,
  isP2PKHAddress: isP2PKHAddress,
  isP2SHAddress: isP2SHAddress,
  InvalidAddressError: InvalidAddressError
}