bitcoin-address-validation
Version:
Validate any Bitcoin address - P2WSH, P2WPKH, P2SH, P2PKH - Mainnet & Testnet
148 lines • 4.29 kB
JavaScript
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