@btc-vision/transaction
Version:
OPNet transaction library allows you to create and sign transactions for the OPNet network.
211 lines (179 loc) • 8.35 kB
text/typescript
import { address, initEccLib, Network } from '@btc-vision/bitcoin';
import * as ecc from '@bitcoinerlab/secp256k1';
import { EcKeyPair } from './EcKeyPair.js';
import { BitcoinUtils } from '../utils/BitcoinUtils.js';
initEccLib(ecc);
export enum AddressTypes {
P2PKH = 'P2PKH',
P2OP = 'P2OP',
P2SH_OR_P2SH_P2WPKH = 'P2SH_OR_P2SH-P2WPKH',
P2PK = 'P2PK',
P2TR = 'P2TR',
P2WPKH = 'P2WPKH',
}
export class AddressVerificator {
/**
* Checks if the given address is a valid P2PKH address.
* @param inAddress - The address to check.
* @param network - The network to validate against.
* @returns - True if the address is a valid P2PKH address, false otherwise.
* @remarks This method is useful for validating legacy addresses (P2PKH) without
*/
public static isValidP2TRAddress(inAddress: string, network: Network): boolean {
if (!inAddress || inAddress.length < 50) return false;
let isValidTapRootAddress: boolean = false;
try {
address.toOutputScript(inAddress, network);
const decodedAddress = address.fromBech32(inAddress);
isValidTapRootAddress = decodedAddress.version === 1;
} catch {}
return isValidTapRootAddress;
}
/**
* Checks if the given address is a valid P2PKH address.
* @param inAddress - The address to check.
* @param network - The network to validate against.
* @returns - True if the address is a valid P2PKH address, false otherwise.
* @remarks This method is useful for validating legacy addresses (P2PKH) without
*/
public static isP2WPKHAddress(inAddress: string, network: Network): boolean {
if (!inAddress || inAddress.length < 20 || inAddress.length > 50) return false;
let isValidSegWitAddress: boolean = false;
try {
// Decode the address using Bech32
const decodedAddress = address.fromBech32(inAddress);
// Ensure the decoded address matches the provided network
address.toOutputScript(inAddress, network);
// Check if the address is P2WPKH (version 0)
isValidSegWitAddress =
decodedAddress.version === 0 && decodedAddress.data.length === 20;
} catch {}
return isValidSegWitAddress;
}
/**
* Checks if the given address is a valid P2PKH or P2SH address.
* @param addy - The address to check.
* @param network - The network to validate against.
* @returns - True if the address is a valid P2PKH or P2SH address, false otherwise.
* @remarks This method is useful for validating legacy addresses (P2PKH or P2SH) without
*/
public static isP2PKHOrP2SH(addy: string, network: Network): boolean {
try {
// First, try to decode as a Base58Check address (P2PKH, P2SH, or P2SH-P2WPKH)
const decodedBase58 = address.fromBase58Check(addy);
if (decodedBase58.version === network.pubKeyHash) {
// P2PKH: Legacy address (starting with '1' for mainnet, 'm/n' for testnet)
return true;
}
return decodedBase58.version === network.scriptHash;
} catch (error) {
// If decoding fails or version is not valid, it's not a valid legacy address
return false;
}
}
/**
* Checks if the input is a valid hexadecimal public key (P2PK).
* Public keys can be compressed (66 characters) or uncompressed (130 characters).
*
* @param input - The input string to check.
* @param network - The Bitcoin network to validate against (mainnet, testnet, etc.).
* @returns - True if the input is a valid public key, false otherwise.
*/
public static isValidPublicKey(input: string, network: Network): boolean {
try {
if (input.startsWith('0x')) {
input = input.slice(2);
}
// Compressed public keys are 66 characters long (0x02 or 0x03 prefix + 32 bytes)
// Uncompressed public keys are 130 characters long (0x04 prefix + 64 bytes)
if (!BitcoinUtils.isValidHex(input)) {
return false;
}
if (input.length === 64) {
return true;
}
const pubKeyBuffer = Buffer.from(input, 'hex');
if ((input.length === 130 && pubKeyBuffer[0] === 0x06) || pubKeyBuffer[0] === 0x07) {
return true;
}
if (input.length === 66 || input.length === 130) {
// Check if the input can be parsed as a valid public key
EcKeyPair.fromPublicKey(pubKeyBuffer, network);
return true;
}
} catch (e) {
return false;
}
return false; // Not a valid public key
}
/**
* Checks if the address requires a redeem script to spend funds.
* @param {string} addy - The address to check.
* @param {Network} network - The network to validate against.
* @returns {boolean} - True if the address requires a redeem script, false otherwise.
*/
public static requireRedeemScript(addy: string, network: Network): boolean {
try {
// First, try to decode as a Base58Check address (P2PKH, P2SH, or P2SH-P2WPKH)
const decodedBase58 = address.fromBase58Check(addy);
if (decodedBase58.version === network.pubKeyHash) {
return false;
}
return decodedBase58.version === network.scriptHash;
} catch {
return false;
}
}
/**
* Validates if a given Bitcoin address is of the specified type and network.
* - P2PKH (Legacy address starting with '1' for mainnet or 'm/n' for testnet)
* - P2SH (Legacy address starting with '3' for mainnet or '2' for testnet)
* - P2SH-P2WPKH (Wrapped SegWit)
* - P2PK (Pay to PubKey, technically treated similarly to P2PKH)
* - P2WPKH (SegWit address starting with 'bc1q' for mainnet or 'tb1q' for testnet)
* - P2TR (Taproot address starting with 'bc1p' for mainnet or 'tb1p' for testnet)
*
* @param addy - The Bitcoin address to validate.
* @param network - The Bitcoin network to validate against (mainnet, testnet, etc.).
* @returns - The type of the valid Bitcoin address, or null if invalid.
*/
public static detectAddressType(addy: string, network: Network): AddressTypes | null {
if (AddressVerificator.isValidPublicKey(addy, network)) {
return AddressTypes.P2PK;
}
try {
// First, try to decode as a Base58Check address (P2PKH, P2SH, or P2SH-P2WPKH)
const decodedBase58 = address.fromBase58Check(addy);
if (decodedBase58.version === network.pubKeyHash) {
// P2PKH: Legacy address (starting with '1' for mainnet, 'm/n' for testnet)
return AddressTypes.P2PKH;
}
if (decodedBase58.version === network.scriptHash) {
// P2SH: Could be P2SH (general) or P2SH-P2WPKH (wrapped SegWit)
return AddressTypes.P2SH_OR_P2SH_P2WPKH;
}
} catch {}
try {
// Try to decode as a Bech32 or Bech32m address (P2WPKH or P2TR)
const decodedBech32 = address.fromBech32(addy);
if (
(decodedBech32.prefix === network.bech32Opnet ||
decodedBech32.prefix === network.bech32) &&
decodedBech32.version === 16
) {
return AddressTypes.P2OP;
}
if (decodedBech32.prefix === network.bech32) {
// P2WPKH: SegWit address (starting with 'bc1q' for mainnet, 'tb1q' for testnet)
if (decodedBech32.version === 0 && decodedBech32.data.length === 20) {
return AddressTypes.P2WPKH;
}
// P2TR: Taproot address (starting with 'bc1p' for mainnet, 'tb1p' for testnet)
if (decodedBech32.version === 1 && decodedBech32.data.length === 32) {
return AddressTypes.P2TR;
}
}
} catch {}
return null; // Not a valid or recognized Bitcoin address type
}
}