UNPKG

bitcoin-address-validation

Version:

Validate any Bitcoin address - P2WSH, P2WPKH, P2SH, P2PKH - Mainnet & Testnet

148 lines 4.29 kB
import { base58_to_binary } from 'base58-js'; import { bech32, bech32m } from 'bech32'; import { createHash } from 'sha256-uint8array'; const sha256 = (payload) => createHash().update(payload).digest(); var Network; (function (Network) { Network["mainnet"] = "mainnet"; Network["testnet"] = "testnet"; Network["regtest"] = "regtest"; Network["signet"] = "signet"; })(Network || (Network = {})); var AddressType; (function (AddressType) { AddressType["p2pkh"] = "p2pkh"; AddressType["p2sh"] = "p2sh"; AddressType["p2wpkh"] = "p2wpkh"; AddressType["p2wsh"] = "p2wsh"; AddressType["p2tr"] = "p2tr"; })(AddressType || (AddressType = {})); const addressTypes = { 0x00: { type: AddressType.p2pkh, network: Network.mainnet, }, 0x6f: { type: AddressType.p2pkh, network: Network.testnet, }, 0x05: { type: AddressType.p2sh, network: Network.mainnet, }, 0xc4: { type: AddressType.p2sh, network: Network.testnet, }, }; function castTestnetTo(fromNetwork, toNetwork) { if (!toNetwork) { return fromNetwork; } if (fromNetwork === Network.mainnet) { throw new Error('Cannot cast mainnet to non-mainnet'); } return toNetwork; } const normalizeAddressInfo = (addressInfo, options) => { return { ...addressInfo, network: castTestnetTo(addressInfo.network, options?.castTestnetTo), }; }; const parseBech32 = (address, options) => { let decoded; try { if (address.startsWith('bc1p') || address.startsWith('tb1p') || address.startsWith('bcrt1p')) { decoded = bech32m.decode(address); } else { decoded = bech32.decode(address); } } catch (error) { throw new Error('Invalid address'); } const mapPrefixToNetwork = { bc: Network.mainnet, tb: Network.testnet, bcrt: Network.regtest, }; const network = mapPrefixToNetwork[decoded.prefix]; if (network === undefined) { throw new Error('Invalid address'); } const witnessVersion = decoded.words[0]; if (witnessVersion === undefined || witnessVersion < 0 || witnessVersion > 16) { throw new Error('Invalid address'); } const data = bech32.fromWords(decoded.words.slice(1)); let type; if (data.length === 20) { type = AddressType.p2wpkh; } else if (witnessVersion === 1) { type = AddressType.p2tr; } else { type = AddressType.p2wsh; } return normalizeAddressInfo({ bech32: true, network, address, type, }, options); }; const getAddressInfo = (address, options) => { let decoded; const prefix = address.slice(0, 2).toLowerCase(); if (prefix === 'bc' || prefix === 'tb') { return parseBech32(address, options); } try { decoded = base58_to_binary(address); } catch (error) { throw new Error('Invalid address'); } const { length } = decoded; if (length !== 25) { throw new Error('Invalid address'); } const version = decoded[0]; const checksum = decoded.slice(length - 4, length); const body = decoded.slice(0, length - 4); const expectedChecksum = sha256(sha256(body)).slice(0, 4); if (checksum.some((value, index) => value !== expectedChecksum[index])) { throw new Error('Invalid address'); } const validVersions = Object.keys(addressTypes).map(Number); if (version === undefined || !validVersions.includes(version)) { throw new Error('Invalid address'); } const addressType = addressTypes[version]; if (!addressType) { throw new Error('Invalid address'); } return normalizeAddressInfo({ ...addressType, address, bech32: false, }, options); }; const validate = (address, network, options) => { try { const addressInfo = getAddressInfo(address, options); if (network) { return network === addressInfo.network; } return true; } catch (error) { return false; } }; export { getAddressInfo, Network, AddressType, validate }; export default validate; //# sourceMappingURL=index.js.map