UNPKG

@btc-vision/transaction

Version:

OPNet transaction library allows you to create and sign transactions for the OPNet network.

170 lines (146 loc) 4.91 kB
import { fromHex, type Network, opcodes, payments, script } from '@btc-vision/bitcoin'; import type { UTXO } from '../utxo/interfaces/IUTXO.js'; import type { IP2WSHAddress } from '../transaction/mineable/IP2WSHAddress.js'; /** * P2WDA Detection and Validation Utilities */ export class P2WDADetector { /** * Check if a UTXO is a P2WDA output by examining its script structure */ public static isP2WDAUTXO(utxo: UTXO): boolean { if (!utxo.witnessScript) { return false; } const witnessScript = utxo.witnessScript instanceof Uint8Array ? utxo.witnessScript : fromHex(utxo.witnessScript); return this.isP2WDAWitnessScript(witnessScript); } /** * Check if a witness script follows the P2WDA pattern */ public static isP2WDAWitnessScript(witnessScript: Uint8Array): boolean { try { const decompiled = script.decompile(witnessScript); if (!decompiled || decompiled.length !== 7) { return false; } // Check for 5 OP_2DROP operations for (let i = 0; i < 5; i++) { if (decompiled[i] !== opcodes.OP_2DROP) { return false; } } // Check for pubkey and OP_CHECKSIG return ( decompiled[5] instanceof Uint8Array && decompiled[5].length === 33 && // Compressed public key decompiled[6] === opcodes.OP_CHECKSIG ); } catch { return false; } } /** * Generate a P2WDA address from a public key */ public static generateP2WDAAddress( publicKey: Uint8Array, network: Network, ): IP2WSHAddress & { scriptPubKey: Uint8Array; } { if (publicKey.length !== 33) { throw new Error('Public key must be 33 bytes (compressed)'); } // Create the P2WDA witness script with 5x OP_2DROP const witnessScript = script.compile([ opcodes.OP_2DROP, opcodes.OP_2DROP, opcodes.OP_2DROP, opcodes.OP_2DROP, opcodes.OP_2DROP, publicKey, opcodes.OP_CHECKSIG, ]); // Wrap in P2WSH const p2wsh = payments.p2wsh({ redeem: { output: witnessScript }, network, }); if (!p2wsh.address || !p2wsh.output) { throw new Error('Failed to generate P2WDA address'); } return { address: p2wsh.address, witnessScript, scriptPubKey: p2wsh.output, }; } /** * Extract the public key from a P2WDA witness script */ public static extractPublicKeyFromP2WDA(witnessScript: Uint8Array): Uint8Array | null { try { const decompiled = script.decompile(witnessScript); if (!decompiled || decompiled.length !== 7) { return null; } // Check for 5x OP_2DROP pattern for (let i = 0; i < 5; i++) { if (decompiled[i] !== opcodes.OP_2DROP) { return null; } } if ( decompiled[5] instanceof Uint8Array && decompiled[5].length === 33 && decompiled[6] === opcodes.OP_CHECKSIG ) { return decompiled[5]; } return null; } catch { return null; } } /** * Create witness data for a simple P2WDA spend (no operation data) */ public static createSimpleP2WDAWitness( transactionSignature: Uint8Array, witnessScript: Uint8Array, ): Uint8Array[] { const witnessStack: Uint8Array[] = [transactionSignature]; // Add 10 empty buffers for the 5x OP_2DROP operations for (let i = 0; i < 10; i++) { witnessStack.push(new Uint8Array(0)); } witnessStack.push(witnessScript); return witnessStack; } /** * Validate P2WDA operation data signature */ public static validateP2WDASignature( _publicKey: Uint8Array, dataSignature: Uint8Array, _operationData: Uint8Array, ): boolean { return dataSignature.length === 64; // Schnorr signatures are always 64 bytes } /** * Calculate the witness size for P2WDA transaction estimation */ public static estimateP2WDAWitnessSize(dataSize: number = 0): number { return 72 + dataSize + 39 + 12; } /** * Check if a scriptPubKey is a P2WSH that could be P2WDA */ public static couldBeP2WDA(scriptPubKey: Uint8Array): boolean { return scriptPubKey.length === 34 && scriptPubKey[0] === 0x00 && scriptPubKey[1] === 0x20; } }