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
JavaScript
/**
* @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