diff --git a/src/account/MnemonicFactory.ts b/src/account/MnemonicFactory.ts index 9ea065192d..e3cecefd74 100644 --- a/src/account/MnemonicFactory.ts +++ b/src/account/MnemonicFactory.ts @@ -1,10 +1,9 @@ -import { mnemonicToSeed } from '@scure/bip39'; +import { mnemonicToSeed, mnemonicToSeedSync } from '@scure/bip39'; import tweetnaclAuth from 'tweetnacl-auth'; import AccountBaseFactory from './BaseFactory.js'; import AccountMemory from './Memory.js'; import { encode, Encoding, Encoded, decode } from '../utils/encoder.js'; import { concatBuffers } from '../utils/other.js'; -import { UnexpectedTsError } from '../utils/errors.js'; export const ED25519_CURVE = Buffer.from('ed25519 seed'); const HARDENED_OFFSET = 0x80000000; @@ -42,17 +41,33 @@ interface Wallet { * A factory class that generates instances of AccountMemory based on provided mnemonic phrase. */ export default class AccountMnemonicFactory extends AccountBaseFactory { - readonly #mnemonic: string | undefined; - - #wallet: Wallet | undefined; + #mnemonicOrWallet: string | Wallet; /** * @param mnemonicOrWallet - BIP39-compatible mnemonic phrase or a wallet derived from mnemonic */ constructor(mnemonicOrWallet: string | Wallet) { super(); - if (typeof mnemonicOrWallet === 'string') this.#mnemonic = mnemonicOrWallet; - else this.#wallet = mnemonicOrWallet; + this.#mnemonicOrWallet = mnemonicOrWallet; + } + + #getWallet(sync: true): Wallet; + #getWallet(sync: false): Wallet | Promise; + #getWallet(sync: boolean): Wallet | Promise { + const setWalletBySeed = (seed: Uint8Array): Wallet => { + const masterKey = deriveKey(seed, ED25519_CURVE); + const walletKey = derivePathFromKey(masterKey, [44, 457]); + this.#mnemonicOrWallet = { + secretKey: encode(walletKey.secretKey, Encoding.Bytearray), + chainCode: encode(walletKey.chainCode, Encoding.Bytearray), + }; + return this.#mnemonicOrWallet; + }; + + if (typeof this.#mnemonicOrWallet === 'object') return this.#mnemonicOrWallet; + return sync + ? setWalletBySeed(mnemonicToSeedSync(this.#mnemonicOrWallet)) + : mnemonicToSeed(this.#mnemonicOrWallet).then(setWalletBySeed); } /** @@ -60,29 +75,23 @@ export default class AccountMnemonicFactory extends AccountBaseFactory { * In comparison with mnemonic, the wallet can be used to derive aeternity accounts only. */ async getWallet(): Promise { - if (this.#wallet != null) return this.#wallet; - if (this.#mnemonic == null) - throw new UnexpectedTsError( - 'AccountMnemonicFactory should be initialized with mnemonic or wallet', - ); - const seed = await mnemonicToSeed(this.#mnemonic); - const masterKey = deriveKey(seed, ED25519_CURVE); - const walletKey = derivePathFromKey(masterKey, [44, 457]); - this.#wallet = { - secretKey: encode(walletKey.secretKey, Encoding.Bytearray), - chainCode: encode(walletKey.chainCode, Encoding.Bytearray), - }; - return this.#wallet; + return this.#getWallet(false); } - async #getAccountSecretKey(accountIndex: number): Promise { - const wallet = await this.getWallet(); + /** + * The same as `getWallet` but synchronous. + */ + getWalletSync(): Wallet { + return this.#getWallet(true); + } + + #getAccountByWallet(accountIndex: number, wallet: Wallet): AccountMemory { const walletKey = { secretKey: decode(wallet.secretKey), chainCode: decode(wallet.chainCode), }; const raw = derivePathFromKey(walletKey, [accountIndex, 0, 0]).secretKey; - return encode(raw, Encoding.AccountSecretKey); + return new AccountMemory(encode(raw, Encoding.AccountSecretKey)); } /** @@ -90,6 +99,15 @@ export default class AccountMnemonicFactory extends AccountBaseFactory { * @param accountIndex - Index of account */ async initialize(accountIndex: number): Promise { - return new AccountMemory(await this.#getAccountSecretKey(accountIndex)); + const wallet = await this.getWallet(); + return this.#getAccountByWallet(accountIndex, wallet); + } + + /** + * The same as `initialize` but synchronous. + */ + initializeSync(accountIndex: number): AccountMemory { + const wallet = this.getWalletSync(); + return this.#getAccountByWallet(accountIndex, wallet); } } diff --git a/test/unit/mnemonic.ts b/test/unit/mnemonic.ts index 5e962089ec..d4945373e6 100644 --- a/test/unit/mnemonic.ts +++ b/test/unit/mnemonic.ts @@ -10,12 +10,22 @@ const wallet = { } as const; describe('Account mnemonic factory', () => { - it('derives wallet', async () => { + it('derives wallet by mnemonic', async () => { const factory = new AccountMnemonicFactory(mnemonic); expect(await factory.getWallet()).to.be.eql(wallet); }); - it('initializes an account', async () => { + it('derives wallet by wallet', async () => { + const factory = new AccountMnemonicFactory(wallet); + expect(await factory.getWallet()).to.be.eql(wallet); + }); + + it('derives wallet in sync', async () => { + const factory = new AccountMnemonicFactory(mnemonic); + expect(factory.getWalletSync()).to.be.eql(wallet); + }); + + it('initializes an account by mnemonic', async () => { const factory = new AccountMnemonicFactory(mnemonic); const account = await factory.initialize(42); expect(account).to.be.instanceOf(MemoryAccount); @@ -25,6 +35,14 @@ describe('Account mnemonic factory', () => { it('initializes an account by wallet', async () => { const factory = new AccountMnemonicFactory(wallet); const account = await factory.initialize(42); + expect(account).to.be.instanceOf(MemoryAccount); + expect(account.address).to.be.equal('ak_2HteeujaJzutKeFZiAmYTzcagSoRErSXpBFV179xYgqT4teakv'); + }); + + it('initializes an account in sync', async () => { + const factory = new AccountMnemonicFactory(mnemonic); + const account = factory.initializeSync(42); + expect(account).to.be.instanceOf(MemoryAccount); expect(account.address).to.be.equal('ak_2HteeujaJzutKeFZiAmYTzcagSoRErSXpBFV179xYgqT4teakv'); });