trezor-address-validator
Version:
Multicoin address validator for Bitcoin and other altcoins.
173 lines (154 loc) • 5.88 kB
JavaScript
var bech32 = require('./crypto/bech32');
var base58 = require('./crypto/base58');
var cryptoUtils = require('./crypto/utils');
const { addressType } = require('./crypto/utils');
var DEFAULT_NETWORK_TYPE = 'prod';
function getDecoded(address) {
try {
return base58.decode(address);
} catch (e) {
// if decoding fails, assume invalid address
return null;
}
}
function getChecksum(hashFunction, payload) {
// Each currency may implement different hashing algorithm
switch (hashFunction) {
// blake then keccak hash chain
case 'blake256keccak256':
var blake = cryptoUtils.blake2b256(payload);
return cryptoUtils.keccak256Checksum(Buffer.from(blake, 'hex'));
case 'blake256':
return cryptoUtils.blake256Checksum(payload);
case 'keccak256':
return cryptoUtils.keccak256Checksum(payload);
case 'groestl512x2':
return cryptoUtils.groestl512x2(payload);
case 'sha256':
default:
return cryptoUtils.sha256Checksum(payload);
}
}
function getAddressType(address, currency) {
currency = currency || {};
// should be 25 bytes per btc address spec and 26 decred
var expectedLength = currency.expectedLength || 25;
var hashFunction = currency.hashFunction || 'sha256';
var decoded = getDecoded(address);
if (decoded) {
var length = decoded.length;
if (length !== expectedLength) {
return null;
}
if(currency.regex) {
if(!currency.regex.test(address)) {
return null;
}
}
var checksum = cryptoUtils.toHex(decoded.slice(length - 4, length)),
body = cryptoUtils.toHex(decoded.slice(0, length - 4)),
goodChecksum = getChecksum(hashFunction, body);
return checksum === goodChecksum ? cryptoUtils.toHex(decoded.slice(0, expectedLength - 24)) : null;
}
return null;
}
function getOutputIndex(address, currency, networkType) {
const addressType = getAddressType(address, currency);
if (addressType) {
const correctAddressTypes =
currency.addressTypes[networkType] ||
Object.keys(currency.addressTypes).reduce((all, key) => {
return all.concat(currency.addressTypes[key]);
}, []);
return correctAddressTypes.indexOf(addressType);
}
return null;
}
function isValidPayToPublicKeyHashAddress(address, currency, networkType) {
return getOutputIndex(address, currency, networkType) === 0;
}
function isValidPayToScriptHashAddress(address, currency, networkType) {
return getOutputIndex(address, currency, networkType) > 0;
}
function isValidPayToWitnessScriptHashAddress(address, currency, networkType) {
try {
const hrp = currency.segwitHrp[networkType];
const decoded = bech32.decode(hrp, address);
return decoded && decoded.version === 0 && decoded.program.length === 32;
} catch (err) {
return null;
}
}
function isValidPayToWitnessPublicKeyHashAddress(address, currency, networkType) {
try {
const hrp = currency.segwitHrp[networkType];
const decoded = bech32.decode(hrp, address);
return decoded && decoded.version === 0 && decoded.program.length === 20;
} catch (err) {
return null;
}
}
function isValidPayToTaprootAddress(address, currency, networkType) {
try {
const hrp = currency.segwitHrp[networkType];
decoded = bech32.decode(hrp, address, true);
return decoded && decoded.version === 1 && decoded.program.length === 32;
} catch (err) {}
return null;
}
function isValidSegwitAddress(address, currency, networkType) {
if (!currency.segwitHrp) {
return false;
}
var hrp = currency.segwitHrp[networkType];
if (!hrp) {
return false;
}
// try bech32 first
let ret = bech32.decode(hrp, address, false);
if (ret) {
if (ret.version === 0 || ret.program.length === 20 || ret.program.length === 32) {
return false;
} else {
return address.toLowerCase() === bech32.encode(hrp, ret.version, ret.program, false);
}
}
// then try bech32m
ret = bech32.decode(hrp, address, true);
if (ret) {
if (ret.version > 1 || ret.program.length !== 32) {
return address.toLowerCase() === bech32.encode(hrp, ret.version, ret.program, true);
}
}
return false;
}
module.exports = {
isValidAddress: function (address, currency, networkType) {
networkType = networkType || DEFAULT_NETWORK_TYPE;
const addrType = this.getAddressType(address, currency, networkType);
// Though WITNESS_UNKNOWN is a valid address, it's not spendable - so we mark it as invalid
return addrType !== undefined && addrType !== addressType.WITNESS_UNKNOWN;
},
getAddressType: function(address, currency, networkType) {
networkType = networkType || DEFAULT_NETWORK_TYPE;
if (isValidPayToPublicKeyHashAddress(address, currency, networkType)) {
return addressType.P2PKH;
}
if (isValidPayToScriptHashAddress(address, currency, networkType)) {
return addressType.P2SH;
}
if (isValidPayToWitnessScriptHashAddress(address, currency, networkType)) {
return addressType.P2WSH;
}
if (isValidPayToWitnessPublicKeyHashAddress(address, currency, networkType)) {
return addressType.P2WPKH;
}
if (isValidPayToTaprootAddress(address, currency, networkType)) {
return addressType.P2TR;
}
if (isValidSegwitAddress(address, currency, networkType)) {
return addressType.WITNESS_UNKNOWN;
}
return undefined;
},
};