UNPKG

j-bitcoin

Version:

Comprehensive JavaScript cryptocurrency wallet library for Bitcoin (BTC), Bitcoin Cash (BCH), and Bitcoin SV (BSV) with custodial and non-custodial wallet support, threshold signatures, and multiple address formats

1,200 lines (1,169 loc) 59.4 kB
/** * @fileoverview Main wallet classes for J-Bitcoin library providing custodial and non-custodial wallet functionality * * This module implements two distinct wallet architectures for Bitcoin cryptocurrency operations: * * **Custodial_Wallet**: Traditional single-party control wallet using hierarchical deterministic * key derivation (BIP32) with standard ECDSA signatures. Suitable for individual users and * applications requiring simple key management. * * **Non_Custodial_Wallet**: Advanced multi-party threshold signature scheme (TSS) implementation * enabling distributed key management without trusted dealers. Ideal for corporate treasuries, * escrow services, and high-security applications requiring multi-party authorization. * * @author yfbsei * @version 1.0.0 * @since 1.0.0 * * @requires fromSeed * @requires derive * @requires bip39 * @requires ecdsa * @requires standardKey * @requires address * @requires ThresholdSignature * @requires bn.js * @requires @noble/curves/secp256k1 * * @example * // Import wallet classes * import { Custodial_Wallet, Non_Custodial_Wallet } from './wallet.js'; * * // Create custodial wallet * const [mnemonic, custodialWallet] = Custodial_Wallet.fromRandom('main'); * * // Create threshold wallet * const thresholdWallet = Non_Custodial_Wallet.fromRandom('main', 3, 2); */ import fromSeed from './BIP32/fromSeed.js'; import derive from './BIP32/derive.js'; import bip39 from './BIP39/bip39.js'; import ecdsa from './ECDSA/ecdsa.js'; import { standardKey, address } from './utilities/encodeKeys.js'; import ThresholdSignature from './Threshold-signature/threshold_signature.js'; import BN from 'bn.js'; import { secp256k1 } from '@noble/curves/secp256k1'; /** * @typedef {Object} HDKeys * @description Hierarchical deterministic key pair following BIP32 specification * @property {string} HDpri - Extended private key in xprv/tprv format (Base58Check encoded) * @property {string} HDpub - Extended public key in xpub/tpub format (Base58Check encoded) * @example * // Example HD key pair * const hdKeys = { * HDpri: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", * HDpub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" * }; */ /** * @typedef {Object} KeyPair * @description Standard Bitcoin key pair for cryptographic operations * @property {string} pri - WIF-encoded private key (Wallet Import Format) * @property {string} pub - Hex-encoded compressed public key (33 bytes) * @example * // Example key pair * const keyPair = { * pri: "L5HgWvFghocq1FmxSjKNaGhVN8f67p6xYg5pY7M8FE77HXwHtGGu", * pub: "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2" * }; */ /** * @typedef {Object} ChildKeyInfo * @description Information about a derived child key in the HD wallet tree * @property {number} depth - Derivation depth in the HD tree (0 = master, 1 = account, etc.) * @property {number} childIndex - Index of this child key in its derivation level * @property {HDKeys} hdKey - HD key pair for this child * @property {KeyPair} keypair - Standard key pair for this child * @property {string} address - Bitcoin address generated from this child key * @example * // Child key at m/44'/0'/0'/0/0 * const childInfo = { * depth: 5, * childIndex: 0, * hdKey: { HDpri: "...", HDpub: "..." }, * keypair: { pri: "...", pub: "..." }, * address: "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2" * }; */ /** * @typedef {Array} ECDSASignatureResult * @description ECDSA signature result with recovery information * @property {Uint8Array} 0 - DER-encoded signature bytes * @property {number} 1 - Recovery ID (0-3) for public key recovery * @example * const [signature, recoveryId] = wallet.sign("Hello Bitcoin!"); * console.log(signature); // Uint8Array with signature bytes * console.log(recoveryId); // Number 0-3 */ /** * @typedef {Object} ThresholdSignatureResult * @description Complete threshold signature with metadata and recovery information * @property {Object} sig - ECDSA signature object with r and s components * @property {bigint} sig.r - Signature r value as BigInt * @property {bigint} sig.s - Signature s value as BigInt * @property {string} serialized_sig - Base64-encoded compact signature format (65 bytes) * @property {Buffer} msgHash - SHA256 hash of the signed message (32 bytes) * @property {number} recovery_id - Recovery ID for public key recovery (0-3) * @example * const signature = thresholdWallet.sign("Multi-party transaction"); * console.log(signature.sig.r); // BigInt r value * console.log(signature.serialized_sig); // "base64-encoded-signature" * console.log(signature.recovery_id); // 0, 1, 2, or 3 */ /** * Custodial wallet implementation supporting hierarchical deterministic key derivation * and standard ECDSA signatures. Suitable for single-party control scenarios. * * This class provides traditional Bitcoin wallet functionality with full control over * private keys. It implements BIP32 hierarchical deterministic key derivation, allowing * generation of unlimited child keys from a single seed. Perfect for individual users, * mobile wallets, and applications requiring straightforward key management. * * **Key Features:** * - BIP32 hierarchical deterministic key derivation * - BIP39 mnemonic phrase support for backup and recovery * - Standard ECDSA signature generation and verification * - Support for Bitcoin mainnet and testnet * - Child key derivation with configurable paths * - Address generation for receiving payments * * **Security Model:** * - Single point of control (private key holder has full access) * - Suitable for individual users and trusted environments * - Mnemonic phrases enable secure backup and recovery * - Child keys provide address privacy without exposing master key * * **Use Cases:** * - Personal Bitcoin wallets * - Mobile wallet applications * - Desktop wallet software * - Simple payment processing systems * - Development and testing environments * * @class Custodial_Wallet * @since 1.0.0 * * @example * // Generate a new random wallet * const [mnemonic, wallet] = Custodial_Wallet.fromRandom('main'); * console.log('Mnemonic:', mnemonic); * console.log('Address:', wallet.address); * * @example * // Import from existing mnemonic * const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; * const wallet = Custodial_Wallet.fromMnemonic('main', mnemonic, 'password'); * console.log('Imported address:', wallet.address); * * @example * // Derive child keys for different purposes * const wallet = Custodial_Wallet.fromRandom('main')[1]; * * // BIP44 Bitcoin receiving addresses * wallet.derive("m/44'/0'/0'/0/0", 'pri'); // First receiving address * wallet.derive("m/44'/0'/0'/0/1", 'pri'); // Second receiving address * * // BIP44 Bitcoin change addresses * wallet.derive("m/44'/0'/0'/1/0", 'pri'); // First change address * * console.log('Child keys:', Array.from(wallet.child_keys)); * * @example * // Sign and verify messages * const wallet = Custodial_Wallet.fromRandom('main')[1]; * const message = "Hello Bitcoin!"; * * // Sign message * const [signature, recoveryId] = wallet.sign(message); * * // Verify signature * const isValid = wallet.verify(signature, message); * console.log('Signature valid:', isValid); // true */ class Custodial_Wallet { /** * Private field storing the serialization format for key derivation operations. * Contains cryptographic parameters, chain codes, and metadata required for * BIP32 hierarchical deterministic key derivation. * * @private * @type {Object} * @memberof Custodial_Wallet */ #serialization_format; /** * Creates a new Custodial_Wallet instance with specified master keys and network configuration. * * This constructor initializes a wallet with pre-generated master keys and serialization * format. It's typically called internally by static factory methods rather than directly. * The wallet instance provides access to HD keys, standard key pairs, Bitcoin addresses, * and child key derivation capabilities. * * @param {string} net - Network type ('main' for mainnet, 'test' for testnet) * @param {Object} master_keys - Master key information * @param {HDKeys} master_keys.hdKey - Hierarchical deterministic keys * @param {KeyPair} master_keys.keypair - Standard key pair (WIF private key, hex public key) * @param {string} master_keys.address - Bitcoin address for receiving payments * @param {Object} serialization_format - Internal serialization format for key derivation * * @throws {Error} If network type is not 'main' or 'test' * @throws {Error} If master keys are invalid or malformed * * @example * // Typically used internally by factory methods * const masterKeys = { * hdKey: { HDpri: "xprv...", HDpub: "xpub..." }, * keypair: { pri: "L5Hg...", pub: "0339..." }, * address: "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2" * }; * const wallet = new Custodial_Wallet('main', masterKeys, serializationFormat); */ constructor(net, master_keys, serialization_format) { /** * Network type for this wallet instance. * Determines address formats, version bytes, and network-specific parameters. * * @type {string} * @readonly * @memberof Custodial_Wallet * @example * console.log(wallet.net); // "main" or "test" */ this.net = net; /** * Hierarchical deterministic key pair for this wallet. * Contains both extended private and public keys in standard BIP32 format. * Used for deriving child keys and maintaining the HD wallet structure. * * @type {HDKeys} * @readonly * @memberof Custodial_Wallet * @example * console.log(wallet.hdKey.HDpri); // "xprv9s21ZrQH143K..." * console.log(wallet.hdKey.HDpub); // "xpub661MyMwAqRbcF..." */ this.hdKey = master_keys.hdKey; /** * Standard key pair for direct cryptographic operations. * Contains WIF-encoded private key and hex-encoded compressed public key. * Used for signing transactions and generating addresses. * * @type {KeyPair} * @readonly * @memberof Custodial_Wallet * @example * console.log(wallet.keypair.pri); // "L5HgWvFghocq1FmxSjKNaGhVN8f67p6xYg5pY7M8FE77HXwHtGGu" * console.log(wallet.keypair.pub); // "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2" */ this.keypair = master_keys.keypair; /** * Bitcoin address for this wallet, derived from the master public key. * Used for receiving payments and identifying the wallet on the blockchain. * Format depends on network (1... for mainnet, m/n... for testnet). * * @type {string} * @readonly * @memberof Custodial_Wallet * @example * console.log(wallet.address); // "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2" */ this.address = master_keys.address; /** * Set of derived child keys from this wallet. * Contains information about all child keys derived using the derive() method. * Each entry includes depth, index, keys, and address information. * * @type {Set<ChildKeyInfo>} * @memberof Custodial_Wallet * @example * wallet.derive("m/0'/1", 'pri'); * console.log(wallet.child_keys.size); // 1 * * for (const childKey of wallet.child_keys) { * console.log('Child address:', childKey.address); * console.log('Derivation depth:', childKey.depth); * } */ this.child_keys = new Set(); // Store serialization format for internal use this.#serialization_format = serialization_format; } /** * Generates a new random wallet with cryptographically secure mnemonic phrase. * * This static factory method creates a fresh wallet using BIP39 mnemonic generation * and BIP32 hierarchical deterministic key derivation. The generated mnemonic provides * a human-readable backup that can restore the entire wallet and all derived keys. * * **Process:** * 1. Generate 128 bits of cryptographically secure entropy * 2. Create 12-word BIP39 mnemonic with checksum validation * 3. Derive 512-bit seed using PBKDF2-HMAC-SHA512 * 4. Generate BIP32 master keys from seed * 5. Create wallet instance with generated keys * * **Security:** * - Uses cryptographically secure random number generation * - Mnemonic includes built-in checksum for error detection * - Optional passphrase provides additional security layer * - Generated keys follow industry standard specifications * * @static * @param {string} [net='main'] - Network type ('main' for mainnet, 'test' for testnet) * @param {string} [passphrase=''] - Optional passphrase for additional security (BIP39) * @returns {Array} Tuple containing mnemonic phrase and wallet instance * @returns {string} returns.0 - Generated 12-word mnemonic phrase * @returns {Custodial_Wallet} returns.1 - New wallet instance * * @throws {Error} If mnemonic generation fails or checksum is invalid * @throws {Error} If network parameter is invalid * * @example * // Generate mainnet wallet * const [mnemonic, wallet] = Custodial_Wallet.fromRandom('main'); * console.log('Mnemonic:', mnemonic); * // "abandon ability able about above absent absorb abstract absurd abuse access accident" * console.log('Address:', wallet.address); * // "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2" * * @example * // Generate testnet wallet with passphrase * const [mnemonic, testWallet] = Custodial_Wallet.fromRandom('test', 'my-secure-passphrase'); * console.log('Testnet address:', testWallet.address); * // "mgRpP3zP1hmxyoeYJgfbcmN3c2Qsurw48D" * * @example * // Store mnemonic securely for backup * const [mnemonic, wallet] = Custodial_Wallet.fromRandom('main', 'company-passphrase'); * * // Store mnemonic in secure location (encrypted, offline, etc.) * secureStorage.store('wallet-mnemonic', mnemonic); * secureStorage.store('wallet-passphrase', 'company-passphrase'); * * // Wallet can be restored later using mnemonic + passphrase * const restoredWallet = Custodial_Wallet.fromMnemonic('main', mnemonic, 'company-passphrase'); */ static fromRandom(net = 'main', passphrase = '') { const { mnemonic, seed } = bip39.random(passphrase); return [mnemonic, this.fromSeed(net, seed)]; } /** * Creates a wallet from an existing BIP39 mnemonic phrase with optional passphrase. * * This static factory method restores a wallet from a previously generated mnemonic * phrase. It validates the mnemonic checksum, derives the cryptographic seed using * PBKDF2, and reconstructs the exact same wallet that was originally created. * This enables secure backup and recovery of Bitcoin wallets. * * **Validation Process:** * 1. Parse mnemonic into individual words * 2. Validate words exist in BIP39 wordlist * 3. Verify built-in checksum for error detection * 4. Derive seed using PBKDF2-HMAC-SHA512 with salt * 5. Generate identical master keys as original wallet * * **Compatibility:** * - Works with any BIP39-compliant mnemonic * - Compatible with hardware wallets (Ledger, Trezor) * - Interoperable with other Bitcoin wallet software * - Supports 12-word mnemonics (this implementation) * * @static * @param {string} [net='main'] - Network type ('main' for mainnet, 'test' for testnet) * @param {string} [mnemonic=''] - 12-word BIP39 mnemonic phrase (space-separated) * @param {string} [passphrase=''] - Optional passphrase used during generation * @returns {Custodial_Wallet} Restored wallet instance with identical keys * * @throws {Error} "invalid checksum" if mnemonic checksum validation fails * @throws {Error} If mnemonic format is invalid or contains unknown words * @throws {Error} If network parameter is invalid * * @example * // Restore wallet from mnemonic * const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; * const wallet = Custodial_Wallet.fromMnemonic('main', mnemonic); * console.log('Restored address:', wallet.address); * * @example * // Restore with passphrase (must match original) * const mnemonicWithPass = "legal winner thank year wave sausage worth useful legal winner thank yellow"; * const passphrase = "my-secure-passphrase"; * const wallet = Custodial_Wallet.fromMnemonic('main', mnemonicWithPass, passphrase); * * @example * // Handle restoration errors gracefully * try { * const invalidMnemonic = "invalid mnemonic phrase with wrong checksum"; * const wallet = Custodial_Wallet.fromMnemonic('main', invalidMnemonic); * } catch (error) { * console.error('Failed to restore wallet:', error.message); * // Handle error: show user-friendly message, request valid mnemonic * } * * @example * // Cross-platform wallet restoration * // Mnemonic generated on mobile app, restored on desktop * const mobileMnemonic = getUserInput('Enter your 12-word backup phrase:'); * const desktopWallet = Custodial_Wallet.fromMnemonic('main', mobileMnemonic); * * // Wallet will have identical addresses and keys as mobile version * console.log('Synced address:', desktopWallet.address); */ static fromMnemonic(net = 'main', mnemonic = '', passphrase = '') { const seed = bip39.mnemonic2seed(mnemonic, passphrase); return this.fromSeed(net, seed); } /** * Creates a wallet from a hex-encoded cryptographic seed (typically from BIP39). * * This static factory method creates a wallet directly from a seed value, bypassing * mnemonic processing. The seed is used to generate BIP32 master keys through * HMAC-SHA512 computation. This method is typically used internally by other * factory methods or when working with pre-computed seeds. * * **Seed Requirements:** * - Must be hex-encoded string * - Recommended length: 128-512 bits (32-128 hex characters) * - Should be generated with cryptographically secure randomness * - BIP39 seeds are 512 bits (128 hex characters) * * **Key Generation Process:** * 1. Convert hex seed to binary format * 2. Compute HMAC-SHA512 with "Bitcoin seed" as key * 3. Split result into private key (256 bits) and chain code (256 bits) * 4. Generate corresponding public key using secp256k1 * 5. Create extended keys with network-specific version bytes * * @static * @param {string} [net='main'] - Network type ('main' for mainnet, 'test' for testnet) * @param {string} [seed="000102030405060708090a0b0c0d0e0f"] - Hex-encoded cryptographic seed * @returns {Custodial_Wallet} New wallet instance derived from seed * * @throws {Error} If seed is not valid hexadecimal format * @throws {Error} If derived private key is invalid (extremely rare) * @throws {Error} If network parameter is invalid * * @example * // Create wallet from hex seed * const seed = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; * const wallet = Custodial_Wallet.fromSeed('main', seed); * console.log('Seed-derived address:', wallet.address); * * @example * // Use BIP39-derived seed * const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; * const bip39Seed = bip39.mnemonic2seed(mnemonic); * const wallet = Custodial_Wallet.fromSeed('main', bip39Seed); * * @example * // Custom seed for testing (deterministic addresses) * const testSeed = "deadbeefcafebabe".repeat(8); // 128 hex chars * const testWallet = Custodial_Wallet.fromSeed('test', testSeed); * console.log('Test address:', testWallet.address); */ static fromSeed(net = 'main', seed = "000102030405060708090a0b0c0d0e0f") { const [hdKey, serialization_format] = fromSeed(seed, net); return new this( net, { hdKey, keypair: standardKey(serialization_format.privKey, serialization_format.pubKey), address: address(serialization_format.versionByte.pubKey, serialization_format.pubKey.key) }, serialization_format ); } /** * Derives a child key from the current wallet using BIP32 hierarchical deterministic path. * * This method implements BIP32 child key derivation, allowing generation of child keys * from the master key or any previously derived key. It supports both hardened and * non-hardened derivation paths, with automatic path parsing and validation. * * **Derivation Types:** * - **Hardened derivation** (index ≥ 2³¹, marked with '): Requires private key, * provides security isolation between parent and child * - **Non-hardened derivation** (index < 2³¹): Can derive from public key only, * enables watch-only wallets but allows parent key compromise from child + chain code * * **Path Format:** * - Standard BIP32 notation: "m/44'/0'/0'/0/0" * - m = master key * - Numbers = derivation indices * - ' (apostrophe) = hardened derivation (adds 2³¹ to index) * - / = path separator * * **Common Derivation Paths:** * - BIP44 Bitcoin: "m/44'/0'/0'/0/0" (account 0, receiving address 0) * - BIP44 Bitcoin Change: "m/44'/0'/0'/1/0" (account 0, change address 0) * - BIP44 Bitcoin Cash: "m/44'/145'/0'/0/0" * - BIP44 Bitcoin SV: "m/44'/236'/0'/0/0" * * @param {string} [path="m/0'"] - BIP32 derivation path (e.g., "m/44'/0'/0'/0/0") * @param {string} [keyType='pri'] - Key type to derive ('pri' for private, 'pub' for public) * @returns {Custodial_Wallet} Returns this wallet instance for method chaining * * @throws {Error} "Public Key can't derive from hardened path" if attempting hardened derivation from public key * @throws {Error} If derivation path format is invalid * @throws {Error} If derived key is invalid (extremely rare: ~1 in 2^127) * * @example * // Standard BIP44 Bitcoin address derivation * const wallet = Custodial_Wallet.fromRandom('main')[1]; * * // Derive first receiving address * wallet.derive("m/44'/0'/0'/0/0", 'pri'); * * // Derive first change address * wallet.derive("m/44'/0'/0'/1/0", 'pri'); * * console.log('Derived keys:', wallet.child_keys.size); // 2 * * @example * // Method chaining for multiple derivations * const wallet = Custodial_Wallet.fromRandom('main')[1]; * * wallet * .derive("m/44'/0'/0'/0/0", 'pri') // First receiving * .derive("m/44'/0'/0'/0/1", 'pri') // Second receiving * .derive("m/44'/0'/0'/1/0", 'pri'); // First change * * // Access all derived addresses * for (const child of wallet.child_keys) { * console.log(`Address ${child.childIndex}:`, child.address); * } * * @example * // Public key derivation (non-hardened only) * const wallet = Custodial_Wallet.fromRandom('main')[1]; * * // This works - non-hardened derivation * wallet.derive("m/0/1/2", 'pub'); * * // This fails - hardened derivation from public key * try { * wallet.derive("m/0'/1", 'pub'); * } catch (error) { * console.log(error.message); // "Public Key can't derive from hardened path" * } * * @example * // Multi-currency wallet derivation * const wallet = Custodial_Wallet.fromRandom('main')[1]; * * // Bitcoin addresses * wallet.derive("m/44'/0'/0'/0/0", 'pri'); // BTC receiving * * // Bitcoin Cash addresses (different coin type) * wallet.derive("m/44'/145'/0'/0/0", 'pri'); // BCH receiving * * // Bitcoin SV addresses * wallet.derive("m/44'/236'/0'/0/0", 'pri'); // BSV receiving * * @example * // Generate multiple addresses for a service * const wallet = Custodial_Wallet.fromRandom('main')[1]; * * // Generate 10 unique receiving addresses * for (let i = 0; i < 10; i++) { * wallet.derive(`m/44'/0'/0'/0/${i}`, 'pri'); * } * * // Each customer gets a unique address * const addresses = Array.from(wallet.child_keys).map(child => child.address); * console.log('Customer addresses:', addresses); */ derive(path = "m/0'", keyType = 'pri') { const key = this.hdKey[keyType === 'pri' ? 'HDpri' : 'HDpub']; const [hdKey, serialization_format] = derive(path, key, this.#serialization_format); this.child_keys.add({ depth: serialization_format.depth, childIndex: serialization_format.childIndex, hdKey, keypair: standardKey(keyType !== 'pub' ? serialization_format.privKey : false, serialization_format.pubKey), address: address(serialization_format.versionByte.pubKey, serialization_format.pubKey.key) }); return this; } /** * Signs a message using ECDSA with the wallet's private key and deterministic nonce generation. * * This method creates a cryptographically secure digital signature using the wallet's * private key. It implements deterministic nonce generation (RFC 6979) to prevent * nonce reuse attacks and ensure signature security. The signature can be verified * by anyone with the corresponding public key. * * **Signature Process:** * 1. Convert message to UTF-8 bytes * 2. Decode WIF private key to raw bytes * 3. Generate deterministic nonce using RFC 6979 * 4. Compute ECDSA signature (r, s) values * 5. Include recovery ID for public key recovery * 6. Return DER-encoded signature with recovery information * * **Security Features:** * - RFC 6979 deterministic nonce generation prevents nonce reuse * - Uses secp256k1 elliptic curve (Bitcoin standard) * - Compatible with Bitcoin transaction signing * - Includes recovery ID for public key derivation * * @param {string} [message=''] - Message to sign (will be converted to UTF-8 bytes) * @returns {ECDSASignatureResult} Tuple containing signature bytes and recovery ID * * @throws {Error} If private key is invalid or signing fails * @throws {Error} If message cannot be converted to bytes * * @example * // Basic message signing * const wallet = Custodial_Wallet.fromRandom('main')[1]; * const message = "Hello Bitcoin!"; * * const [signature, recoveryId] = wallet.sign(message); * console.log('Signature length:', signature.length); // ~71-73 bytes (DER format) * console.log('Recovery ID:', recoveryId); // 0, 1, 2, or 3 * * @example * // Sign and verify workflow * const wallet = Custodial_Wallet.fromRandom('main')[1]; * const message = "Transfer $100 to Alice"; * * // Create signature * const [signature, _] = wallet.sign(message); * * // Verify signature (should return true) * const isValid = wallet.verify(signature, message); * console.log('Signature valid:', isValid); // true * * // Verify with wrong message (should return false) * const isInvalid = wallet.verify(signature, "Transfer $200 to Bob"); * console.log('Wrong message valid:', isInvalid); // false * * @example * // Transaction authorization pattern * const wallet = Custodial_Wallet.fromRandom('main')[1]; * * function authorizeTransaction(txData) { * const txMessage = JSON.stringify({ * to: txData.recipient, * amount: txData.amount, * timestamp: Date.now(), * nonce: Math.random() * }); * * const [signature, recoveryId] = wallet.sign(txMessage); * * return { * transaction: txData, * signature: signature, * recovery: recoveryId, * signer: wallet.address * }; * } * * const authorization = authorizeTransaction({ * recipient: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", * amount: 0.001 * }); * * @example * // Batch signing for multiple messages * const wallet = Custodial_Wallet.fromRandom('main')[1]; * const messages = ["msg1", "msg2", "msg3"]; * * const signatures = messages.map(msg => { * const [sig, recovery] = wallet.sign(msg); * return { message: msg, signature: sig, recovery }; * }); * * console.log(`Signed ${signatures.length} messages`); */ sign(message = '') { return ecdsa.sign(this.keypair.pri, message); } /** * Verifies an ECDSA signature against a message using the wallet's public key. * * This method performs cryptographic verification to ensure that a given signature * was created by the holder of this wallet's private key. It uses standard ECDSA * verification on the secp256k1 curve and can verify signatures created by this * wallet or any other ECDSA-compatible implementation. * * **Verification Process:** * 1. Convert message to UTF-8 bytes (same as signing) * 2. Parse signature into r and s components * 3. Compute verification values using public key * 4. Check that signature equation holds on elliptic curve * 5. Return boolean result of verification * * **Security Properties:** * - Mathematically proves signature was created with corresponding private key * - Cannot be forged without knowledge of private key * - Deterministic result for same signature/message/public key combination * - Compatible with Bitcoin transaction verification * * @param {Uint8Array|Buffer} sig - DER-encoded signature bytes to verify * @param {string} msg - Original message that was signed (must match exactly) * @returns {boolean} True if signature is valid for this wallet's public key, false otherwise * * @throws {Error} If signature format is invalid or corrupted * @throws {Error} If message cannot be converted to bytes * * @example * // Basic signature verification * const wallet = Custodial_Wallet.fromRandom('main')[1]; * const message = "Hello Bitcoin!"; * * // Sign message * const [signature, _] = wallet.sign(message); * * // Verify signature * const isValid = wallet.verify(signature, message); * console.log('Signature valid:', isValid); // true * * // Verify with modified message * const isInvalid = wallet.verify(signature, "Hello Ethereum!"); * console.log('Modified message valid:', isInvalid); // false * * @example * // Cross-wallet verification (different wallets) * const wallet1 = Custodial_Wallet.fromRandom('main')[1]; * const wallet2 = Custodial_Wallet.fromRandom('main')[1]; * const message = "Cross-wallet test"; * * // Wallet1 signs message * const [signature, _] = wallet1.sign(message); * * // Wallet1 can verify its own signature * console.log('Self verification:', wallet1.verify(signature, message)); // true * * // Wallet2 cannot verify wallet1's signature * console.log('Cross verification:', wallet2.verify(signature, message)); // false * * @example * // Transaction verification workflow * function verifyTransactionSignature(txData, signature, senderAddress) { * // Reconstruct the exact message that was signed * const txMessage = JSON.stringify(txData); * * // Find wallet for sender address (in real app, lookup from database) * const senderWallet = findWalletByAddress(senderAddress); * * // Verify signature matches sender's private key * return senderWallet.verify(signature, txMessage); * } * * @example * // Batch verification for audit trail * const wallet = Custodial_Wallet.fromRandom('main')[1]; * const signedMessages = [ * { msg: "tx1", sig: wallet.sign("tx1")[0] }, * { msg: "tx2", sig: wallet.sign("tx2")[0] }, * { msg: "tx3", sig: wallet.sign("tx3")[0] } * ]; * * // Verify all signatures are valid * const allValid = signedMessages.every(item => * wallet.verify(item.sig, item.msg) * ); * * console.log('All signatures valid:', allValid); // true */ verify(sig, msg) { return ecdsa.verify(sig, msg, this.#serialization_format.pubKey.key); } } /** * Non-custodial wallet implementation using Threshold Signature Scheme (TSS) * for distributed key management. Enables multi-party control without a trusted party. * * This class implements advanced threshold cryptography where any subset of participants * meeting the threshold requirement can collaboratively generate valid signatures without * ever reconstructing the private key. It's ideal for scenarios requiring distributed * control, enhanced security, and elimination of single points of failure. * * **Key Features:** * - Distributed key generation using Joint Verifiable Random Secret Sharing (JVRSS) * - Threshold signature generation compatible with standard ECDSA verification * - No trusted dealer required for key setup * - Configurable t-of-n threshold schemes (e.g., 2-of-3, 3-of-5, 5-of-7) * - Secret shares can be distributed across different entities or devices * - Compatible with Bitcoin transaction signing and verification * * **Security Model:** * - Requires exactly t participants to generate signatures * - Information-theoretic security: < t participants learn nothing about private key * - No single point of failure: up to n-t participants can be compromised safely * - Private key never exists in complete form anywhere * - Forward secrecy: compromising future shares doesn't reveal past signatures * * **Use Cases:** * - Corporate treasury management with executive approval * - Cryptocurrency exchanges with operator separation * - Escrow services with dispute resolution * - Multi-signature wallets for shared accounts * - Compliance requirements for multi-party authorization * - High-value asset protection with distributed control * * @class Non_Custodial_Wallet * @extends ThresholdSignature * @since 1.0.0 * * @example * // Create a 2-of-3 escrow wallet * const escrowWallet = Non_Custodial_Wallet.fromRandom("main", 3, 2); * const [buyerShare, sellerShare, arbiterShare] = escrowWallet._shares; * * // Normal release: buyer + seller * const releaseWallet = Non_Custodial_Wallet.fromShares("main", [buyerShare, sellerShare], 2); * const releaseSignature = releaseWallet.sign("Release funds to seller"); * * // Dispute resolution: buyer + arbiter or seller + arbiter * const disputeWallet = Non_Custodial_Wallet.fromShares("main", [buyerShare, arbiterShare], 2); * const disputeSignature = disputeWallet.sign("Refund to buyer after dispute"); * * @example * // Corporate treasury with 3-of-5 executive approval * const corporateWallet = Non_Custodial_Wallet.fromRandom("main", 5, 3); * const executiveShares = corporateWallet._shares; * * // Distribute shares to 5 executives * const executives = [ * { name: "CEO", share: executiveShares[0] }, * { name: "CFO", share: executiveShares[1] }, * { name: "COO", share: executiveShares[2] }, * { name: "CTO", share: executiveShares[3] }, * { name: "Board Rep", share: executiveShares[4] } * ]; * * // Any 3 executives can authorize payments * const paymentAuth = Non_Custodial_Wallet.fromShares("main", * [executives[0].share, executives[1].share, executives[2].share], 3); * * const authSignature = paymentAuth.sign("Q4 dividend payment: $1M"); * * @example * // Cryptocurrency exchange cold storage * const exchangeWallet = Non_Custodial_Wallet.fromRandom("main", 7, 4); * const operatorShares = exchangeWallet._shares; * * // Distribute shares across geographic locations and roles * const distribution = [ * { location: "US-East", role: "Security Officer", share: operatorShares[0] }, * { location: "US-West", role: "Operations Lead", share: operatorShares[1] }, * { location: "EU", role: "Compliance Officer", share: operatorShares[2] }, * { location: "Asia", role: "Technical Lead", share: operatorShares[3] }, * { location: "Backup-1", role: "Emergency Access", share: operatorShares[4] }, * { location: "Backup-2", role: "Emergency Access", share: operatorShares[5] }, * { location: "Audit", role: "External Auditor", share: operatorShares[6] } * ]; * * // Requires 4 of 7 operators to authorize large withdrawals * // Provides redundancy and prevents single operator compromise */ class Non_Custodial_Wallet extends ThresholdSignature { /** * Creates a new Non_Custodial_Wallet instance with specified threshold parameters. * * This constructor initializes a threshold signature scheme with the given group size * and threshold requirements. It automatically generates the distributed key shares * using JVRSS (Joint Verifiable Random Secret Sharing) and computes the corresponding * Bitcoin address for receiving payments. * * **Initialization Process:** * 1. Validate threshold parameters (t ≤ n, t ≥ 2) * 2. Execute JVRSS protocol for distributed key generation * 3. Generate secret shares for each participant * 4. Compute aggregate public key from polynomial constants * 5. Derive Bitcoin address from public key * * **Parameter Constraints:** * - group_size ≥ 2 (minimum meaningful distribution) * - threshold ≥ 2 (minimum security requirement) * - threshold ≤ group_size (cannot exceed total participants) * - Recommended: threshold ≤ (group_size + 1) / 2 for practical usability * * @param {string} net - Network type ('main' for mainnet, 'test' for testnet) * @param {number} group_size - Total number of participants in the threshold scheme * @param {number} threshold - Minimum number of participants required for operations * * @throws {Error} "Threshold is too high or low" if parameter constraints are violated * @throws {Error} If network type is not 'main' or 'test' * * @example * // Create a 2-of-3 threshold wallet * const wallet = new Non_Custodial_Wallet('main', 3, 2); * console.log('Group size:', wallet.group_size); // 3 * console.log('Threshold:', wallet.threshold); // 2 * console.log('Address:', wallet.address); // Bitcoin address * console.log('Shares:', wallet._shares.length); // 3 hex-encoded shares * * @example * // Corporate wallet with higher security * const corporateWallet = new Non_Custodial_Wallet('main', 7, 4); * // Requires 4 of 7 executives to authorize transactions * * @example * // Error handling for invalid parameters * try { * const invalidWallet = new Non_Custodial_Wallet('main', 3, 5); // threshold > group_size * } catch (error) { * console.error('Invalid parameters:', error.message); * } */ constructor(net, group_size, threshold) { super(group_size, threshold); /** * Network type for this threshold wallet instance. * Determines address formats, version bytes, and network-specific parameters. * * @type {string} * @readonly * @memberof Non_Custodial_Wallet * @example * console.log(wallet.net); // "main" or "test" */ this.net = net; // Generate wallet address and public key from threshold scheme [this.publicKey, this.address] = this.#wallet(); } /** * Generates a new random threshold wallet with specified parameters. * * This static factory method creates a fresh threshold signature scheme using * cryptographically secure randomness. It initializes the distributed key generation * protocol and produces a complete threshold wallet ready for multi-party operations. * * **Generation Process:** * 1. Create new threshold signature instance with specified parameters * 2. Execute JVRSS for distributed key generation * 3. Generate secret shares for all participants * 4. Compute aggregate public key and Bitcoin address * 5. Return initialized wallet instance * * **Security Properties:** * - Uses cryptographically secure random number generation * - No participant has knowledge of the complete private key * - Secret shares are information-theoretically secure * - Aggregate public key is verifiable and deterministic * * @static * @param {string} [net="main"] - Network type ('main' for mainnet, 'test' for testnet) * @param {number} [group_size=3] - Total number of participants in the scheme * @param {number} [threshold=2] - Minimum participants needed for signature generation * @returns {Non_Custodial_Wallet} New threshold wallet instance * * @throws {Error} "Threshold is too high or low" if constraints are violated * @throws {Error} If network parameter is invalid * * @example * // Standard 2-of-3 multi-signature wallet * const multiSigWallet = Non_Custodial_Wallet.fromRandom("main", 3, 2); * console.log('Multi-sig address:', multiSigWallet.address); * * // Get shares for distribution * const [share1, share2, share3] = multiSigWallet._shares; * console.log('Share 1:', share1); // Hex-encoded secret share * * @example * // Corporate treasury wallet (3-of-5) * const treasuryWallet = Non_Custodial_Wallet.fromRandom("main", 5, 3); * const executiveShares = treasuryWallet._shares; * * // Distribute shares to executives * const shareDistribution = [ * { executive: "CEO", share: executiveShares[0] }, * { executive: "CFO", share: executiveShares[1] }, * { executive: "COO", share: executiveShares[2] }, * { executive: "CTO", share: executiveShares[3] }, * { executive: "Board Rep", share: executiveShares[4] } * ]; * * @example * // High-security vault (5-of-9) * const vaultWallet = Non_Custodial_Wallet.fromRandom("main", 9, 5); * console.log(`Vault requires ${vaultWallet.threshold} of ${vaultWallet.group_size} participants`); * * // Example distribution across different security zones * const vaultShares = vaultWallet._shares; * const securityZones = [ * { zone: "Primary Datacenter", shares: vaultShares.slice(0, 3) }, * { zone: "Secondary Datacenter", shares: vaultShares.slice(3, 6) }, * { zone: "Offline Storage", shares: vaultShares.slice(6, 9) } * ]; */ static fromRandom(net = "main", group_size = 3, threshold = 2) { return new this( net, group_size, threshold ) } /** * Reconstructs a threshold wallet from existing secret shares. * * This static factory method rebuilds a threshold wallet from previously distributed * secret shares. It's used when participants want to reconstruct the wallet for * signature generation or when migrating shares between systems. The method validates * share consistency and reconstructs the public key and address. * * **Reconstruction Process:** * 1. Create new threshold instance with matching parameters * 2. Convert hex-encoded shares to BigNumber format * 3. Reconstruct the aggregate public key from shares * 4. Derive Bitcoin address from reconstructed public key * 5. Validate share consistency and threshold requirements * * **Security Considerations:** * - Only provided shares are used; missing shares remain unknown * - Threshold requirement still applies for signature generation * - Share authenticity should be verified through secure channels * - Reconstructed wallet has same capabilities as original * * @static * @param {string} [net="main"] - Network type ('main' for mainnet, 'test' for testnet) * @param {string[]} shares - Array of hex-encoded secret shares * @param {number} [threshold=2] - Minimum participants required for operations * @returns {Non_Custodial_Wallet} Reconstructed threshold wallet instance * * @throws {Error} If threshold is greater than number of provided shares * @throws {Error} If reconstructed public key is invalid * * @example * // Reconstruct 2-of-3 wallet from shares * const originalShares = [ * "79479395a59a8e9d930f2b10ccd5ac3671b0ff0bf8a66aaa1d74978c5353694b", * "98510126c920e18b148130ac1145686cb299d21f0e010b98ede44169a7bb1c13", * "b7428d37e5847f9a8b3d4c2f9a1e5c8d7b4f2a8e9c1d5b7a3f8e2c9d4b6a1f5" * ]; * * const reconstructedWallet = Non_Custodial_Wallet.fromShares("main", originalShares, 2); * console.log('Reconstructed address:', reconstructedWallet.address); * * @example * // Partial reconstruction for signing (only threshold shares needed) * const originalWallet = Non_Custodial_Wallet.fromRandom("main", 5, 3); * const allShares = originalWallet._shares; * * // Use only 3 shares (minimum threshold) * const signingShares = [allShares[0], allShares[2], allShares[4]]; * const signingWallet = Non_Custodial_Wallet.fromShares("main", signingShares, 3); * * // Can generate signatures with just threshold shares * const signature = signingWallet.sign("Authorized payment"); * * @example * // Corporate recovery scenario * function recoverCorporateWallet(executiveShares) { * if (executiveShares.length < 3) { * throw new Error("Insufficient executives present for recovery"); * } * * // Reconstruct wallet from available executive shares * const recoveredWallet = Non_Custodial_Wallet.fromShares( * "main", * executiveShares.slice(0, 3), // Use first 3 available shares * 3 * ); * * return recoveredWallet; * } * * @example * // Cross-platform wallet migration * // Export shares from mobile app * const mobileShares = mobileWallet._shares; * * // Import to desktop application * const desktopWallet = Non_Custodial_Wallet.fromShares("main", mobileShares, 2); * * // Desktop wallet has identical functionality * console.log('Same address:', mobileWallet.address === desktopWallet.address); // true */ static fromShares(net = "main", shares, threshold = 2) { const wallet = new this( net, shares.length, threshold ) // Convert hex shares to BigNumber format and reconstruct public key wallet.shares = shares.map(x => new BN(x, 'hex')); wallet.public_key = secp256k1.ProjectivePoint.fromPrivateKey(wallet.privite_key().toBuffer()); [wallet.publicKey, wallet.address] = wallet.#wallet(); return wallet; } /** * Gets the secret shares as hex-encoded strings for secure distribution to participants. * * This getter provides access to the distributed secret shares in a format suitable * for secure transmission and storage. Each share is a hex-encoded string representing * a point on the secret-sharing polynomial. These shares should be distributed to * different participants and stored securely. * * **Share Properties:** * - Each share is cryptographically independent * - Shares are information-theoretically secure (< threshold reveals nothing) * - Hex encoding ensures safe transmission over text-based channels * - Each share is typically 64 hex characters (32 bytes) * - Shares should be transmitted over secure, authenticated channels * * **Distribution Best Practices:** * - Use secure communication channels (encrypted email, secure messaging) * - Verify recipient identity before share distribution * - Consider using QR codes for offline share transfer * - Implement share backup and recovery procedures * - Document which participant holds which share index * * @returns {string[]} Array of hex-encoded secret shares for distribution * * @example * // Basic share distribution * const wallet = Non_Custodial_Wallet.fromRandom("main", 3, 2); * const shares = wallet._shares; * * console.log('Number of shares:', shares.length); // 3 * console.log('Share format:', shares[0]); // "79479395a59a8e9d..." * * @example * // Secure share distribution to participants * const corporateWallet = Non_Custodial_Wallet.fromRandom("main", 5, 3); * const executiveShares = corporateWallet._shares; * * const executives = [ * { name: "Alice Johnson", email: "alice@company.com", share: executiveShares[0] }, * { name: "Bob Smith", email: "bob@company.com", share: executiveShares[1] }, * { name: "Carol Davis", email: "carol@company.com", share: executiveShares[2] }, * { name: "Dave Wilson", email: "dave@company.com", share: executiveShares[3] }, * { name: "Eve Brown", email: "eve@company.com", share: executiveShares[4] } * ]; * * // Distribute shares securely * executives.forEach(exec => { * sendSecureEmail(exec.email, `Your wallet share: ${exec.share}`); * console.log(`Share distributed to ${exec.name}`); * }); * * @example * // QR code generation for offline distribution * const wallet = Non_Custodial_Wallet.fromRandom("main", 3, 2); * const shares = wallet._shares; * * shares.forEach((share, index) => { * const qrCode = generateQRCode(share); * saveQRCode(qrCode, `share_${index + 1}.png`); * console.log(`QR code generated for share ${index + 1}`); * }); * * @example * // Backup and recovery documentation * const wallet = Non_Custodial_Wallet.fromRandom("main", 5, 3); * const shares = wallet._shares; * * const backupDocument = { * walletAddress: wallet.address, * threshold: wallet.threshold, * totalShares: wallet.group_size, * creationDate: new Date().toISOString(), * shares: shares.map((share, index) => ({ * index: index + 1, * share: share, * holde