UNPKG

charms-wallet-js

Version:

Professional Bitcoin wallet library for Charms ecosystem using @scure stack

243 lines 9.36 kB
"use strict"; // Transaction Signer for Charms Wallet JS var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TransactionSigner = void 0; const bip39_1 = require("@scure/bip39"); const bip32_1 = require("@scure/bip32"); const btc = __importStar(require("@scure/btc-signer")); const base_1 = require("@scure/base"); const constants_1 = require("../constants"); const types_1 = require("../types"); /** * Professional transaction signer using @scure/btc-signer * Supports Taproot key-path spending with BIP86 derivation */ class TransactionSigner { constructor(network = constants_1.DEFAULT_NETWORK) { this.network = network; } /** * Signs a PSBT using the provided mnemonic * Automatically derives the correct keys for Taproot signing */ async signPSBT(psbtHex, utxo, mnemonic, derivationPath = constants_1.DEFAULT_DERIVATION_PATH) { try { // Validate inputs this.validateSigningInputs(psbtHex, utxo, mnemonic); // Parse PSBT const psbtBytes = base_1.hex.decode(psbtHex); const tx = btc.Transaction.fromPSBT(psbtBytes); // Derive Taproot keys const keys = await this.deriveTaprootKeys(mnemonic, derivationPath, 0); // Update input with witness UTXO if needed this.updateWitnessUtxo(tx, utxo, keys); // Sign the transaction tx.sign(keys.privateKey); // Finalize the transaction tx.finalize(); // Extract the final transaction const finalTx = tx.extract(); const txHex = base_1.hex.encode(finalTx); const txId = this.calculateTxId(finalTx); return { success: true, txid: txId, hex: txHex, size: finalTx.length, vsize: this.calculateVirtualSize(finalTx), fee: this.calculateFee(utxo, tx) }; } catch (error) { return { success: false, error: `Signing failed: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } /** * Signs a transaction and returns a SignedTransaction object */ async signTransaction(psbtHex, utxo, mnemonic, derivationPath) { const result = await this.signPSBT(psbtHex, utxo, mnemonic, derivationPath); if (!result.success || !result.txid || !result.hex) { throw new types_1.TransactionError(result.error || 'Transaction signing failed'); } return { txid: result.txid, hex: result.hex, size: result.size || 0, vsize: result.vsize || 0, fee: result.fee || 0 }; } /** * Derives Taproot keys from mnemonic using BIP86 derivation */ async deriveTaprootKeys(mnemonic, derivationPath = constants_1.DEFAULT_DERIVATION_PATH, index = 0) { try { // Convert mnemonic to seed const seed = await (0, bip39_1.mnemonicToSeed)(mnemonic); // Create HD key from seed const hdkey = bip32_1.HDKey.fromMasterSeed(seed); // Derive account key using provided path const accountKey = hdkey.derive(derivationPath); // Derive receiving chain (0) and address index const chainKey = accountKey.derive('0'); const addressKey = chainKey.derive(index.toString()); if (!addressKey.privateKey || !addressKey.publicKey) { throw new types_1.CryptographyError('Failed to derive private or public key'); } // Extract x-only public key for Taproot const xOnlyPubkey = addressKey.publicKey.slice(1); // Get network configuration const networkConfig = this.getNetworkConfig(); // Create Taproot payment const p2tr = btc.p2tr(xOnlyPubkey, undefined, networkConfig); if (!p2tr.address) { throw new types_1.CryptographyError('Failed to generate Taproot address'); } return { privateKey: addressKey.privateKey, publicKey: addressKey.publicKey, xOnlyPubkey, address: p2tr.address }; } catch (error) { throw new types_1.CryptographyError(`Failed to derive Taproot keys: ${error instanceof Error ? error.message : 'Unknown error'}`, { derivationPath, index, error }); } } /** * Updates PSBT input with witness UTXO data */ updateWitnessUtxo(tx, utxo, keys) { try { // Check if input already has witnessUtxo const input = tx.getInput(0); if (input.witnessUtxo) { return; // Already has witness UTXO } // Create script for the UTXO address const networkConfig = this.getNetworkConfig(); const p2tr = btc.p2tr(keys.xOnlyPubkey, undefined, networkConfig); // Update input with witness UTXO tx.updateInput(0, { witnessUtxo: { script: p2tr.script, amount: BigInt(utxo.amount) } }); } catch (error) { throw new types_1.TransactionError(`Failed to update witness UTXO: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Calculates transaction ID from raw transaction bytes */ calculateTxId(txBytes) { try { const tx = btc.Transaction.fromRaw(txBytes); return tx.id; } catch (error) { // Fallback: create a simple hash-based ID return base_1.hex.encode(txBytes.slice(0, 32)); // Use first 32 bytes as ID } } /** * Calculates virtual size of transaction */ calculateVirtualSize(txBytes) { // For Taproot transactions, virtual size is typically close to actual size // This is a simplified calculation return Math.ceil(txBytes.length * 0.75); // Approximate vsize calculation } /** * Calculates transaction fee */ calculateFee(utxo, tx) { try { // Get total output amount let totalOutput = 0; const outputCount = tx.outputsLength || 0; for (let i = 0; i < outputCount; i++) { const output = tx.getOutput(i); totalOutput += Number(output.amount); } // Fee = input amount - output amount return utxo.amount - totalOutput; } catch (error) { console.warn('Failed to calculate fee:', error); return 0; } } /** * Gets network configuration for @scure */ getNetworkConfig() { switch (this.network) { case 'mainnet': return btc.NETWORK; case 'testnet': case 'testnet4': return btc.TEST_NETWORK; default: throw new types_1.ValidationError(`Unsupported network: ${this.network}`); } } /** * Validates signing inputs */ validateSigningInputs(psbtHex, utxo, mnemonic) { // Validate PSBT hex if (!psbtHex || typeof psbtHex !== 'string') { throw new types_1.ValidationError('Invalid PSBT hex'); } try { base_1.hex.decode(psbtHex); } catch (error) { throw new types_1.ValidationError('Invalid PSBT hex format'); } // Validate UTXO if (!utxo || !utxo.txid || typeof utxo.vout !== 'number' || !utxo.amount || !utxo.address) { throw new types_1.ValidationError('Invalid UTXO format'); } // Validate mnemonic if (!mnemonic || typeof mnemonic !== 'string') { throw new types_1.ValidationError('Invalid mnemonic'); } const words = mnemonic.trim().split(/\s+/); if (words.length < 12 || words.length > 24) { throw new types_1.ValidationError('Invalid mnemonic word count'); } } } exports.TransactionSigner = TransactionSigner; //# sourceMappingURL=signer.js.map