/** * Bitcoin Taproot (P2TR) Chain Adapter * * Full Taproot transaction lifecycle: address derivation, UTXO management, * transaction building, FROST Schnorr signing, and broadcast. * * FROST-secp256k1 produces BIP-340 compatible Schnorr signatures that are * NATIVE to Taproot key-path spends -- no signature format conversion needed. * * Key features: * - **Real secp256k1 EC point arithmetic** for BIP-341 key tweaking * (lift_x, point_add, scalar_mul -- no external crypto dependencies) * - **Per-input sighash computation** for multi-input transactions * (each input gets its own BIP-341 sighash for independent FROST signing) * - **Full transaction serialization** with proper segwit witness structure * - **Broadcast via Mempool.space API** (mainnet, testnet, signet, regtest) * * No external dependencies beyond Node.js crypto (SHA-256). * * @example * ```typescript * import { BitcoinTaprootAdapter } from '@sequence0/sdk'; * * const btc = new BitcoinTaprootAdapter({ network: 'mainnet' }); * * // Derive a Taproot address from a FROST group public key * const addr = btc.deriveAddress('02abcdef...'); * console.log(addr.address); // bc1p... * * // Get balance and UTXOs * const balance = await btc.getBalance('bc1p...'); * const utxos = await btc.getUTXOs('bc1p...'); * * // Build, sign, and broadcast * const unsignedTx = await btc.buildTransaction( * { to: 'bc1p...recipient', amount: 50000, feeRate: 15 }, * 'bc1p...sender' * ); * // ... sign via FROST ... * const txid = await btc.broadcast(signedTxHex); * ``` */ import { ChainAdapter, BtcTransaction } from '../core/types'; /** Bitcoin network type */ export type BitcoinNetwork = 'mainnet' | 'testnet' | 'signet' | 'regtest'; /** UTXO from the indexer API */ export interface TaprootUtxo { /** Transaction hash (big-endian hex, 64 chars) */ txid: string; /** Output index */ vout: number; /** Value in satoshis */ value: number; /** ScriptPubKey (hex-encoded) */ scriptPubkey: string; /** Confirmation status */ status: { confirmed: boolean; blockHeight?: number; blockHash?: string; blockTime?: number; }; } /** Taproot address metadata returned by deriveAddress */ export interface TaprootAddressInfo { /** Bech32m-encoded Taproot address (bc1p... / tb1p...) */ address: string; /** X-only internal public key (64-char hex) */ xOnlyPubkey: string; /** X-only output key after tweak (64-char hex) */ outputKey: string; /** ScriptPubKey (hex-encoded, OP_1 <32-byte output key>) */ scriptPubkey: string; /** * Taproot tweak scalar (64-char hex). * This must be applied to the FROST group private key during signing: * tweaked_privkey = privkey + tweak (mod n) * The FROST signing request should include this tweak so agents can * adjust their secret shares before producing partial signatures. */ tapTweak: string; /** Network */ network: BitcoinNetwork; } /** Unsigned Taproot transaction ready for FROST signing */ export interface UnsignedTaprootTx { /** * Sighash for the first input (32 bytes, hex-encoded). * For single-input transactions this is all you need. * For multi-input transactions, use `sighashes` instead. */ sighash: string; /** * Per-input sighashes (32 bytes each, hex-encoded). * Each input has its own BIP-341 sighash that must be independently * signed via FROST. The same key signs all inputs, but each sighash * includes the input index and produces a different message. */ sighashes: string[]; /** Serialized unsigned transaction (hex-encoded) */ rawUnsigned: string; /** Transaction inputs */ inputs: TaprootTxInput[]; /** Transaction outputs */ outputs: TaprootTxOutput[]; /** Estimated virtual size in vbytes */ estimatedVsize: number; /** Estimated fee in satoshis */ estimatedFee: number; } /** Signed Taproot transaction */ export interface SignedTaprootTx { /** Serialized signed transaction (hex-encoded), ready for broadcast */ rawSigned: string; /** Transaction ID */ txid: string; /** Virtual size in vbytes */ vsize: number; } /** Transaction input (a UTXO being spent) */ export interface TaprootTxInput { txid: string; vout: number; value: number; scriptPubkey: string; } /** Transaction output */ export interface TaprootTxOutput { address: string; value: number; } /** Options for creating the adapter */ export interface BitcoinTaprootOptions { /** Bitcoin network (default: 'mainnet') */ network?: BitcoinNetwork; /** Custom Mempool/Blockstream API URL for UTXO queries and broadcast */ apiUrl?: string; /** Custom Electrum RPC URL (alternative to HTTP API) */ electrumUrl?: string; } /** Fee rate estimates from the mempool */ export interface FeeRateEstimate { /** Fee rate for next block confirmation (sat/vB) */ fastest: number; /** Fee rate for confirmation within 30 minutes (sat/vB) */ halfHour: number; /** Fee rate for confirmation within 1 hour (sat/vB) */ hour: number; /** Fee rate for economy confirmation (sat/vB) */ economy: number; /** Minimum relay fee (sat/vB) */ minimum: number; } export declare class BitcoinTaprootAdapter implements ChainAdapter { private network; private apiUrl; /** Optional: Taproot address to receive Sequence0 service fee (30% of miner fee) */ private feeAddress?; constructor(options?: BitcoinTaprootOptions); getRpcUrl(): string; /** * Derive a Taproot (P2TR) address from a FROST group public key. * * The FROST group verifying key is a secp256k1 point. For Taproot: * 1. Extract the x-only public key (32 bytes) * 2. Compute the Taproot tweak: t = hash_TapTweak(x_only_pubkey) * 3. Compute the output key: Q = P + t*G * 4. Encode as Bech32m with witness version 1 * * @param groupPubkeyHex - Hex-encoded FROST group verifying key (33 bytes compressed or 32 bytes x-only) * @returns TaprootAddressInfo with address, keys, and scriptPubkey */ deriveAddress(groupPubkeyHex: string): TaprootAddressInfo; /** * Compute the Taproot tweak for a FROST group public key. * * The FROST signing protocol must apply this tweak to the group private key * before signing. This ensures the Schnorr signature verifies against the * tweaked output key (which is what the scriptPubKey commits to). * * The tweak scalar t = hash_TapTweak(internal_key) is returned as hex. * During FROST signing, the group's secret share is tweaked: * tweaked_share = share + t (mod n) * * @param groupPubkeyHex - Hex-encoded FROST group verifying key (33 or 32 bytes) * @returns Hex-encoded 32-byte tweak scalar */ getTapTweak(groupPubkeyHex: string): string; /** * Fetch unspent transaction outputs (UTXOs) for a Taproot address. * * @param address - Bech32m Taproot address (bc1p... / tb1p...) * @returns Array of UTXOs sorted by value (largest first) */ getUTXOs(address: string): Promise; /** * Get the balance of a Bitcoin address in satoshis. * Includes both confirmed and unconfirmed (mempool) balances. * * @param address - Taproot address * @returns Balance in satoshis as a string */ getBalance(address: string): Promise; /** * Get the confirmed balance only (excluding mempool transactions). * * @param address - Taproot address * @returns Confirmed balance in satoshis as a string */ getConfirmedBalance(address: string): Promise; /** * Get recommended fee rates from the mempool. * * @returns Fee rate estimates in sat/vB */ getFeeRates(): Promise; /** * Estimate the fee for a transaction with the given number of inputs and outputs. * * @param inputCount - Number of Taproot inputs * @param outputCount - Number of outputs (including change) * @param feeRate - Fee rate in sat/vB * @returns Estimated fee in satoshis */ estimateFee(inputCount: number, outputCount: number, feeRate: number): number; /** * Build an unsigned Bitcoin Taproot transaction. * * Fetches UTXOs, selects inputs using a largest-first strategy, * constructs outputs (recipient + change), computes the BIP-341 * sighash, and returns the serialized unsigned transaction. * * The returned sighash is what gets passed to the FROST signing * protocol. The resulting 64-byte Schnorr signature is directly * usable as the Taproot witness. * * @param tx - Transaction parameters (to, amount, feeRate) * @param fromAddress - Sender's Taproot address * @returns Hex-encoded unsigned transaction data (JSON-encoded internally) */ buildTransaction(tx: BtcTransaction, fromAddress: string): Promise; /** * Build an unsigned Taproot transaction with explicit control over inputs and outputs. * * For advanced usage when you want to manually select UTXOs and construct * the transaction outputs. * * @param inputs - UTXOs to spend * @param outputs - Transaction outputs * @param xOnlyInternalKey - 32-byte x-only internal key (hex-encoded) * @param feeRate - Fee rate in sat/vB * @returns UnsignedTaprootTx ready for FROST signing */ buildUnsignedTx(inputs: TaprootTxInput[], outputs: TaprootTxOutput[], feeRate: number): UnsignedTaprootTx; /** * Attach FROST Schnorr signature(s) to an unsigned Taproot transaction. * * The FROST signing protocol produces 64-byte BIP-340 Schnorr signatures * (R_x || s) that are directly used as Taproot witness for key-path spends. * * For single-input transactions: pass a single 128-char hex signature. * For multi-input transactions: pass signatures separated by commas, or * a single signature that will be applied to all inputs (if all inputs * share the same signing key and the caller signs each sighash separately). * * @param unsignedTxHex - Hex-encoded unsigned transaction (from buildTransaction) * @param signatureHex - 64-byte FROST Schnorr signature(s). For multi-input * transactions, separate per-input signatures with commas. * @returns Hex-encoded signed transaction ready for broadcast */ attachSignature(unsignedTxHex: string, signatureHex: string): Promise; /** * Attach FROST Schnorr signature(s) with full output (returns structured data). * * @param unsignedTx - The UnsignedTaprootTx from buildUnsignedTx * @param signatureHex - 64-byte FROST Schnorr signature(s) (hex). For multi-input * transactions, pass an array of per-input signatures or a comma-separated string. * @returns SignedTaprootTx with raw_signed, txid, and vsize */ attachSignatureToTx(unsignedTx: UnsignedTaprootTx, signatureHex: string | string[]): SignedTaprootTx; /** * Parse signature hex into per-input signatures. * Supports: single sig (applied to all inputs), comma-separated, or concatenated. */ private parseSignatures; /** * Broadcast a signed Taproot transaction to the Bitcoin network. * * Accepts either a raw Bitcoin transaction hex or the hex-encoded JSON * format from attachSignature(). * * @param signedTx - Hex-encoded signed transaction * @returns Transaction ID (txid) */ broadcast(signedTx: string): Promise; /** * Get transaction details by txid. * * @param txid - Transaction ID * @returns Transaction data or null if not found */ getTransaction(txid: string): Promise; /** * Get the current block height. */ getBlockHeight(): Promise; /** * Check if an address is a valid Taproot (P2TR) address. */ isTaprootAddress(address: string): boolean; /** * Convert a Taproot address to its scriptPubKey. * P2TR scriptPubKey: OP_1 (0x51) + PUSH32 (0x20) + <32-byte witness program> */ addressToScriptPubkey(address: string): string; private selectUTXOs; /** * Serialize an unsigned Taproot transaction. * * Bitcoin transaction format (segwit): * - Version (4 bytes LE) * - Marker + Flag (00 01 for segwit) * - Input count (varint) * - Inputs (outpoint + empty scriptSig + sequence) * - Output count (varint) * - Outputs (value + scriptPubkey) * - Witness (placeholder for signing) * - Locktime (4 bytes LE) */ private serializeUnsignedTx; /** * Serialize a signed Taproot transaction with per-input signatures. * * @param unsignedTx - The unsigned transaction data * @param signatures - Array of per-input signature hex strings (128 chars each) */ private serializeSignedTx; /** * Compute the BIP-341 Taproot sighash for key-path spend. * * The sighash message for SIGHASH_DEFAULT (0x00) includes: * - Epoch (0x00) * - Sighash type (0x00) * - Transaction version (4 bytes LE) * - Locktime (4 bytes LE) * - SHA-256 of prevouts * - SHA-256 of amounts * - SHA-256 of scriptPubKeys * - SHA-256 of sequences * - SHA-256 of outputs * - Spend type (0x00 for key-path, no annex) * - Input index (4 bytes LE) */ /** * Compute all per-input BIP-341 sighashes for the transaction. * * Each input has its own sighash because the input_index field differs. * The common transaction-level hashes (prevouts, amounts, scripts, sequences, * outputs) are precomputed once and reused across all inputs. * * @returns Array of hex-encoded sighashes, one per input */ private computeAllSighashes; } /** * Create a Bitcoin Taproot adapter for mainnet. */ export declare function createBitcoinTaprootAdapter(options?: Omit): BitcoinTaprootAdapter; /** * Create a Bitcoin Taproot adapter for testnet. */ export declare function createBitcoinTestnetTaprootAdapter(options?: Omit): BitcoinTaprootAdapter; //# sourceMappingURL=bitcoin-taproot.d.ts.map