p-sdk-wallet
Version:
A comprehensive wallet SDK for React Native (pwc), supporting multi-chain and multi-account features.
157 lines (156 loc) • 6.28 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.HDKeyring = void 0;
const EncryptionService_1 = require("../crypto/EncryptionService");
const chains_1 = require("../config/chains");
const ChainService_1 = require("../chain/ChainService");
/**
* HD (Hierarchical Deterministic) keyring for managing multiple accounts
* derived from a single mnemonic phrase using BIP-44 derivation paths.
* Supports EVM-compatible chains.
*/
class HDKeyring {
/**
* Creates a new HDKeyring instance from a mnemonic phrase.
* @param mnemonic - The mnemonic phrase (12, 15, 18, 21, or 24 words)
* @throws Error if the mnemonic is invalid
*/
constructor(mnemonic) {
this.type = 'HD';
this.seed = null;
// For now, we manage one account, but this can be extended.
this.accounts = [];
if (!EncryptionService_1.EncryptionService.validateMnemonic(mnemonic)) {
throw new Error('Invalid mnemonic');
}
this.mnemonic = mnemonic;
}
/**
* Initializes the keyring by generating the seed and creating the first account.
* This method must be called before using the keyring.
* @returns Promise that resolves when initialization is complete
* @throws Error if seed generation or account derivation fails
*/
async initialize() {
// Check cache first
if (HDKeyring.seedCache.has(this.mnemonic)) {
this.seed = HDKeyring.seedCache.get(this.mnemonic);
}
else {
this.seed = await EncryptionService_1.EncryptionService.mnemonicToSeed(this.mnemonic);
// Cache the seed
HDKeyring.seedCache.set(this.mnemonic, this.seed);
}
const firstAccount = await this.deriveAccount(0);
this.accounts.push(firstAccount.address);
}
/**
* Derives an account at a specific index using BIP-44 derivation.
* @param index - The account index to derive (0-based)
* @returns Promise resolving to an object containing the derived address and private key
* @throws Error if the keyring is not initialized or derivation fails
*/
async deriveAccount(index) {
if (!this.seed) {
throw new Error('Keyring not initialized. Call initialize() first.');
}
// This can be extended to support custom paths per index.
const path = `${chains_1.DERIVATION_PATHS.EVM.slice(0, -1)}${index}`;
const privateKeyBuffer = await EncryptionService_1.EncryptionService.derivePrivateKey(this.seed, path);
const privateKeyHex = privateKeyBuffer.toString('hex');
const address = ChainService_1.ChainService.getAddress(privateKeyHex);
return { address, privateKey: privateKeyHex };
}
/**
* Adds a new account derived from the mnemonic at the next available index.
* @returns Promise resolving to the address of the newly created account
* @throws Error if account derivation fails or duplicate address is generated
*/
async addNewAccount() {
const newIndex = this.accounts.length;
const newAccount = await this.deriveAccount(newIndex);
if (this.accounts.includes(newAccount.address)) {
throw new Error('Duplicate account derived. Please check derivation path logic.');
}
this.accounts.push(newAccount.address);
return newAccount.address;
}
/**
* Gets the private key for a given address managed by this keyring.
* @param address - The account address to get the private key for
* @returns Promise resolving to the private key as a hex string
* @throws Error if the address is not found in this keyring
*/
async getPrivateKeyForAddress(address) {
const index = this.accounts.indexOf(address);
if (index === -1) {
throw new Error('Address not found in this keyring.');
}
const account = await this.deriveAccount(index);
return account.privateKey;
}
/**
* Returns the mnemonic phrase. USE WITH CAUTION - this exposes sensitive data.
* @returns The mnemonic phrase as a string
*/
getMnemonic() {
return this.mnemonic;
}
/**
* Serializes the keyring data for encryption and storage.
* @returns An object containing the keyring type, mnemonic, and account addresses
*/
serialize() {
return {
type: this.type,
mnemonic: this.mnemonic,
accounts: this.accounts
};
}
/**
* Deserializes data into an HDKeyring instance.
* @param data - The serialized keyring data
* @returns Promise resolving to a new HDKeyring instance
* @throws Error if the data is invalid or missing required fields
*/
static async deserialize(data) {
if (data.type !== 'HD' || !data.mnemonic) {
throw new Error('Invalid data for HDKeyring deserialization.');
}
const keyring = new HDKeyring(data.mnemonic);
// If accounts were persisted, restore them. Otherwise, initialize fresh.
if (data.accounts && data.accounts.length > 0) {
keyring.accounts = data.accounts;
// Cache the seed to avoid regenerating it on every load
if (HDKeyring.seedCache.has(keyring.mnemonic)) {
keyring.seed = HDKeyring.seedCache.get(keyring.mnemonic);
}
else {
keyring.seed = await EncryptionService_1.EncryptionService.mnemonicToSeed(keyring.mnemonic);
// Cache the seed
HDKeyring.seedCache.set(keyring.mnemonic, keyring.seed);
}
}
else {
await keyring.initialize();
}
return keyring;
}
/**
* Clears the seed cache to free up memory.
* Call this when you want to clear sensitive data from memory.
*/
static clearSeedCache() {
HDKeyring.seedCache.clear();
}
/**
* Gets the cache size for debugging purposes.
* @returns Number of cached seeds
*/
static getSeedCacheSize() {
return HDKeyring.seedCache.size;
}
}
exports.HDKeyring = HDKeyring;
// Cache để tránh tạo lại seed
HDKeyring.seedCache = new Map();