const listener = (message: MessageEvent<any>) => {
this.#report(message)
}
+ const terminator = () => {
+ this.terminate()
+ }
BROWSER: {
this.#url = URL.createObjectURL(new Blob([vaultWorker], { type: 'text/javascript' }))
this.#worker = new Worker(this.#url, { type: 'module' })
this.#worker.addEventListener('message', listener)
- this.#worker.addEventListener('error', this.terminate)
- this.#worker.addEventListener('messageerror', this.terminate)
+ this.#worker.addEventListener('error', terminator)
+ this.#worker.addEventListener('messageerror', terminator)
}
NODE: {
this.#worker = new NodeWorker(vaultWorker, {
})
this.#url = dec.toString(this.#worker.threadId)
this.#worker.on('message', listener)
- this.#worker.on('error', this.terminate)
- this.#worker.on('messageerror', this.terminate)
+ this.#worker.on('error', terminator)
+ this.#worker.on('messageerror', terminator)
}
}
? data.index
: undefined
+ // Number of public keys to derive
+ if (action === 'derive' && (typeof data.count !== 'number' || data.count < 1)) {
+ throw new TypeError('Count is required to derive a batch of keys')
+ }
+ const count = typeof data.count === 'number'
+ ? data.count
+ : undefined
+
// Data to sign
if ('message' in data) {
if (action === 'sign') {
? data.timeout
: undefined
- return { seed, mnemonicPhrase, mnemonicSalt, encrypted, index, message, timeout }
+ return { seed, mnemonicPhrase, mnemonicSalt, encrypted, index, count, message, timeout }
} catch (err) {
new Uint8Array(seed ?? []).fill(0)
new Uint8Array(encrypted ?? []).fill(0)
const id = parseId(action, data)
const iv = parseIv(action, data)
const parsed = parseData(action, data)
- const { seed, mnemonicPhrase, mnemonicSalt, index, encrypted, message, timeout } = parsed
+ const { seed, mnemonicPhrase, mnemonicSalt, index, count, encrypted, message, timeout } = parsed
passkey(action, keySalt, data)
.then((key: CryptoKey | undefined): Promise<Record<string, boolean | number | ArrayBuffer> | void> => {
switch (action) {
return create(type, id, key, keySalt, mnemonicSalt)
}
case 'derive': {
- return derive(index)
+ return derive(index, count)
}
case 'load': {
return load(type, id, key, keySalt, mnemonicPhrase ?? seed, mnemonicSalt)
* wallet seed at a specified index and then returns the public key. The wallet
* must be unlocked prior to derivation.
*/
-function derive (index?: number): Promise<Record<string, number | ArrayBuffer>> {
+function derive (index?: number, count?: number): Promise<Record<string, number | ArrayBuffer>> {
try {
_timer.pause()
if (_locked) {
if (_type !== 'BIP-44' && _type !== 'BLAKE2b' && _type !== 'Exodus') {
throw new Error('Invalid wallet type')
}
- if (typeof index !== 'number') {
+ if (typeof index !== 'number' || index < 0) {
throw new Error('Invalid wallet account index')
}
- return _ckd(index).then(result => {
- const prv = new Uint8Array(result)
- const pub = nano25519_derive(prv)
- prv.fill(0)
- _timer = new VaultTimer(_autolock, _timeout)
- return { index, publicKey: pub.buffer }
- })
+ if (typeof count !== 'number' || count < 1) {
+ throw new Error('Invalid wallet account batch size')
+ }
+ const max = index + count
+ if (max > (_type === 'BLAKE2b' ? 0xffffffff : 0x7fffffff)) {
+ throw new Error('Wallet account range exceeded ')
+ }
+ const promises = []
+ for (let i = index; i < max; i++) {
+ promises.push(_ckd(index).then(result => {
+ const prv = new Uint8Array(result)
+ const pub = nano25519_derive(prv)
+ prv.fill(0)
+ return { index, publicKey: pub.buffer }
+ }))
+ }
+ return Promise.all(promises)
+ .then(results => {
+ const data: Record<string, ArrayBuffer> = {}
+ for (const result of results) {
+ data[result.index] = result.publicKey
+ }
+ _timer = new VaultTimer(_autolock, _timeout)
+ return data
+ })
+ .catch(err => {
+ console.error(err)
+ _timer.resume()
+ throw new Error('Failed to derive account', { cause: err })
+ })
} catch (err) {
console.error(err)
_timer.resume()
throw new TypeError('Invalid account range', { cause: `${from}-${to}` })
}
if (to - from > 100) {
- console.warn(`libnemo performance may degrade when deriving many accounts at once`)
+ console.warn('libnemo performance may degrade when deriving many accounts at once')
}
const output = new Map<number, Account>()
const indexes: number[] = []
accounts.set(index, account)
}
} else {
- const promises = []
- for (const index of indexes) {
- promises.push(vault.request<number | ArrayBuffer>({
- action: 'derive',
- index
- }))
- }
- const publicKeys = await Promise.all(promises)
- for (const { index, publicKey } of publicKeys) {
- if (typeof index === 'number' && publicKey instanceof ArrayBuffer) {
+ // const promises = []
+ // for (const index of indexes) {
+ // promises.push(vault.request<number | ArrayBuffer>({
+ // action: 'derive',
+ // index
+ // }))
+ // }
+ // const publicKeys = await Promise.all(promises)
+ const min = Math.min(...indexes)
+ const count = Math.max(...indexes) - min + 1
+ const publicKeys = await vault.request<ArrayBuffer>({
+ action: 'derive',
+ index: min,
+ count
+ })
+ console.log(publicKeys)
+ for (const [index, publicKey] of Object.entries(publicKeys)) {
+ if (typeof Number(index) === 'number' && publicKey instanceof ArrayBuffer) {
const account = new Account(publicKey)
- output.set(index, account)
- accounts.set(index, account)
+ output.set(Number(index), account)
+ accounts.set(Number(index), account)
}
}
}
* Retrieves accounts from a wallet using its child key derivation function.\r
* Defaults to the first account at index 0.\r
*\r
+ * Derives a maximum of 1000 accounts per call and will throw an error if\r
+ * exceeded. This is not a hard limit since the method can be called multiple\r
+ * times, but it is a safety measure to let developers know when they may be\r
+ * creating a poor user experience.\r
+ *\r
* The returned object will have keys corresponding with the requested range\r
* of account indexes. The value of each key will be the Account derived for\r
* that index in the wallet.\r
await Promise.all([\r
suite('Derive accounts from BIP-44 wallet', async () => {\r
\r
+ await test('derive the first 10000 accounts from the given BIP-44 seed', async () => {\r
+ const wallet = await Wallet.load('BIP-44', TEST_PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
+ await wallet.unlock(TEST_PASSWORD)\r
+\r
+ let start, end\r
+ start = performance.now()\r
+ await wallet.accounts(0, 10000)\r
+ end = performance.now()\r
+ console.log('duration', end - start)\r
+\r
+ await assert.resolves(wallet.destroy())\r
+ })\r
+\r
await test('derive the first account from the given BIP-44 seed', async () => {\r
const wallet = await Wallet.load('BIP-44', TEST_PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
await wallet.unlock(TEST_PASSWORD)\r