}\r
\r
/**\r
- * Instantiates an Account object from its private key. The corresponding\r
- * public key will automatically be derived and saved.\r
+ * Instantiates an Account object from its private key which is then encrypted\r
+ * and stored in IndexedDB. The corresponding public key will automatically be\r
+ * derived and saved.\r
*\r
* @param {(string|Uint8Array)} privateKey - Private key of the account\r
* @param {number} [index] - Account number used when deriving the key\r
* @returns {Account} A new Account object\r
*/\r
- static async fromPrivateKey (privateKey: string | Uint8Array<ArrayBuffer>, index?: number): Promise<Account> {\r
+ static async fromPrivateKey (password: string | Uint8Array<ArrayBuffer>, privateKey: string | Uint8Array<ArrayBuffer>, index?: number): Promise<Account> {\r
+ if (typeof password === 'string') password = utf8.toBytes(password)\r
+ if (password == null || !(password instanceof Uint8Array)) {\r
+ throw new Error('Invalid password when importing Account')\r
+ }\r
this.#validateKey(privateKey)\r
if (typeof privateKey === 'string') privateKey = hex.toBytes(privateKey)\r
let publicKey: string\r
} catch (err) {\r
throw new Error(`Failed to derive public key from private key`, { cause: err })\r
}\r
- try {\r
- const self = await this.fromPublicKey(publicKey, index)\r
- self.#privateKey = privateKey\r
- return self\r
- } catch (err) {\r
- throw new Error(`Failed to lock new Account`, { cause: err })\r
- }\r
- }\r
\r
- /**\r
- * Locks the account with a password that will be needed to unlock it later.\r
- *\r
- * @param {(string|Uint8Array)} password Used to lock the account\r
- * @returns True if successfully locked\r
- */\r
- async lock (password: string | Uint8Array<ArrayBuffer>): Promise<boolean> {\r
- if (this.isLocked) {\r
- throw new Error(`Account ${this.address} is already locked`)\r
- }\r
- if (typeof password === 'string') password = utf8.toBytes(password)\r
- if (password == null || !(password instanceof Uint8Array)) {\r
- throw new Error(`Failed to lock Account ${this.address}`)\r
- }\r
+ const self = await this.fromPublicKey(publicKey, index)\r
try {\r
const headers = {\r
method: 'set',\r
- name: this.publicKey\r
+ name: publicKey\r
}\r
const data = {\r
password: password.buffer,\r
- id: new Uint8Array(this.#publicKey).buffer,\r
- privateKey: this.#privateKey.buffer\r
+ id: hex.toBytes(publicKey).buffer,\r
+ privateKey: privateKey.buffer\r
}\r
- return await SafeWorker.add(headers, data)\r
+ const isLocked = await SafeWorker.add(headers, data)\r
+ if (!isLocked) {\r
+ throw null\r
+ }\r
+ return self\r
} catch (err) {\r
- throw new Error(`Failed to lock Account ${this.address}`, { cause: err })\r
+ throw new Error(`Failed to lock Account ${self.address}`, { cause: err })\r
} finally {\r
bytes.erase(password)\r
}\r
* @param {(ChangeBlock|ReceiveBlock|SendBlock)} block - The block data to be hashed and signed\r
* @returns {Promise<string>} Hexadecimal-formatted 64-byte signature\r
*/\r
- async sign (block: ChangeBlock | ReceiveBlock | SendBlock): Promise<string> {\r
- if (this.isLocked) {\r
- throw new Error(`Account ${this.address} must be unlocked prior to signing`)\r
- }\r
+ async sign (password: string | Uint8Array<ArrayBuffer>, block: ChangeBlock | ReceiveBlock | SendBlock): Promise<string> {\r
+ const privateKey = await this.exportPrivateKey(password)\r
try {\r
const headers = {\r
method: 'detached'\r
}\r
const data = {\r
- privateKey: new Uint8Array(this.#privateKey).buffer,\r
+ privateKey: privateKey.buffer,\r
msg: hex.toBytes(block.hash).buffer\r
}\r
const result = await NanoNaClWorker.add(headers, data)\r
return result\r
} catch (err) {\r
throw new Error(`Failed to sign block`, { cause: err })\r
+ } finally {\r
+ bytes.erase(privateKey)\r
}\r
}\r
\r
* @param {(string|Uint8Array)} password Used previously to lock the account\r
* @returns True if successfully unlocked\r
*/\r
- async unlock (password: string | Uint8Array<ArrayBuffer>): Promise<boolean> {\r
+ async exportPrivateKey (password: string | Uint8Array<ArrayBuffer>): Promise<Uint8Array<ArrayBuffer>>\r
+ async exportPrivateKey (password: string | Uint8Array<ArrayBuffer>, format: 'hex'): Promise<string>\r
+ async exportPrivateKey (password: string | Uint8Array<ArrayBuffer>, format?: 'hex'): Promise<string | Uint8Array<ArrayBuffer>> {\r
if (typeof password === 'string') password = utf8.toBytes(password)\r
if (password == null || !(password instanceof Uint8Array)) {\r
throw new Error('Password must be string or bytes')\r
if (id !== this.publicKey) {\r
throw null\r
}\r
- this.#privateKey = new Uint8Array(privateKey as ArrayBuffer)\r
+ const sk = new Uint8Array(privateKey as ArrayBuffer)\r
+ return format === 'hex'\r
+ ? bytes.toHex(sk)\r
+ : sk\r
} catch (err) {\r
throw new Error(`Failed to export private key for Account ${this.address}`, { cause: err })\r
} finally {\r
bytes.erase(password)\r
- return this.isUnlocked\r
}\r
}\r
\r
} else {
try {
const account = (typeof input === 'string')
- ? await Account.fromPrivateKey(input)
+ ? await Account.fromPrivateKey('', input)
: this.account
- this.signature = await account.sign(this)
+ this.signature = await account.sign('', this)
} catch (err) {
throw new Error(`Failed to sign block`, { cause: err })
}
const { privateKey, publicKey, index } = keypair\r
if (index == null) throw new RangeError('Account keys derived but index missing')\r
if (privateKey != null) {\r
- output[index] = await Account.fromPrivateKey(privateKey, index)\r
+ output[index] = await Account.fromPrivateKey(this.seed, privateKey, index)\r
} else if (publicKey != null) {\r
output[index] = await Account.fromPublicKey(publicKey, index)\r
} else {\r
throw new Error('Failed to lock wallet')\r
}\r
try {\r
- const promises = []\r
- for (const account of this.#accounts) {\r
- promises.push(account.lock(this.seed))\r
- }\r
- await Promise.all(promises)\r
+ // const promises = []\r
+ // for (const account of this.#accounts) {\r
+ // promises.push(account.lock(this.seed))\r
+ // }\r
+ // await Promise.all(promises)\r
const headers = {\r
method: 'set',\r
name: this.id\r
this.#s = new Uint8Array(seed as ArrayBuffer)\r
seed = null\r
}\r
- const promises = []\r
- for (const account of this.#accounts) {\r
- promises.push(account.unlock(this.seed))\r
- }\r
- await Promise.all(promises)\r
+ // const promises = []\r
+ // for (const account of this.#accounts) {\r
+ // promises.push(account.exportPrivateKey(this.seed))\r
+ // }\r
+ // await Promise.all(promises)\r
} catch (err) {\r
throw new Error('Failed to unlock wallet')\r
} finally {\r
delete data.password
try {
- if (await this.#exists(name)) {
- throw null
- }
const derivationKey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
if (this.#isInvalid(name, derivationKey, data)) {
- throw null
+ throw new Error('Failed to import key')
}
const base32: { [key: string]: string } = {}
salt: salt.hex,
encrypted
}
- return await this.#add(record, name)
+ return await this.#put(record, name)
} catch (err) {
throw new Error(this.ERR_MSG)
} finally {
try {
const derivationKey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
if (this.#isInvalid(name, derivationKey)) {
- throw null
+ throw new Error('Failed to import key')
}
const record: SafeRecord = await this.#get(name)
if (record == null) {
- throw null
+ throw new Error('Failed to find record')
}
const { encrypted } = record
try {
return await this.#transact<SafeRecord>('readonly', db => db.get(name))
} catch {
- throw new Error(this.ERR_MSG)
+ throw new Error('Failed to get record')
}
}
resolve((event.target as IDBOpenDBRequest).result)
}
request.onerror = (event) => {
- reject(new Error('Failed to open IndexedDB', { cause: event }))
+ reject(new Error('Database error', { cause: event }))
}
})
}
- static async #add (record: SafeRecord, name: string): Promise<boolean> {
- const result = await this.#transact<typeof name>('readwrite', db => db.add(record, name))
+ static async #put (record: SafeRecord, name: string): Promise<boolean> {
+ const result = await this.#transact<typeof name>('readwrite', db => db.put(record, name))
return await this.#exists(result)
}
resolve((event.target as IDBRequest).result)
}
request.onerror = (event) => {
- console.error('IndexedDB transaction error:', (event.target as IDBRequest).error)
+ console.error('Database error')
reject((event.target as IDBRequest).error)
}
})
const wallet = await Bip44Wallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
const account = await wallet.account()\r
+ const privateKey = await account.exportPrivateKey(wallet.seed, 'hex')\r
\r
- assert.equals(account.privateKey, NANO_TEST_VECTORS.PRIVATE_0)\r
+ assert.equals(privateKey, NANO_TEST_VECTORS.PRIVATE_0)\r
assert.equals(account.publicKey, NANO_TEST_VECTORS.PUBLIC_0)\r
assert.equals(account.address, NANO_TEST_VECTORS.ADDRESS_0)\r
assert.equals(account.index, 0)\r
const wallet = await Bip44Wallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
const accounts = await wallet.accounts(1, 2)\r
+ const privateKey1 = await accounts[1].exportPrivateKey(wallet.seed, 'hex')\r
+ const privateKey2 = await accounts[2].exportPrivateKey(wallet.seed, 'hex')\r
\r
assert.equals(accounts.length, 2)\r
- assert.equals(accounts[1].privateKey, NANO_TEST_VECTORS.PRIVATE_1)\r
+ assert.equals(privateKey1, NANO_TEST_VECTORS.PRIVATE_1)\r
assert.equals(accounts[1].publicKey, NANO_TEST_VECTORS.PUBLIC_1)\r
assert.equals(accounts[1].address, NANO_TEST_VECTORS.ADDRESS_1)\r
- assert.equals(accounts[2].privateKey, NANO_TEST_VECTORS.PRIVATE_2)\r
+ assert.equals(privateKey2, NANO_TEST_VECTORS.PRIVATE_2)\r
assert.equals(accounts[2].publicKey, NANO_TEST_VECTORS.PUBLIC_2)\r
assert.equals(accounts[2].address, NANO_TEST_VECTORS.ADDRESS_2)\r
\r
assert.exists(a)\r
assert.exists(a.address)\r
assert.exists(a.publicKey)\r
- assert.exists(a.privateKey)\r
assert.exists(a.index)\r
assert.equals(a.index, i)\r
+ const privateKey = await a.exportPrivateKey(wallet.seed, 'hex')\r
+ assert.exists(privateKey)\r
}\r
\r
await wallet.destroy()\r
assert.exists(a)\r
assert.exists(a.address)\r
assert.exists(a.publicKey)\r
- assert.exists(a.privateKey)\r
assert.exists(a.index)\r
+ const privateKey = await a.exportPrivateKey(wallet.seed, 'hex')\r
+ assert.exists(privateKey)\r
}\r
\r
const highAccounts = await wallet.accounts(0x70000000, 0x700000ff)\r
assert.exists(a)\r
assert.exists(a.address)\r
assert.exists(a.publicKey)\r
- assert.exists(a.privateKey)\r
assert.exists(a.index)\r
+ const privateKey = await a.exportPrivateKey(wallet.seed, 'hex')\r
+ assert.exists(privateKey)\r
}\r
\r
await wallet.destroy()\r