Skip to content

Instantly share code, notes, and snippets.

@losh11
Last active April 3, 2025 04:25
Show Gist options
  • Save losh11/036ffb56215fd0ea1f063584874b51c9 to your computer and use it in GitHub Desktop.
Save losh11/036ffb56215fd0ea1f063584874b51c9 to your computer and use it in GitHub Desktop.
// Validation library from: https://github.com/ruigomeseu/bitcoin-address-validation
// Copyright (c) 2018 Rui Gomes [email protected]
import {base58_to_binary} from 'base58-js';
import {bech32} from 'bech32';
import {createHash} from 'sha256-uint8array';
const sha256 = (payload: Uint8Array) => createHash().update(payload).digest();
enum Network {
mainnet = 'mainnet',
testnet = 'testnet',
regtest = 'regtest',
}
enum AddressType {
p2pkh = 'p2pkh',
p2sh = 'p2sh',
p2wpkh = 'p2wpkh',
p2wsh = 'p2wsh',
p2tr = 'p2tr',
mweb = 'mweb',
}
type AddressInfo = {
bech32: boolean;
network: Network;
address: string;
type: AddressType;
};
const addressTypes: {[key: number]: {type: AddressType; network: Network}} = {
0x30: {
type: AddressType.p2pkh,
network: Network.mainnet,
},
0x6f: {
type: AddressType.p2pkh,
network: Network.testnet,
},
0x32: {
type: AddressType.p2sh,
network: Network.mainnet,
},
0x3a: {
type: AddressType.p2sh,
network: Network.testnet,
},
};
const parseBech32 = (address: string): AddressInfo => {
let decoded;
try {
decoded = bech32.decode(address);
} catch (error) {
throw new Error('Invalid address');
}
const mapPrefixToNetwork: {[key: string]: Network} = {
ltc: Network.mainnet,
tltc: Network.testnet,
rltc: Network.regtest,
};
const network: Network = mapPrefixToNetwork[decoded.prefix];
if (network === undefined) {
throw new Error('Invalid address');
}
const witnessVersion = decoded.words[0];
if (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 {
bech32: true,
network,
address,
type,
};
};
const parseMweb = (address: string): AddressInfo => {
let decoded;
try {
decoded = bech32.decode(address, 121);
} catch (error) {
throw new Error('Invalid address');
}
const mapPrefixToNetwork: {[key: string]: Network} = {
ltcmweb: Network.mainnet,
tmweb: Network.testnet,
rmweb: Network.regtest,
};
const network: Network = mapPrefixToNetwork[decoded.prefix];
if (network === undefined) {
throw new Error('Invalid address');
}
const data = bech32.fromWords(decoded.words.slice(1));
if (data.length !== 66) {
throw new Error('Invalid address');
}
return {
bech32: true,
network,
address,
type: AddressType.mweb,
};
};
const getAddressInfo = (address: string): AddressInfo => {
let decoded: Uint8Array;
// check and handle bech32 address
const prefix = address.substring(0, 4).toLowerCase();
if (prefix === 'ltc1' || prefix === 'tltc') {
if (address.length >= 15 && address.length <= 92) {
return parseBech32(address);
} else {
throw new Error('Invalid bech32 address');
}
}
// check and handle mweb address
const mwebPrefix = address.substring(0, 6).toLowerCase();
if (mwebPrefix === 'ltcmwe' || mwebPrefix === 'tmweb1') {
return parseMweb(address);
}
// else handle base58 addresses
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: number, index: number) => value !== expectedChecksum[index],
)
) {
throw new Error('Invalid address');
}
const validVersions = Object.keys(addressTypes).map(Number);
if (!validVersions.includes(version)) {
throw new Error('Invalid address');
}
const addressType = addressTypes[version];
return {
...addressType,
address,
bech32: false,
};
};
const validate = (address: string, network?: Network) => {
try {
const addressInfo = getAddressInfo(address);
if (network) {
return network === addressInfo.network;
}
return true;
} catch (error) {
return false;
}
};
export {getAddressInfo, Network, AddressType, type AddressInfo, validate};
export default validate;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment