static {
NODE: this.#parentPort = parentPort
- const listener = async (message: MessageEvent<any>): Promise<void> => {
- try {
+ const listener = (message: MessageEvent<any>): Promise<void> => {
+ return this.#extractData(message.data).then(extracted => {
const {
action,
type,
index,
encrypted,
data
- } = await this.#extractData(message.data)
- let result: NamedData
+ } = extracted
+ let result: Promise<NamedData>
switch (action) {
case 'STOP': {
BROWSER: close()
NODE: process.exit()
}
case 'create': {
- result = await this.create(type, key, keySalt, mnemonicSalt)
+ result = this.create(type, key, keySalt, mnemonicSalt)
break
}
case 'derive': {
- result = await this.derive(index)
+ result = this.derive(index)
break
}
case 'load': {
- result = await this.load(type, key, keySalt, mnemonicPhrase ?? seed, mnemonicSalt)
+ result = this.load(type, key, keySalt, mnemonicPhrase ?? seed, mnemonicSalt)
break
}
case 'lock': {
- result = this.lock()
+ result = Promise.resolve(this.lock())
break
}
case 'sign': {
- result = await this.sign(index, data)
+ result = this.sign(index, data)
break
}
case 'unlock': {
- result = await this.unlock(type, key, iv, encrypted)
+ result = this.unlock(type, key, iv, encrypted)
break
}
case 'update': {
- result = await this.update(key, keySalt)
+ result = this.update(key, keySalt)
break
}
case 'verify': {
- result = this.verify(seed, mnemonicPhrase)
+ result = Promise.resolve(this.verify(seed, mnemonicPhrase))
break
}
default: {
throw new Error(`Unknown wallet action '${action}'`)
}
}
- const transfer = []
- for (const k of Object.keys(result)) {
- if (result[k] instanceof ArrayBuffer || result[k] instanceof CryptoKey) {
- transfer.push(result[k])
+ return result.then(result => {
+ const transfer = []
+ for (const k of Object.keys(result)) {
+ if (result[k] instanceof ArrayBuffer || result[k] instanceof CryptoKey) {
+ transfer.push(result[k])
+ }
}
- }
- //@ts-expect-error
- BROWSER: postMessage(result, transfer)
- //@ts-expect-error
- NODE: parentPort?.postMessage(result, transfer)
- } catch (err) {
- console.error(err)
- for (const key of Object.keys(message.data)) {
- if (message.data[key] instanceof ArrayBuffer && !message.data[key].detached) {
- new Uint8Array(message.data[key]).fill(0).buffer.transfer?.()
+ //@ts-expect-error
+ BROWSER: postMessage(result, transfer)
+ //@ts-expect-error
+ NODE: parentPort?.postMessage(result, transfer)
+ })
+ })
+ .catch((err: any) => {
+ console.error(err)
+ for (const key of Object.keys(message.data)) {
+ if (message.data[key] instanceof ArrayBuffer && !message.data[key].detached) {
+ new Uint8Array(message.data[key]).fill(0).buffer.transfer?.()
+ }
+ message.data[key] = undefined
}
- message.data[key] = undefined
- }
- BROWSER: postMessage({ error: 'Failed to process Vault request', cause: err })
- NODE: parentPort?.postMessage({ error: 'Failed to process Vault request', cause: err })
- }
+ BROWSER: postMessage({ error: 'Failed to process Vault request', cause: err })
+ NODE: parentPort?.postMessage({ error: 'Failed to process Vault request', cause: err })
+ })
}
BROWSER: addEventListener('message', listener)
NODE: this.#parentPort?.on('message', listener)
* Generates a new mnemonic and seed and then returns the initialization vector
* vector, salt, and encrypted data representing the wallet in a locked state.
*/
- static async create (type?: 'BIP-44' | 'BLAKE2b', key?: CryptoKey, keySalt?: ArrayBuffer, mnemonicSalt?: string): Promise<NamedData<ArrayBuffer>> {
+ static create (type?: 'BIP-44' | 'BLAKE2b', key?: CryptoKey, keySalt?: ArrayBuffer, mnemonicSalt?: string): Promise<NamedData<ArrayBuffer>> {
try {
const entropy = crypto.getRandomValues(new Uint8Array(32))
- const { phrase: mnemonicPhrase } = await Bip39.fromEntropy(entropy)
- const record = await this.#load(type, key, keySalt, mnemonicPhrase, mnemonicSalt)
- if (this.#seed == null || this.#mnemonic == null) {
- throw new Error('Failed to generate seed and mnemonic')
- }
- return { ...record, seed: this.#seed.slice(), mnemonic: this.#mnemonic.slice() }
+ return Bip39.fromEntropy(entropy)
+ .then(bip39 => {
+ return this.#load(type, key, keySalt, bip39.phrase, mnemonicSalt)
+ .then(record => {
+ if (this.#seed == null || this.#mnemonic == null) {
+ throw new Error('Failed to generate seed and mnemonic')
+ }
+ return { ...record, seed: this.#seed.slice(), mnemonic: this.#mnemonic.slice() }
+ })
+ })
} catch (err) {
console.error(err)
throw new Error('Failed to create wallet', { cause: err })
* wallet seed at a specified index and then returns the public key. The wallet
* must be unlocked prior to derivation.
*/
- static async derive (index?: number): Promise<NamedData<number | ArrayBuffer>> {
+ static derive (index?: number): Promise<NamedData<number | ArrayBuffer>> {
try {
+ this.#timeout.pause()
if (this.#locked) {
throw new Error('Wallet is locked')
}
if (typeof index !== 'number') {
throw new Error('Invalid wallet account index')
}
- this.#timeout.pause()
- const prv = this.#type === 'BIP-44'
- ? await Bip44.ckd(this.#seed, BIP44_COIN_NANO, index)
- : await this.#deriveBlake2bPrivateKey(this.#seed, index)
- const pub = await NanoNaCl.convert(new Uint8Array(prv))
- this.#timeout = new VaultTimer(() => this.lock(), 120000)
- return { index, publicKey: pub.buffer }
+ const derive = this.#type === 'BIP-44'
+ ? Bip44.ckd(this.#seed, BIP44_COIN_NANO, index)
+ : Promise.resolve(this.#deriveBlake2bPrivateKey(this.#seed, index))
+ return derive.then(prv => {
+ const pub = NanoNaCl.convert(new Uint8Array(prv))
+ this.#timeout = new VaultTimer(() => this.lock(), 120000)
+ return { index, publicKey: pub.buffer }
+ })
} catch (err) {
console.error(err)
this.#timeout.resume()
* Encrypts an existing seed or mnemonic+salt and returns the initialization
* vector, salt, and encrypted data representing the wallet in a locked state.
*/
- static async load (type?: 'BIP-44' | 'BLAKE2b', key?: CryptoKey, keySalt?: ArrayBuffer, secret?: string | ArrayBuffer, mnemonicSalt?: string): Promise<NamedData<ArrayBuffer>> {
- try {
- const record = await this.#load(type, key, keySalt, secret, mnemonicSalt)
- if (this.#seed == null) {
- throw new Error('Wallet seed not found')
- }
- return record
- } catch (err) {
- console.error(err)
- throw new Error('Failed to load wallet', { cause: err })
- } finally {
- this.lock()
- }
+ static load (type?: 'BIP-44' | 'BLAKE2b', key?: CryptoKey, keySalt?: ArrayBuffer, secret?: string | ArrayBuffer, mnemonicSalt?: string): Promise<NamedData<ArrayBuffer>> {
+ return this.#load(type, key, keySalt, secret, mnemonicSalt)
+ .then(record => {
+ if (this.#seed == null) {
+ throw new Error('Wallet seed not found')
+ }
+ return record
+ })
+ .catch(err => {
+ console.error(err)
+ throw new Error('Failed to load wallet', { cause: err })
+ })
+ .finally(() => this.lock())
}
static lock (): NamedData<boolean> {
* Derives the account private key at a specified index, signs the input data,
* and returns a signature. The wallet must be unlocked prior to verification.
*/
- static async sign (index?: number, data?: ArrayBuffer): Promise<NamedData<ArrayBuffer>> {
+ static sign (index?: number, data?: ArrayBuffer): Promise<NamedData<ArrayBuffer>> {
try {
+ this.#timeout.pause()
if (this.#locked) {
throw new Error('Wallet is locked')
}
if (data == null) {
throw new Error('Data to sign not found')
}
- this.#timeout.pause()
- const prv = this.#type === 'BIP-44'
- ? await Bip44.ckd(this.#seed, BIP44_COIN_NANO, index)
- : await this.#deriveBlake2bPrivateKey(this.#seed, index)
- const sig = await NanoNaCl.detached(new Uint8Array(data), new Uint8Array(prv))
- this.#timeout = new VaultTimer(() => this.lock(), 120000)
- return { signature: sig.buffer }
+ const derive = this.#type === 'BIP-44'
+ ? Bip44.ckd(this.#seed, BIP44_COIN_NANO, index)
+ : Promise.resolve(this.#deriveBlake2bPrivateKey(this.#seed, index))
+ return derive.then(prv => {
+ const sig = NanoNaCl.detached(new Uint8Array(data), new Uint8Array(prv))
+ this.#timeout = new VaultTimer(() => this.lock(), 120000)
+ return { signature: sig.buffer }
+ })
} catch (err) {
console.error(err)
this.#timeout.resume()
/**
* Decrypts the input and sets the seed and, if it is included, the mnemonic.
*/
- static async unlock (type?: string, key?: CryptoKey, iv?: ArrayBuffer, encrypted?: ArrayBuffer): Promise<NamedData<boolean>> {
- try {
- if (type == null) {
- throw new TypeError('Wallet type is required')
- }
- if (key == null) {
- throw new TypeError('Wallet password is required')
- }
- if (iv == null) {
- throw new TypeError('Wallet IV is required')
- }
- if (encrypted == null) {
- throw new TypeError('Wallet encrypted data is required')
- }
- this.#timeout?.pause()
- await this.#decryptWallet(type, key, iv, encrypted)
- if (!(this.#seed instanceof ArrayBuffer)) {
- throw new TypeError('Invalid seed')
- }
- if (this.#mnemonic != null && !(this.#mnemonic instanceof ArrayBuffer)) {
- throw new TypeError('Invalid mnemonic')
- }
- this.#locked = false
- this.#timeout = new VaultTimer(() => this.lock(), 120000)
- return { isUnlocked: !this.#locked }
- } catch (err) {
- console.error(err)
- this.#timeout?.resume()
- throw new Error('Failed to unlock wallet', { cause: err })
+ static unlock (type?: string, key?: CryptoKey, iv?: ArrayBuffer, encrypted?: ArrayBuffer): Promise<NamedData<boolean>> {
+ if (type == null) {
+ throw new TypeError('Wallet type is required')
+ }
+ if (key == null) {
+ throw new TypeError('Wallet password is required')
}
+ if (iv == null) {
+ throw new TypeError('Wallet IV is required')
+ }
+ if (encrypted == null) {
+ throw new TypeError('Wallet encrypted data is required')
+ }
+ this.#timeout?.pause()
+ return this.#decryptWallet(type, key, iv, encrypted)
+ .then(() => {
+ if (!(this.#seed instanceof ArrayBuffer)) {
+ throw new TypeError('Invalid seed')
+ }
+ if (this.#mnemonic != null && !(this.#mnemonic instanceof ArrayBuffer)) {
+ throw new TypeError('Invalid mnemonic')
+ }
+ this.#locked = false
+ this.#timeout = new VaultTimer(() => this.lock(), 120000)
+ return { isUnlocked: !this.#locked }
+ })
+ .catch(err => {
+ console.error(err)
+ this.#timeout?.resume()
+ throw new Error('Failed to unlock wallet', { cause: err })
+ })
}
/**
* Re-encrypts the wallet with a new password.
*/
- static async update (key?: CryptoKey, keySalt?: ArrayBuffer): Promise<NamedData<ArrayBuffer>> {
+ static update (key?: CryptoKey, keySalt?: ArrayBuffer): Promise<NamedData<ArrayBuffer>> {
try {
+ this.#timeout.pause()
if (this.#locked) {
throw new Error('Wallet is locked')
}
if (key == null || keySalt == null) {
throw new TypeError('Wallet password is required')
}
- this.#timeout.pause()
- const { iv, encrypted } = await this.#encryptWallet(key)
- this.#timeout = new VaultTimer(() => this.lock(), 120000)
- return { iv, salt: keySalt, encrypted }
+ return this.#encryptWallet(key)
+ .then(({ iv, encrypted }) => {
+ this.#timeout = new VaultTimer(() => this.lock(), 120000)
+ return { iv, salt: keySalt, encrypted }
+ })
} catch (err) {
console.error(err)
this.#timeout.resume()
}
}
- static async #createAesKey (purpose: 'encrypt' | 'decrypt', password: ArrayBuffer, keySalt: ArrayBuffer): Promise<CryptoKey> {
- const derivationKey = await crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveKey'])
- new Uint8Array(password).fill(0).buffer.transfer?.()
- const derivationAlgorithm: Pbkdf2Params = {
- name: 'PBKDF2',
- hash: 'SHA-512',
- iterations: 210000,
- salt: keySalt
- }
- const derivedKeyType: AesKeyGenParams = {
- name: 'AES-GCM',
- length: 256
- }
- return await crypto.subtle.deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, [purpose])
+ static #createAesKey (purpose: 'encrypt' | 'decrypt', keySalt: ArrayBuffer, password?: ArrayBuffer): Promise<CryptoKey | undefined> {
+ return new Promise((resolve, reject): void => {
+ if (password == null) {
+ resolve(undefined)
+ return
+ }
+ try {
+ crypto.subtle
+ .importKey('raw', password, 'PBKDF2', false, ['deriveKey'])
+ .then(derivationKey => {
+ new Uint8Array(password).fill(0).buffer.transfer?.()
+ const derivationAlgorithm: Pbkdf2Params = {
+ name: 'PBKDF2',
+ hash: 'SHA-512',
+ iterations: 210000,
+ salt: keySalt
+ }
+ const derivedKeyType: AesKeyGenParams = {
+ name: 'AES-GCM',
+ length: 256
+ }
+ crypto.subtle
+ .deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, [purpose])
+ .then(resolve)
+ })
+ } catch (err) {
+ reject(err)
+ }
+ })
}
- static async #decryptWallet (type: string, key: CryptoKey, iv: ArrayBuffer, encrypted: ArrayBuffer): Promise<void> {
+ static #decryptWallet (type: string, key: CryptoKey, iv: ArrayBuffer, encrypted: ArrayBuffer): Promise<void> {
const seedLength = type === 'BIP-44' ? 64 : 32
const additionalData = utf8.toBytes(type)
- const decrypted = new Uint8Array(await crypto.subtle.decrypt({ name: 'AES-GCM', iv, additionalData }, key, encrypted))
- this.#seed = decrypted.buffer.slice(0, seedLength)
- this.#mnemonic = decrypted.buffer.slice(seedLength)
- decrypted.fill(0)
+ return crypto.subtle.decrypt({ name: 'AES-GCM', iv, additionalData }, key, encrypted)
+ .then(decrypted => {
+ this.#seed = decrypted.slice(0, seedLength)
+ this.#mnemonic = decrypted.slice(seedLength)
+ new Uint8Array(decrypted).fill(0)
+ })
}
/**
return new Blake2b(32).update(s).update(i).digest().buffer
}
- static async #encryptWallet (key: CryptoKey): Promise<NamedData<ArrayBuffer>> {
+ static #encryptWallet (key: CryptoKey): Promise<NamedData<ArrayBuffer>> {
if (this.#type == null) {
throw new Error('Invalid wallet type')
}
const iv = crypto.getRandomValues(new Uint8Array(12)).buffer
const additionalData = utf8.toBytes(this.#type)
const encoded = new Uint8Array([...seed, ...mnemonic])
- const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv, additionalData }, key, encoded)
- encoded.fill(0)
- return { iv, encrypted }
+ return crypto.subtle.encrypt({ name: 'AES-GCM', iv, additionalData }, key, encoded)
+ .then(encrypted => {
+ encoded.fill(0)
+ return { iv, encrypted }
+ })
}
/**
* Parse inbound message from main thread into typechecked variables.
*/
- static async #extractData (message: unknown) {
- // Message itself
- if (message == null) {
- throw new TypeError('Worker received no data')
- }
- if (typeof message !== 'object') {
- throw new Error('Invalid data')
- }
- const messageData = message as { [key: string]: unknown }
-
- // Action for selecting method execution
- if (!('action' in messageData)) {
- throw new TypeError('Wallet action is required')
- }
- if (messageData.action !== 'STOP'
- && messageData.action !== 'create'
- && messageData.action !== 'derive'
- && messageData.action !== 'load'
- && messageData.action !== 'lock'
- && messageData.action !== 'sign'
- && messageData.action !== 'unlock'
- && messageData.action !== 'update'
- && messageData.action !== 'verify') {
- throw new TypeError('Invalid wallet action')
- }
- const action = messageData.action
-
- // Password for lock/unlock key
- if (messageData.password != null && !(messageData.password instanceof ArrayBuffer)) {
- throw new TypeError('Password must be ArrayBuffer')
- }
- let password = messageData.password?.slice()
- if (messageData.password instanceof ArrayBuffer) {
- new Uint8Array(messageData.password).fill(0)
- delete messageData.password
- }
-
- // IV for crypto key, included if unlocking or generated if creating
- if (action === 'unlock' && !(messageData.iv instanceof ArrayBuffer)) {
- throw new TypeError('Initialization vector required to unlock wallet')
- }
- const iv: ArrayBuffer = action === 'unlock' && messageData.iv instanceof ArrayBuffer
- ? messageData.iv
- : crypto.getRandomValues(new Uint8Array(32)).buffer
-
- // Salt for decryption key to unlock
- if (action === 'unlock' && !(messageData.keySalt instanceof ArrayBuffer)) {
- throw new TypeError('Salt required to unlock wallet')
- }
- const keySalt: ArrayBuffer = action === 'unlock' && messageData.keySalt instanceof ArrayBuffer
- ? messageData.keySalt
- : crypto.getRandomValues(new Uint8Array(32)).buffer
-
- // CryptoKey from password, decryption key if unlocking else encryption key
- const key = password instanceof ArrayBuffer
- ? await this.#createAesKey(action === 'unlock' ? 'decrypt' : 'encrypt', password, keySalt)
- : undefined
- if (password instanceof ArrayBuffer && !password.detached) {
- new Uint8Array(password).fill(0)
- password = undefined
- }
-
- // Type of wallet
- if (messageData.type !== undefined && messageData.type !== 'BIP-44' && messageData.type !== 'BLAKE2b') {
- throw new TypeError('Invalid wallet type', { cause: messageData.type })
- }
- const type: 'BIP-44' | 'BLAKE2b' | undefined = messageData.type
-
- // Import requires seed or mnemonic phrase
- if (action === 'load' && messageData.seed == null && messageData.mnemonicPhrase == null) {
- throw new TypeError('Seed or mnemonic phrase required to load wallet')
- }
+ static #extractData (message: unknown) {
+ try {
+ // Message itself
+ if (message == null) {
+ throw new TypeError('Worker received no data')
+ }
+ if (typeof message !== 'object') {
+ throw new Error('Invalid data')
+ }
+ const messageData = message as { [key: string]: unknown }
+
+ // Action for selecting method execution
+ if (!('action' in messageData)) {
+ throw new TypeError('Wallet action is required')
+ }
+ if (messageData.action !== 'STOP'
+ && messageData.action !== 'create'
+ && messageData.action !== 'derive'
+ && messageData.action !== 'load'
+ && messageData.action !== 'lock'
+ && messageData.action !== 'sign'
+ && messageData.action !== 'unlock'
+ && messageData.action !== 'update'
+ && messageData.action !== 'verify') {
+ throw new TypeError('Invalid wallet action')
+ }
+ const action = messageData.action
+
+ // Password for lock/unlock key
+ if (messageData.password != null && !(messageData.password instanceof ArrayBuffer)) {
+ throw new TypeError('Password must be ArrayBuffer')
+ }
+ let password = messageData.password?.slice()
+ if (messageData.password instanceof ArrayBuffer) {
+ new Uint8Array(messageData.password).fill(0)
+ delete messageData.password
+ }
+
+ // IV for crypto key, included if unlocking or generated if creating
+ if (action === 'unlock' && !(messageData.iv instanceof ArrayBuffer)) {
+ throw new TypeError('Initialization vector required to unlock wallet')
+ }
+ const iv: ArrayBuffer = action === 'unlock' && messageData.iv instanceof ArrayBuffer
+ ? messageData.iv
+ : crypto.getRandomValues(new Uint8Array(32)).buffer
+
+ // Salt for decryption key to unlock
+ if (action === 'unlock' && !(messageData.keySalt instanceof ArrayBuffer)) {
+ throw new TypeError('Salt required to unlock wallet')
+ }
+ const keySalt: ArrayBuffer = action === 'unlock' && messageData.keySalt instanceof ArrayBuffer
+ ? messageData.keySalt
+ : crypto.getRandomValues(new Uint8Array(32)).buffer
+
+ // CryptoKey from password, decryption key if unlocking else encryption key
+ return this.#createAesKey(action === 'unlock' ? 'decrypt' : 'encrypt', keySalt, password)
+ .then(key => {
+ if (password?.detached === false) {
+ new Uint8Array(password).fill(0)
+ password = undefined
+ }
- // Seed to load
- if (action === 'load' && 'seed' in messageData && !(messageData.seed instanceof ArrayBuffer)) {
- throw new TypeError('Seed required to load wallet')
- }
- const seed = messageData.seed instanceof ArrayBuffer
- ? messageData.seed.slice()
- : undefined
- if (messageData.seed instanceof ArrayBuffer) {
- new Uint8Array(messageData.seed).fill(0)
- delete messageData.seed
- }
+ // Type of wallet
+ if (messageData.type !== undefined && messageData.type !== 'BIP-44' && messageData.type !== 'BLAKE2b') {
+ throw new TypeError('Invalid wallet type', { cause: messageData.type })
+ }
+ const type: 'BIP-44' | 'BLAKE2b' | undefined = messageData.type
- // Mnemonic phrase to load
- if (action === 'load' && 'mnemonicPhrase' in message && typeof messageData.mnemonicPhrase !== 'string') {
- throw new TypeError('Invalid mnemonic phrase')
- }
- const mnemonicPhrase = typeof messageData.mnemonicPhrase === 'string'
- ? messageData.mnemonicPhrase
- : undefined
- delete messageData.mnemonicPhrase
-
- // Mnemonic salt for mnemonic phrase to load
- if (action === 'load' && messageData.mnemonicSalt != undefined && typeof messageData.mnemonicSalt !== 'string') {
- throw new TypeError('Invalid mnemonic salt for mnemonic phrase')
- }
- const mnemonicSalt = typeof messageData.mnemonicSalt === 'string'
- ? messageData.mnemonicSalt
- : undefined
- delete messageData.mnemonicSalt
+ // Import requires seed or mnemonic phrase
+ if (action === 'load' && messageData.seed == null && messageData.mnemonicPhrase == null) {
+ throw new TypeError('Seed or mnemonic phrase required to load wallet')
+ }
- // Encrypted seed and possibly mnemonic
- if (action === 'unlock') {
- if (messageData.encrypted == null) {
- throw new TypeError('Wallet encrypted secrets not found')
- }
- if (!(messageData.encrypted instanceof ArrayBuffer)) {
- throw new TypeError('Invalid wallet encrypted secrets')
- }
- }
- const encrypted = messageData.encrypted instanceof ArrayBuffer
- ? messageData.encrypted.slice()
- : undefined
- if (messageData.encrypted instanceof ArrayBuffer) {
- new Uint8Array(messageData.encrypted).fill(0)
- delete messageData.encrypted
- }
+ // Seed to load
+ if (action === 'load' && 'seed' in messageData && !(messageData.seed instanceof ArrayBuffer)) {
+ throw new TypeError('Seed required to load wallet')
+ }
+ const seed = messageData.seed instanceof ArrayBuffer
+ ? messageData.seed.slice()
+ : undefined
+ if (messageData.seed instanceof ArrayBuffer) {
+ new Uint8Array(messageData.seed).fill(0)
+ delete messageData.seed
+ }
- // Index for child account to derive or sign
- if ((action === 'derive' || action === 'sign') && typeof messageData.index !== 'number') {
- throw new TypeError('Index is required to derive an account private key')
- }
- const index = typeof messageData.index === 'number'
- ? messageData.index
- : undefined
+ // Mnemonic phrase to load
+ if (action === 'load' && 'mnemonicPhrase' in message && typeof messageData.mnemonicPhrase !== 'string') {
+ throw new TypeError('Invalid mnemonic phrase')
+ }
+ const mnemonicPhrase = typeof messageData.mnemonicPhrase === 'string'
+ ? messageData.mnemonicPhrase
+ : undefined
+ delete messageData.mnemonicPhrase
+
+ // Mnemonic salt for mnemonic phrase to load
+ if (action === 'load' && messageData.mnemonicSalt != undefined && typeof messageData.mnemonicSalt !== 'string') {
+ throw new TypeError('Invalid mnemonic salt for mnemonic phrase')
+ }
+ const mnemonicSalt = typeof messageData.mnemonicSalt === 'string'
+ ? messageData.mnemonicSalt
+ : undefined
+ delete messageData.mnemonicSalt
+
+ // Encrypted seed and possibly mnemonic
+ if (action === 'unlock') {
+ if (messageData.encrypted == null) {
+ throw new TypeError('Wallet encrypted secrets not found')
+ }
+ if (!(messageData.encrypted instanceof ArrayBuffer)) {
+ throw new TypeError('Invalid wallet encrypted secrets')
+ }
+ }
+ const encrypted = messageData.encrypted instanceof ArrayBuffer
+ ? messageData.encrypted.slice()
+ : undefined
+ if (messageData.encrypted instanceof ArrayBuffer) {
+ new Uint8Array(messageData.encrypted).fill(0)
+ delete messageData.encrypted
+ }
- // Data to sign
- if (action === 'sign') {
- if (messageData.data == null) {
- throw new TypeError('Data to sign not found')
- }
- if (!(messageData.data instanceof ArrayBuffer)) {
- throw new TypeError('Invalid data to sign')
- }
+ // Index for child account to derive or sign
+ if ((action === 'derive' || action === 'sign') && typeof messageData.index !== 'number') {
+ throw new TypeError('Index is required to derive an account private key')
+ }
+ const index = typeof messageData.index === 'number'
+ ? messageData.index
+ : undefined
+
+ // Data to sign
+ if (action === 'sign') {
+ if (messageData.data == null) {
+ throw new TypeError('Data to sign not found')
+ }
+ if (!(messageData.data instanceof ArrayBuffer)) {
+ throw new TypeError('Invalid data to sign')
+ }
+ }
+ const data = messageData.data instanceof ArrayBuffer
+ ? messageData.data
+ : undefined
+ delete messageData.data
+
+ return { action, type, key, iv, keySalt, seed, mnemonicPhrase, mnemonicSalt, encrypted, index, data }
+ })
+ .catch(err => {
+ console.error(err)
+ throw new Error('Failed to create AES CryptoKey', { cause: err })
+ })
+ } catch (err) {
+ console.error(err)
+ throw new Error('Failed to extract data', { cause: err })
}
- const data = messageData.data instanceof ArrayBuffer
- ? messageData.data
- : undefined
- delete messageData.data
-
- return { action, type, key, iv, keySalt, seed, mnemonicPhrase, mnemonicSalt, encrypted, index, data }
}
/**
* Encrypts an existing seed or mnemonic+salt and returns the initialization
* vector, salt, and encrypted data representing the wallet in a locked state.
*/
- static async #load (type?: 'BIP-44' | 'BLAKE2b', key?: CryptoKey, keySalt?: ArrayBuffer, secret?: string | ArrayBuffer, mnemonicSalt?: string): Promise<NamedData<ArrayBuffer>> {
+ static #load (type?: 'BIP-44' | 'BLAKE2b', key?: CryptoKey, keySalt?: ArrayBuffer, secret?: string | ArrayBuffer, mnemonicSalt?: string): Promise<NamedData<ArrayBuffer>> {
try {
if (!this.#locked) {
throw new Error('Wallet is in use')
}
}
this.#type = type
+ let seed: Promise<ArrayBuffer>
if (secret instanceof ArrayBuffer) {
- this.#seed = secret
if (type === 'BLAKE2b') {
- this.#mnemonic = utf8.toBuffer((await Bip39.fromEntropy(new Uint8Array(secret))).phrase ?? '')
+ seed = Bip39.fromEntropy(new Uint8Array(secret))
+ .then(bip39 => {
+ this.#mnemonic = utf8.toBuffer(bip39.phrase ?? '')
+ return secret
+ })
+ } else {
+ seed = Promise.resolve(secret)
}
} else {
- const mnemonic = await Bip39.fromPhrase(secret)
- this.#mnemonic = utf8.toBuffer(mnemonic.phrase ?? '')
- this.#seed = type === 'BIP-44'
- ? (await mnemonic.toBip39Seed(mnemonicSalt ?? '')).buffer
- : (await mnemonic.toBlake2bSeed()).buffer
- }
- const { iv, encrypted } = await this.#encryptWallet(key)
- return { iv, salt: keySalt, encrypted }
+ seed = Bip39.fromPhrase(secret)
+ .then(bip39 => {
+ this.#mnemonic = utf8.toBuffer(bip39.phrase ?? '')
+ const derive = type === 'BIP-44'
+ ? bip39.toBip39Seed(mnemonicSalt ?? '')
+ : Promise.resolve(bip39.toBlake2bSeed())
+ return derive.then(s => s.buffer)
+ })
+ }
+ return seed.then(seed => {
+ this.#seed = seed
+ return this.#encryptWallet(key)
+ .then(({ iv, encrypted }) => {
+ return { iv, salt: keySalt, encrypted }
+ })
+ })
} catch (err) {
throw new Error('Failed to load wallet', { cause: err })
}