UNPKG

@btc-vision/btc-runtime

Version:

Bitcoin L1 Smart Contract Runtime for OP_NET. Build decentralized applications on Bitcoin using AssemblyScript and WebAssembly. Fully audited.

209 lines (188 loc) 8.11 kB
import { CsvP2wshResult, MultisigP2wshResult } from './ScriptUtils'; import { Segwit } from './Segwit'; import { BitcoinScript } from './Script'; import { sha256 } from '../env/global'; /** * Ct provides constant-time comparison functions * This is important for cryptographic operations to avoid timing attacks */ @final export class Ct { /** * Compare two 32-byte arrays in constant time * Returns true if they are equal, false otherwise * * The function always takes the same amount of time regardless of where * the arrays differ, which prevents timing-based attacks */ @inline public static eq32(a: Uint8Array, b: Uint8Array): bool { // XOR the lengths with 32 to detect length mismatches let d: i32 = (a.length ^ 32) | (b.length ^ 32); // Compare each byte, accumulating differences in d for (let i = 0; i < 32; i++) { // Handle arrays shorter than 32 bytes by treating missing bytes as 0 const ai: u8 = i < a.length ? a[i] : 0; const bi: u8 = i < b.length ? b[i] : 0; // XOR accumulates any differences without early exit d |= ai ^ bi; } // d will be 0 only if all bytes matched return d == 0; } } /** * BitcoinAddresses provides high-level functions for creating and verifying * various types of Bitcoin addresses, particularly those using witness scripts */ @final export class BitcoinAddresses { /** * Create a witness script for a CSV (CheckSequenceVerify) timelock * This script requires a certain number of blocks to pass before spending * * @param pubkey - The public key that can spend after the timelock * @param csvBlocks - Number of blocks to wait (must be 0-65535) * @returns The witness script bytes */ public static csvWitnessScript(pubkey: Uint8Array, csvBlocks: i32): Uint8Array { return BitcoinScript.csvTimelock(pubkey, csvBlocks); } /** * Create a P2WSH (Pay to Witness Script Hash) address with CSV timelock * This creates both the address and the witness script needed to spend it * * @param pubkey - The public key that can spend after the timelock * @param csvBlocks - Number of blocks to wait * @param hrp - Human-readable part for the address (e.g., "bc" for mainnet) * @returns Object containing both the address and witness script */ public static csvP2wshAddress(pubkey: Uint8Array, csvBlocks: i32, hrp: string): CsvP2wshResult { const ws = BitcoinAddresses.csvWitnessScript(pubkey, csvBlocks); const addr = Segwit.p2wsh(hrp, ws); return new CsvP2wshResult(addr, ws); } /** * Verify that a given address corresponds to a specific CSV timelock setup * This is useful for validating that an address was created with expected parameters * * @param pubkey - The expected public key * @param csvBlocks - The expected number of CSV blocks * @param address - The address to verify * @param hrp - Expected human-readable part * @param strictMinimal - Whether to enforce strict minimal encoding rules * @returns true if the address matches the expected parameters */ public static verifyCsvP2wshAddress( pubkey: Uint8Array, csvBlocks: i32, address: string, hrp: string, strictMinimal: bool = true, ): bool { // Try to decode the address - this replaces the try-catch block const dec = Segwit.decodeOrNull(address); if (!dec) return false; // Verify it's a v0 witness program with 32-byte hash if (dec.version != 0 || dec.hrp != hrp || dec.program.length != 32) return false; // Reconstruct the witness script and verify it matches const ws = BitcoinAddresses.csvWitnessScript(pubkey, csvBlocks); // Parse the witness script to ensure it's well-formed const rec = BitcoinScript.recognizeCsvTimelock(ws, strictMinimal); if (!rec.ok || rec.csvBlocks != csvBlocks) return false; // Compute the script hash and compare with the address program const prog = sha256(ws); return Ct.eq32(dec.program, prog); } /** * Create a witness script for a multisig setup * This creates a script requiring m-of-n signatures to spend * * @param m - Number of required signatures * @param pubkeys - Array of public keys (n total) * @returns The witness script bytes */ public static multisigWitnessScript(m: i32, pubkeys: Array<Uint8Array>): Uint8Array { return BitcoinScript.multisig(m, pubkeys); } /** * Create a P2WSH multisig address * This creates both the address and the witness script for a multisig setup * * @param m - Number of required signatures * @param pubkeys - Array of public keys * @param hrp - Human-readable part for the address * @returns Object containing both the address and witness script */ public static multisigP2wshAddress( m: i32, pubkeys: Array<Uint8Array>, hrp: string, ): MultisigP2wshResult { const ws = BitcoinAddresses.multisigWitnessScript(m, pubkeys); const addr = Segwit.p2wsh(hrp, ws); return new MultisigP2wshResult(addr, ws); } /** * Verify that a given address corresponds to a specific multisig setup * This validates that an address was created with the expected m-of-n parameters * * @param m - Expected number of required signatures * @param pubkeys - Expected array of public keys * @param address - The address to verify * @param hrp - Expected human-readable part * @returns true if the address matches the expected parameters */ public static verifyMultisigP2wshAddress( m: i32, pubkeys: Array<Uint8Array>, address: string, hrp: string, ): bool { // Decode the address safely const dec = Segwit.decodeOrNull(address); if (!dec) return false; // Verify it's a v0 witness program with 32-byte hash if (dec.version != 0 || dec.hrp != hrp || dec.program.length != 32) return false; // Reconstruct the witness script and compare const ws = BitcoinAddresses.multisigWitnessScript(m, pubkeys); const prog = sha256(ws); return Ct.eq32(dec.program, prog); } /** * Create a Taproot (P2TR) key-path spend address * This is the simplest form of Taproot address, spendable with a single key * * @param outputKeyX32 - The 32-byte X coordinate of the output key * @param hrp - Human-readable part for the address * @returns The Bech32m-encoded address */ public static p2trKeyPathAddress(outputKeyX32: Uint8Array, hrp: string): string { return Segwit.p2tr(hrp, outputKeyX32); } /** * Create a Pay-to-Witness-Public-Key-Hash (P2WPKH) address * @param pubkey - The public key (33 bytes compressed or 65 bytes uncompressed) * @param hrp - Human-readable part (e.g., "bc" for mainnet) * @returns The Bech32-encoded address */ public static p2wpkh(pubkey: Uint8Array, hrp: string): string { return Segwit.p2wpkh(hrp, pubkey); } /** * Verify that a given address corresponds to a specific Taproot output key * * @param outputKeyX32 - The expected 32-byte X coordinate * @param address - The address to verify * @param hrp - Expected human-readable part * @returns true if the address matches the expected output key */ public static verifyP2trAddress(outputKeyX32: Uint8Array, address: string, hrp: string): bool { // Decode the address safely const dec = Segwit.decodeOrNull(address); if (!dec) return false; // Verify it's a v1 witness program with 32-byte key if (dec.version != 1 || dec.hrp != hrp || dec.program.length != 32) return false; // Compare the output key return Ct.eq32(dec.program, outputKeyX32); } }