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