UNPKG

charms-wallet-js

Version:

Professional Bitcoin wallet library for Charms ecosystem using @scure stack

362 lines 16.2 kB
"use strict"; // Core Wallet implementation 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.CharmsWallet = void 0; const bip39_1 = require("@scure/bip39"); const english_1 = require("@scure/bip39/wordlists/english"); const bip32_1 = require("@scure/bip32"); const btc = __importStar(require("@scure/btc-signer")); const constants_1 = require("../constants"); const types_1 = require("../types"); const address_wallet_1 = require("./address-wallet"); /** * Core wallet class providing secure HD wallet functionality * Built on the @scure cryptographic stack for maximum security */ class CharmsWallet { constructor(config = {}) { this.config = { network: config.network || constants_1.DEFAULT_NETWORK, derivationPath: config.derivationPath || constants_1.DEFAULT_DERIVATION_PATH, addressType: config.addressType || 'taproot', storage: config.storage || {} }; } /** * Generates a cryptographically secure BIP39 mnemonic phrase * Uses 128 bits of entropy for 12-word mnemonic by default */ generateMnemonic(wordCount = constants_1.BIP39_CONSTANTS.DEFAULT_WORD_COUNT) { try { // Validate word count if (!constants_1.BIP39_CONSTANTS.SUPPORTED_WORD_COUNTS.includes(wordCount)) { throw new types_1.ValidationError(`Unsupported word count: ${wordCount}. Supported: ${constants_1.BIP39_CONSTANTS.SUPPORTED_WORD_COUNTS.join(', ')}`); } // Calculate entropy bits based on word count const entropyBits = wordCount === 12 ? constants_1.BIP39_CONSTANTS.ENTROPY_BITS_12 : constants_1.BIP39_CONSTANTS.ENTROPY_BITS_24; // Generate mnemonic with specified entropy const mnemonic = (0, bip39_1.generateMnemonic)(english_1.wordlist, entropyBits); return mnemonic; } catch (error) { throw new types_1.CryptographyError(`Failed to generate mnemonic: ${error instanceof Error ? error.message : 'Unknown error'}`, { wordCount, error }); } } /** * Validates a BIP39 mnemonic phrase */ validateMnemonic(mnemonic) { try { const isValid = (0, bip39_1.validateMnemonic)(mnemonic, english_1.wordlist); if (!isValid) { return { valid: false, error: 'Invalid mnemonic phrase. Please check the words and try again.' }; } return { valid: true }; } catch (error) { return { valid: false, error: `Mnemonic validation failed: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } /** * Static method to generate address with specific network * Params: mnemonic, network, index */ static async generateAddress(mnemonic, network, index = 0) { try { // Validate mnemonic const isValid = (0, bip39_1.validateMnemonic)(mnemonic, english_1.wordlist); if (!isValid) { throw new types_1.ValidationError('Invalid mnemonic phrase'); } // 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 BIP86 path: m/86'/0'/0' const accountKey = hdkey .deriveChild(86 + 0x80000000) // 86' (hardened) .deriveChild(0 + 0x80000000) // 0' (hardened) .deriveChild(0 + 0x80000000); // 0' (hardened) // Derive receiving chain (0) and address index const chainKey = accountKey.deriveChild(0); // 0 = receiving, 1 = change const addressKey = chainKey.deriveChild(index); if (!addressKey.privateKey || !addressKey.publicKey) { throw new types_1.CryptographyError('Failed to derive private or public key'); } // Extract x-only public key for Taproot (remove prefix byte) const xOnlyPubkey = addressKey.publicKey.slice(1); // Get network configuration based on parameter const networkConfig = network === 'mainnet' ? btc.NETWORK : btc.TEST_NETWORK; // Create Taproot payment using @scure/btc-signer const p2tr = btc.p2tr(xOnlyPubkey, undefined, networkConfig); if (!p2tr.address) { throw new types_1.CryptographyError('Failed to generate Taproot address'); } return p2tr.address; } catch (error) { throw new types_1.CryptographyError(`Failed to generate address: ${error instanceof Error ? error.message : 'Unknown error'}`, { network, index, error }); } } /** * Static method to get private key for specific address index * Returns only the private key without exposing other data */ static async getPrivateKey(mnemonic, network, index = 0) { try { // Validate mnemonic const isValid = (0, bip39_1.validateMnemonic)(mnemonic, english_1.wordlist); if (!isValid) { throw new types_1.ValidationError('Invalid mnemonic phrase'); } // 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 BIP86 path: m/86'/0'/0' const accountKey = hdkey .deriveChild(86 + 0x80000000) // 86' (hardened) .deriveChild(0 + 0x80000000) // 0' (hardened) .deriveChild(0 + 0x80000000); // 0' (hardened) // Derive receiving chain (0) and address index const chainKey = accountKey.deriveChild(0); // 0 = receiving, 1 = change const addressKey = chainKey.deriveChild(index); if (!addressKey.privateKey) { throw new types_1.CryptographyError('Failed to derive private key'); } return addressKey.privateKey; } catch (error) { throw new types_1.CryptographyError(`Failed to get private key: ${error instanceof Error ? error.message : 'Unknown error'}`, { network, index, error }); } } /** * Static method to get complete address data including keys * Returns address, private key, public key, and x-only public key */ static async getAddressData(mnemonic, network, index = 0) { try { // Validate mnemonic const isValid = (0, bip39_1.validateMnemonic)(mnemonic, english_1.wordlist); if (!isValid) { throw new types_1.ValidationError('Invalid mnemonic phrase'); } // 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 BIP86 path: m/86'/0'/0' const accountKey = hdkey .deriveChild(86 + 0x80000000) // 86' (hardened) .deriveChild(0 + 0x80000000) // 0' (hardened) .deriveChild(0 + 0x80000000); // 0' (hardened) // Derive receiving chain (0) and address index const chainKey = accountKey.deriveChild(0); // 0 = receiving, 1 = change const addressKey = chainKey.deriveChild(index); if (!addressKey.privateKey || !addressKey.publicKey) { throw new types_1.CryptographyError('Failed to derive private or public key'); } // Extract x-only public key for Taproot (remove prefix byte) const xOnlyPubkey = addressKey.publicKey.slice(1); // Get network configuration based on parameter const networkConfig = network === 'mainnet' ? btc.NETWORK : btc.TEST_NETWORK; // Create Taproot payment using @scure/btc-signer 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 get address data: ${error instanceof Error ? error.message : 'Unknown error'}`, { network, index, error }); } } // Creates AddressWallet from private key without exposing mnemonic static fromPrivateKey(privateKey, network) { const addressSuffix = privateKey.slice(0, 8).reduce((acc, byte) => acc + byte.toString(16).padStart(2, '0'), ''); const prefix = network === 'mainnet' ? 'bc1p' : 'tb1p'; const mockAddress = `${prefix}${addressSuffix}${'0'.repeat(54 - addressSuffix.length)}`; return new address_wallet_1.AddressWallet(privateKey, network, mockAddress); } /** * Derives Taproot keys from mnemonic using BIP86 derivation */ async deriveTaprootKeys(mnemonic, 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 BIP86 path: m/86'/0'/0' // Use individual derivation steps for @scure/bip32 compatibility const accountKey = hdkey .deriveChild(86 + 0x80000000) // 86' (hardened) .deriveChild(0 + 0x80000000) // 0' (hardened) .deriveChild(0 + 0x80000000); // 0' (hardened) // Derive receiving chain (0) and address index const chainKey = accountKey.deriveChild(0); // 0 = receiving, 1 = change const addressKey = chainKey.deriveChild(index); if (!addressKey.privateKey || !addressKey.publicKey) { throw new types_1.CryptographyError('Failed to derive private or public key'); } // Extract x-only public key for Taproot (remove prefix byte) const xOnlyPubkey = addressKey.publicKey.slice(1); // Get network configuration const networkConfig = this.getNetworkConfig(); // Create Taproot payment using @scure/btc-signer 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'}`, { index, error }); } } /** * Validates a Bitcoin address */ validateAddress(address) { try { const network = this.config.network; // Check Taproot addresses if (network === 'mainnet' && constants_1.ADDRESS_PATTERNS.MAINNET_P2TR.test(address)) { return { valid: true, network: 'mainnet', type: 'p2tr' }; } if ((network === 'testnet' || network === 'testnet4') && constants_1.ADDRESS_PATTERNS.TESTNET_P2TR.test(address)) { return { valid: true, network, type: 'p2tr' }; } // Check SegWit addresses if (network === 'mainnet' && constants_1.ADDRESS_PATTERNS.MAINNET_P2WPKH.test(address)) { return { valid: true, network: 'mainnet', type: 'p2wpkh' }; } if ((network === 'testnet' || network === 'testnet4') && constants_1.ADDRESS_PATTERNS.TESTNET_P2WPKH.test(address)) { return { valid: true, network, type: 'p2wpkh' }; } // Check legacy addresses if (network === 'mainnet' && constants_1.ADDRESS_PATTERNS.LEGACY.test(address)) { return { valid: true, network: 'mainnet', type: 'legacy' }; } if ((network === 'testnet' || network === 'testnet4') && constants_1.ADDRESS_PATTERNS.TESTNET_LEGACY.test(address)) { return { valid: true, network, type: 'legacy' }; } return { valid: false, error: `Invalid address format for ${network} network` }; } catch (error) { return { valid: false, error: `Address validation failed: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } /** * Creates wallet data object */ createWalletData(mnemonic, address) { return { mnemonic, address, created: new Date().toISOString(), network: this.config.network }; } /** * Gets the current network configuration */ getNetworkConfig() { const network = this.config.network; // Map our network types to @scure/btc-signer network objects switch (network) { case 'mainnet': return btc.NETWORK; case 'testnet': case 'testnet4': return btc.TEST_NETWORK; default: throw new types_1.ValidationError(`Unsupported network: ${network}`); } } /** * Gets the current wallet configuration */ getConfig() { return { ...this.config }; } /** * Updates wallet configuration */ updateConfig(updates) { Object.assign(this.config, updates); } /** * Utility method to copy text to clipboard (browser environment) */ async copyToClipboard(text) { try { if (typeof navigator !== 'undefined' && navigator.clipboard) { await navigator.clipboard.writeText(text); return true; } // Fallback for older browsers if (typeof document !== 'undefined') { const textArea = document.createElement('textarea'); textArea.value = text; document.body.appendChild(textArea); textArea.select(); const success = document.execCommand('copy'); document.body.removeChild(textArea); return success; } return false; } catch (error) { console.warn('Failed to copy to clipboard:', error); return false; } } } exports.CharmsWallet = CharmsWallet; //# sourceMappingURL=wallet.js.map