// SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>
// SPDX-License-Identifier: GPL-3.0-or-later
-import { buffer, utf8 } from './convert.js'
+import { buffer, hex, utf8 } from './convert.js'
import { Entropy } from './entropy.js'
const { subtle } = globalThis.crypto
+const storage = globalThis.sessionStorage
const ERR_MSG = 'Failed to store item in Safe'
export class Safe {
- #items: Map<string, { encrypted: ArrayBuffer, iv: Entropy }> = new Map()
/**
* Encrypts data with a password and stores it in the Safe.
*/
async put (name: string, key: CryptoKey, data: any): Promise<boolean>
async put (name: string, passkey: string | CryptoKey, data: any): Promise<boolean> {
- if (this.#items.get(name)) {
+ if (storage.getItem(name)) {
throw new Error(ERR_MSG)
}
return this.overwrite(name, passkey as string, data)
data = JSON.stringify(data)
const encoded = utf8.toBytes(data)
const encrypted = await subtle.encrypt({ name: 'AES-GCM', iv: iv.buffer }, passkey, encoded)
- this.#items.set(name, { encrypted, iv })
+ const record = {
+ encrypted: buffer.toHex(encrypted),
+ iv: iv.hex
+ }
+ storage.setItem(name, JSON.stringify(record))
passkey = ''
} catch (err) {
throw new Error(ERR_MSG)
}
- return this.#items.has(name)
+ return (storage.getItem(name) != null)
}
/**
return null
}
- const item = this.#items.get(name)
+ const item = storage.getItem(name)
if (item == null) {
return null
}
- const { encrypted, iv } = item
+ const record = JSON.parse(item)
+ const encrypted = hex.toBytes(record.encrypted)
+ const iv = new Entropy(record.iv)
try {
if (typeof passkey === 'string') {
const decoded = buffer.toUtf8(decrypted)
const data = JSON.parse(decoded)
passkey = ''
- this.#items.delete(name)
+ storage.removeItem(name)
return data
} catch (err) {
return null
\r
abstract ckd (index: number): Promise<Account | null>\r
\r
- constructor (seed?: string, mnemonic?: Bip39Mnemonic) {\r
+ constructor (seed?: string, mnemonic?: Bip39Mnemonic, id?: string) {\r
if (this.constructor === Wallet) {\r
throw new Error('Wallet is an abstract class and cannot be instantiated directly.')\r
}\r
this.#accounts = []\r
- this.#id = new Entropy(16)\r
+ this.#id = id\r
+ ? new Entropy(id)\r
+ : new Entropy(16)\r
this.#mnemonic = mnemonic ?? null\r
this.#safe = new Safe()\r
this.#seed = seed ?? null\r
async lock (passkey: string | CryptoKey): Promise<boolean> {\r
let success = true\r
try {\r
- success &&= await this.#safe.overwrite(this.id, passkey as string, this.id)\r
- if (!success) {\r
- throw null\r
+ const data: { id: string, mnemonic: string | null, seed: string | null } = {\r
+ id: this.id,\r
+ mnemonic: null,\r
+ seed: null\r
}\r
if (this.#mnemonic instanceof Bip39Mnemonic) {\r
- success &&= await this.#safe.put('mnemonic', passkey as string, this.#mnemonic.phrase)\r
+ data.mnemonic = this.#mnemonic.phrase\r
}\r
if (typeof this.#seed === 'string') {\r
- success &&= await this.#safe.put('seed', passkey as string, this.#seed)\r
+ data.seed = this.#seed\r
}\r
+ success &&= await this.#safe.put(this.id, passkey as string, data)\r
for (const a of this.#accounts) {\r
success &&= await a.lock(passkey as string)\r
}\r
if (!success) {\r
- throw success\r
+ throw null\r
}\r
} catch (err) {\r
throw new Error('Failed to lock wallet')\r
async unlock (key: CryptoKey): Promise<boolean>\r
async unlock (passkey: string | CryptoKey): Promise<boolean> {\r
try {\r
- const id = await this.#safe.get(this.id, passkey as string)\r
+ const { id, mnemonic, seed } = await this.#safe.get(this.id, passkey as string)\r
if (id !== this.id) {\r
throw null\r
}\r
- const mnemonic = await this.#safe.get('mnemonic', passkey as string)\r
- const seed = await this.#safe.get('seed', passkey as string)\r
for (const a of this.#accounts) {\r
await a.unlock(passkey as string)\r
}\r
export class Bip44Wallet extends Wallet {\r
static #isInternal: boolean = false\r
\r
- constructor (seed: string, mnemonic?: Bip39Mnemonic) {\r
+ constructor (seed: string, mnemonic?: Bip39Mnemonic, id?: string) {\r
if (!Bip44Wallet.#isInternal) {\r
throw new Error(`Bip44Wallet cannot be instantiated directly. Use 'await Bip44Wallet.create()' instead.`)\r
}\r
- super(seed, mnemonic)\r
+ super(seed, mnemonic, id)\r
Bip44Wallet.#isInternal = false\r
}\r
\r
return wallet\r
}\r
\r
+ static async import (id: string) {\r
+ Bip44Wallet.#isInternal = true\r
+ const wallet = new this('', undefined, id)\r
+ }\r
+\r
/**\r
* Derives BIP-44 Nano account private keys.\r
*\r
export class Blake2bWallet extends Wallet {\r
static #isInternal: boolean = false\r
\r
- constructor (seed: string, mnemonic: Bip39Mnemonic) {\r
+ constructor (seed: string, mnemonic?: Bip39Mnemonic, id?: string) {\r
if (!Blake2bWallet.#isInternal) {\r
throw new Error(`Blake2bWallet cannot be instantiated directly. Use 'await Blake2bWallet.create()' instead.`)\r
}\r
- super(seed, mnemonic)\r
+ super(seed, mnemonic, id)\r
Blake2bWallet.#isInternal = false\r
}\r
\r
}\r
}\r
\r
+ static async import (id: string) {\r
+ Blake2bWallet.#isInternal = true\r
+ const wallet = new this('', undefined, id)\r
+ }\r
+\r
/**\r
* Derives BLAKE2b account private keys.\r
*\r
// SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>
// SPDX-License-Identifier: GPL-3.0-or-later
+export const STORAGE = (() => {
+ const _sessionStorage = {}
+ Object.defineProperty(globalThis, 'sessionStorage', {
+ value: {
+ length: Object.getOwnPropertyNames(_sessionStorage).length,
+ setItem: (key, value) => _sessionStorage[key] = value,
+ getItem: (key) => _sessionStorage[key],
+ removeItem: (key) => delete _sessionStorage[key]
+ },
+ configurable: true,
+ enumerable: true
+ })
+})()
+
export const GENESIS_ADDRESS = 'nano_3t6k35gi95xu6tergt6p69ck76ogmitsa8mnijtpxm9fkcm736xtoncuohr3'
export const RAW_MAX = '340282366920938463463374607431768211455'
export const SUPPLY_MAX = '133248297920938463463374607431768211455'
\r
import { describe, it } from 'node:test'\r
import { strict as assert } from 'assert'\r
+import { NANO_TEST_VECTORS, STORAGE, TREZOR_TEST_VECTORS } from './TEST_VECTORS.js'\r
import { Account, Bip44Wallet, Blake2bWallet, LedgerWallet } from '../dist/main.js'\r
-import { NANO_TEST_VECTORS, TREZOR_TEST_VECTORS } from './TEST_VECTORS.js'\r
\r
// WARNING: Do not send any funds to the test vectors below\r
// Test vectors from https://docs.nano.org/integration-guides/key-management/ and elsewhere\r
\r
import { describe, it } from 'node:test'\r
import { strict as assert } from 'assert'\r
+import { NANO_TEST_VECTORS, STORAGE } from './TEST_VECTORS.js'\r
import { Bip44Wallet, Blake2bWallet, LedgerWallet } from '../dist/main.js'\r
-import { NANO_TEST_VECTORS } from './TEST_VECTORS.js'\r
\r
// WARNING: Do not send any funds to the test vectors below\r
// Test vectors from https://docs.nano.org/integration-guides/key-management/ and elsewhere\r
\r
import { describe, it } from 'node:test'\r
import { strict as assert } from 'assert'\r
+import { BIP32_TEST_VECTORS, CUSTOM_TEST_VECTORS, NANO_TEST_VECTORS, STORAGE, TREZOR_TEST_VECTORS } from './TEST_VECTORS.js'\r
import { Account, Bip44Wallet, Blake2bWallet } from '../dist/main.js'\r
-import { BIP32_TEST_VECTORS, CUSTOM_TEST_VECTORS, NANO_TEST_VECTORS, TREZOR_TEST_VECTORS } from './TEST_VECTORS.js'\r
\r
// WARNING: Do not send any funds to the test vectors below\r
// Test vectors from https://docs.nano.org/integration-guides/key-management/ and elsewhere\r
\r
import { describe, it } from 'node:test'\r
import { strict as assert } from 'assert'\r
+import { NANO_TEST_VECTORS, STORAGE, TREZOR_TEST_VECTORS } from './TEST_VECTORS.js'\r
import { Bip44Wallet, Blake2bWallet } from '../dist/main.js'\r
-import { NANO_TEST_VECTORS, TREZOR_TEST_VECTORS } from './TEST_VECTORS.js'\r
\r
const skip = false\r
\r
import { describe, it } from 'node:test'
import { strict as assert } from 'assert'
+import { NANO_TEST_VECTORS, STORAGE } from './TEST_VECTORS.js'
import { Account, Bip44Wallet, Node } from '../dist/main.js'
-import { NANO_TEST_VECTORS } from './TEST_VECTORS.js'
// WARNING: Do not send any funds to the test vectors below
// Test vectors from https://docs.nano.org/integration-guides/key-management/ and elsewhere
\r
import { describe, it } from 'node:test'\r
import { strict as assert } from 'assert'\r
+import { RAW_MAX, NANO_TEST_VECTORS, STORAGE } from './TEST_VECTORS.js'\r
import { Bip44Wallet, Account, SendBlock, Node, Tools } from '../dist/main.js'\r
-import { RAW_MAX, NANO_TEST_VECTORS } from './TEST_VECTORS.js'\r
\r
const wallet = await Bip44Wallet.fromSeed(NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r