transaction.oncomplete = (event) => {
const results: NamedData<T> = {}
for (const request of requests) {
- results[request.result.id] = request.error ?? request.result
+ results[request.result.name] = request.error ?? request.result
}
resolve(results)
}
} else {
const results: NamedData<T> = {}
for (const result of request.result) {
- results[result.id] = request.error ?? result[result.id]
+ results[result.id] = request.error ?? result
}
resolve(results)
}
const transaction = this.#storage.transaction(store, 'readwrite')
const db = transaction.objectStore(store)
return new Promise((resolve, reject) => {
- const requests = Object.keys(data).map(key => db.put({ id: key, [key]: data[key] }))
+ const requests = Object.keys(data).map(key => db.put(data[key], key))
transaction.oncomplete = (event) => {
const results = []
for (const request of requests) {
}
for (const DB_STORE of this.DB_STORES) {
if (!db.objectStoreNames.contains(DB_STORE)) {
- db.createObjectStore(DB_STORE, { keyPath: 'id' })
+ db.createObjectStore(DB_STORE)
}
}
}
'use strict'
import { parentPort } from 'node:worker_threads'
+import { Bip39Mnemonic } from './bip39-mnemonic.js'
import { Bip39Words } from './bip39-wordlist'
import { Bip44Ckd } from './bip44-ckd'
import { Blake2b } from './blake2b'
import { Blake2bCkd } from './blake2b-ckd'
-import { NanoNaCl } from './nano-nacl'
-import { Bip39Mnemonic } from './bip39-mnemonic.js'
+import { BIP39_ITERATIONS } from './constants'
import { default as Convert, bytes, hex, utf8 } from './convert.js'
+import { NanoNaCl } from './nano-nacl'
import { NamedData } from '#types'
/**
type,
key,
keySalt,
+ iv,
seed,
mnemonicPhrase,
mnemonicSalt,
break
}
case 'unlock': {
- result = await this.unlock(key, keySalt, encrypted)
+ result = await this.unlock(key, iv, encrypted)
break
}
case 'verify': {
transfer.push(result[k])
}
}
+ debugger
//@ts-expect-error
BROWSER: postMessage(result, transfer)
//@ts-expect-error
NODE: parentPort?.postMessage(result, transfer)
} catch (err) {
- BROWSER: postMessage({ error: 'Failed to derive key from password', cause: err })
- NODE: parentPort?.postMessage({ error: 'Failed to derive key from password', cause: err })
+ console.error(err)
+ BROWSER: postMessage({ error: 'Failed to process Safe request', cause: err })
+ NODE: parentPort?.postMessage({ error: 'Failed to process Safe request', cause: err })
}
}
BROWSER: addEventListener('message', listener)
throw new TypeError('Invalid seed')
}
this.#seed = seed
- this.#mnemonic = await Bip39Mnemonic.fromPhrase(mnemonic)
+ if (mnemonic != null) this.#mnemonic = await Bip39Mnemonic.fromPhrase(mnemonic)
this.#locked = false
return { isUnlocked: !this.#locked }
} catch (err) {
+ console.error(err)
throw new Error('Failed to unlock wallet', { cause: err })
}
}
}
static async #createAesKey (purpose: 'encrypt' | 'decrypt', password: ArrayBuffer, keySalt: ArrayBuffer): Promise<CryptoKey> {
+ console.log(keySalt)
+ debugger
const derivationKey = await crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
new Uint8Array(password).fill(0).buffer.transfer()
const derivationAlgorithm: Pbkdf2Params = {
}
static async #decryptWallet (key: CryptoKey, iv: ArrayBuffer, encrypted: ArrayBuffer): Promise<NamedData<string | ArrayBuffer>> {
+ console.log(iv, encrypted)
+ debugger
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, encrypted)
const decoded = JSON.parse(bytes.toUtf8(new Uint8Array(decrypted)))
const seed = hex.toBuffer(decoded.seed)
throw new TypeError('Invalid wallet action')
}
const action = messageData.action
- debugger
+
// Password for lock/unlock key
if (messageData.password != null && !(messageData.password instanceof ArrayBuffer)) {
throw new TypeError('Password must be ArrayBuffer')
}
const type: 'BIP-44' | 'BLAKE2b' | undefined = messageData.type
+ // Import requires seed or mnemonic phrase
+ if (action === 'import' && messageData.seed == null && messageData.mnemonicPhrase == null) {
+ throw new TypeError('Seed or mnemonic phrase required to import wallet')
+ }
+
// Seed to import
if (action === 'import' && !(messageData.seed instanceof ArrayBuffer)) {
throw new TypeError('Seed required to import wallet')
: undefined
// Mnemonic phrase to import
- if (action === 'import' && typeof messageData.mnemonicPhrase !== 'string') {
+ if (action === 'import' && 'mnemonicPhrase' in message && typeof messageData.mnemonicPhrase !== 'string') {
throw new TypeError('Invalid mnemonic phrase')
}
const mnemonicPhrase = typeof messageData.mnemonicPhrase === 'string'
export default `
${importWorkerThreads}
${Convert}
+ const BIP39_ITERATIONS = ${BIP39_ITERATIONS}
const Bip39Mnemonic = ${Bip39Mnemonic}
const Bip39Words = ["${Bip39Words.join('","')}"]
const Bip44Ckd = ${Bip44Ckd}
import { Rpc } from '#src/lib/rpc.js'\r
import { default as SafeWorker } from '#src/lib/safe.js'\r
import { WorkerQueue } from '#src/lib/worker-queue.js'\r
-import { KeyPair, WalletType } from '#types'\r
+import { KeyPair, NamedData, WalletType } from '#types'\r
\r
/**\r
* Represents a wallet containing numerous Nano accounts derived from a single\r
*/\r
static async #get (name: string) {\r
try {\r
- const record = await Database.get<string>(name, this.#DB_NAME)\r
- const decoded = JSON.parse(record[name])\r
- const type: 'BIP-44' | 'BLAKE2b' = decoded.type\r
- const iv: ArrayBuffer = hex.toBuffer(decoded.iv)\r
- const salt: ArrayBuffer = hex.toBuffer(decoded.salt)\r
- const encrypted: ArrayBuffer = hex.toBuffer(decoded.encrypted)\r
- return { type, iv, salt, encrypted }\r
+ const record = await Database.get<NamedData>(name, this.#DB_NAME)\r
+ return record[name]\r
} catch (err) {\r
throw new Error('Failed to get wallet from database', { cause: err })\r
}\r
Wallet.#isInternal = true\r
const self = new this(name, type)\r
try {\r
+ debugger\r
const { iv, salt, encrypted } = await self.#safe.request<ArrayBuffer>({\r
action: 'create',\r
type,\r
password: utf8.toBuffer(password),\r
mnemonicSalt: mnemonicSalt ?? ''\r
})\r
- const encoded = JSON.stringify({\r
+ const data = {\r
+ name,\r
type,\r
- iv: bytes.toHex(new Uint8Array(iv)),\r
- salt: bytes.toHex(new Uint8Array(salt)),\r
- encrypted: bytes.toHex(new Uint8Array(encrypted))\r
- })\r
- await Database.put({ [name]: encoded }, Wallet.#DB_NAME)\r
+ iv,\r
+ salt,\r
+ encrypted\r
+ }\r
+ await Database.put({ [name]: data }, Wallet.#DB_NAME)\r
+ return self\r
+ } catch (err) {\r
+ throw new Error('Error creating new Wallet', { cause: err })\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Imports an existing HD wallet by using an entropy value generated using a\r
+ * cryptographically strong pseudorandom number generator.\r
+ *\r
+ * @param {string} password - Encrypts the wallet to lock and unlock it\r
+ * @param {string} [salt=''] - Used when generating the final seed\r
+ * @returns {Wallet} A newly instantiated Wallet\r
+ */\r
+ static async import (name: string, type: 'BIP-44' | 'BLAKE2b', password: string, seed: string): Promise<Wallet>\r
+ /**\r
+ * Imports an existing HD wallet by using an entropy value generated using a\r
+ * cryptographically strong pseudorandom number generator.\r
+ *\r
+ * @param {string} password - Encrypts the wallet to lock and unlock it\r
+ * @param {string} [salt=''] - Used when generating the final seed\r
+ * @returns {Wallet} A newly instantiated Wallet\r
+ */\r
+ static async import (name: string, type: 'BIP-44' | 'BLAKE2b', password: string, mnemonicPhrase: string, mnemonicSalt?: string): Promise<Wallet>\r
+ static async import (name: string, type: 'BIP-44' | 'BLAKE2b', password: string, secret: string, mnemonicSalt?: string): Promise<Wallet> {\r
+ Wallet.#isInternal = true\r
+ const self = new this(name, type)\r
+ try {\r
+ const data: any = {\r
+ action: 'import',\r
+ type,\r
+ password: utf8.toBuffer(password),\r
+ mnemonicSalt\r
+ }\r
+ if (/^(?:[A-Fa-f0-9]{64}){1,2}$/.test(secret)) {\r
+ data.seed = hex.toBuffer(secret)\r
+ } else {\r
+ data.mnemonicPhrase = secret\r
+ }\r
+ const result = self.#safe.request<ArrayBuffer>(data)\r
+ const { iv, salt, encrypted } = await result\r
+ const record = {\r
+ name,\r
+ type,\r
+ iv,\r
+ salt,\r
+ encrypted\r
+ }\r
+ console.log(record)\r
+ await Database.put({ [name]: record }, Wallet.#DB_NAME)\r
return self\r
} catch (err) {\r
throw new Error('Error creating new Wallet', { cause: err })\r
throw new TypeError('Wallet name is required to restore')\r
}\r
const { type } = await this.#get(name)\r
+ if (type !== 'BIP-44' && type !== 'BLAKE2b') {\r
+ throw new Error('Invalid wallet type from database')\r
+ }\r
Wallet.#isInternal = true\r
return new this(name, type)\r
} catch (err) {\r
const { signature } = await this.#safe.request<ArrayBuffer>({\r
action: 'sign',\r
index,\r
- data: JSON.stringify(block)\r
+ data: hex.toBuffer(block.hash)\r
})\r
const sig = bytes.toHex(new Uint8Array(signature))\r
block.signature = sig\r
*/\r
async unlock (password: string): Promise<boolean> {\r
try {\r
+ debugger\r
const { iv, salt, encrypted } = await Wallet.#get(this.#name)\r
- const { isUnlocked } = await this.#safe.request<boolean>({\r
+ console.log(iv, salt, encrypted)\r
+ const result = await this.#safe.request<boolean>({\r
action: 'unlock',\r
password: utf8.toBuffer(password),\r
iv,\r
- salt,\r
+ keySalt: salt,\r
encrypted\r
})\r
+ const { isUnlocked } = result\r
if (!isUnlocked) {\r
throw new Error('Unlock request to Safe failed')\r
}\r
constructor (account: Account | string, balance: string, representative: Account | string, frontier: string, work?: string)
}
-export type Data = boolean | number | number[] | string | string[] | ArrayBuffer | CryptoKey
+export type Data = boolean | number | number[] | string | string[] | ArrayBuffer | CryptoKey | { [key: string]: Data }
/**
* Represents a cryptographically strong source of entropy suitable for use in
*/
static create (name: string, type: 'BIP-44' | 'BLAKE2b', password: string, mnemonicSalt?: string): Promise<Wallet>
/**
+ * Imports an existing HD wallet by using an entropy value generated using a
+ * cryptographically strong pseudorandom number generator.
+ *
+ * @param {string} password - Encrypts the wallet to lock and unlock it
+ * @param {string} [salt=''] - Used when generating the final seed
+ * @returns {Wallet} A newly instantiated Wallet
+ */
+ static import (name: string, type: 'BIP-44' | 'BLAKE2b', password: string, seed: string): Promise<Wallet>
+ /**
+ * Imports an existing HD wallet by using an entropy value generated using a
+ * cryptographically strong pseudorandom number generator.
+ *
+ * @param {string} password - Encrypts the wallet to lock and unlock it
+ * @param {string} [salt=''] - Used when generating the final seed
+ * @returns {Wallet} A newly instantiated Wallet
+ */
+ static import (name: string, type: 'BIP-44' | 'BLAKE2b', password: string, mnemonicPhrase: string, mnemonicSalt?: string): Promise<Wallet>
+ /**
* Retrieves an existing wallet from the database using its name.
*
* @param {string} name - Entered by user when the wallet was initially created
import { failures, passes } from './GLOBALS.mjs'
import './test.runner-check.mjs'
-import './test.blake2b.mjs'
+// import './test.blake2b.mjs'
import './test.blocks.mjs'
-import './test.calculate-pow.mjs'
-import './test.create-wallet.mjs'
-import './test.derive-accounts.mjs'
-import './test.import-wallet.mjs'
-import './test.ledger.mjs'
-import './test.lock-unlock.mjs'
-import './test.manage-rolodex.mjs'
-import './test.refresh-accounts.mjs'
-import './test.tools.mjs'
+// import './test.calculate-pow.mjs'
+// import './test.create-wallet.mjs'
+// import './test.derive-accounts.mjs'
+// import './test.import-wallet.mjs'
+// import './test.ledger.mjs'
+// import './test.lock-unlock.mjs'
+// import './test.manage-rolodex.mjs'
+// import './test.refresh-accounts.mjs'
+// import './test.tools.mjs'
console.log('%cTESTING COMPLETE', 'color:orange;font-weight:bold')
console.log('%cPASS: ', 'color:green;font-weight:bold', passes.length)
suite('Block signing using official test vectors', async () => {\r
\r
await test('sign open block with wallet', async () => {\r
- const wallet = await Wallet.create('Test', 'BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
+ const wallet = await Wallet.import('Test', 'BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
await assert.resolves(wallet.unlock(NANO_TEST_VECTORS.PASSWORD))\r
\r
const block = new ReceiveBlock(\r