import { _refresh } from './refresh'\r
import { _validate } from './validate'\r
\r
-type KeyPair = {\r
- index?: number\r
- privateKey?: string | Uint8Array<ArrayBuffer>\r
- publicKey?: string | Uint8Array<ArrayBuffer>\r
-}\r
-\r
/**\r
* Represents a single Nano address and the associated public key. To include\r
* the matching private key, it must be known at the time of object\r
export class Account {\r
[key: string]: any\r
\r
- static #isInternal: boolean = false\r
- static get isInternal (): boolean { return this.#isInternal }\r
- /**\r
- * @returns {'Account'}\r
- */\r
+ /** @returns {'Account'} */\r
static get DB_NAME (): 'Account' { return 'Account' }\r
\r
+ #publicKey: Uint8Array<ArrayBuffer> = new Uint8Array(32)\r
#address?: Address\r
- #index?: number\r
- #publicKey: Uint8Array<ArrayBuffer>\r
\r
#confirmed_balance?: bigint\r
#confirmed_height?: number\r
#representative_block?: string\r
#weight?: bigint\r
\r
+ get publicKey (): string { return bytes.toHex(this.#publicKey) }\r
get address (): string {\r
- if (this.#address == null) {\r
+ if (this.#address === undefined) {\r
throw new Error('Account not found')\r
}\r
return this.#address.toString()\r
}\r
- get index (): number | undefined { return this.#index }\r
- get publicKey (): string { return bytes.toHex(this.#publicKey) }\r
\r
get confirmed_balance (): bigint | undefined { return this.#confirmed_balance }\r
get confirmed_height (): number | undefined { return this.#confirmed_height }\r
if (v instanceof (this.constructor as typeof Account)) {\r
this.#confirmed_representative = v\r
} else if (typeof v === 'string') {\r
- this.#confirmed_representative = (this.constructor as typeof Account).load(v)\r
+ this.#confirmed_representative = new Account(v)\r
} else {\r
throw new TypeError(`Invalid argument for account confirmed representative: ${v}`)\r
}\r
if (v instanceof (this.constructor as typeof Account) || v === undefined) {\r
this.#representative = v\r
} else if (typeof v === 'string') {\r
- this.#representative = (this.constructor as typeof Account).load(v)\r
+ this.#representative = new Account(v)\r
} else {\r
throw new TypeError(`Invalid argument for account representative: ${v}`)\r
}\r
set representative_block (v: string | undefined) { this.#representative_block = v }\r
set weight (v: bigint | number | string) { this.#weight = BigInt(v) }\r
\r
- private constructor (address: Address, publicKey: Uint8Array<ArrayBuffer>, index?: number) {\r
- if (!(this.constructor as typeof Account).isInternal) {\r
- throw new Error('Account cannot be instantiated directly. Use `load()` instead.')\r
- }\r
- this.#address = address\r
- this.#publicKey = publicKey\r
- this.#index = index\r
- }\r
-\r
/**\r
* Releases variable references to allow garbage collection.\r
*/\r
destroy (): void {\r
- this.#address = undefined\r
- this.#index = undefined\r
this.#publicKey.fill(0)\r
+ this.#address = undefined\r
\r
this.#confirmed_balance = undefined\r
this.#confirmed_height = undefined\r
*/\r
toJSON () {\r
return {\r
- address: this.address,\r
- index: this.index,\r
publicKey: this.publicKey,\r
+ address: this.address,\r
confirmed_balance: this.confirmed_balance?.toString(),\r
confirmed_height: this.confirmed_height?.toString(),\r
confirmed_frontier: this.confirmed_frontier,\r
* @param {string} address - Address of the account\r
* @returns {Account} A new Account object\r
*/\r
- static load (address: string): Account\r
+ constructor (address: string)\r
/**\r
- * Instantiates Account objects from their Nano addresses.\r
- * @param {string[]} addresses - Addresses of the accounts\r
- * @returns {Account[]} Array of new Account objects\r
- */\r
- static load (addresses: string[]): Account[]\r
- /**\r
- * Instantiates an Account object from its public key. It is unable to sign\r
- * blocks or messages since it has no private key.\r
- * @param {string | Uint8Array<ArrayBuffer>} publicKey - Public key of the account\r
+ * Instantiates an Account object from its public key.\r
+ * @param {(string | Uint8Array<ArrayBuffer>)} publicKey - Public key of the account\r
* @returns {Account} A new Account object\r
*/\r
- static load (publicKey: string | Uint8Array<ArrayBuffer>): Account\r
- /**\r
- * Instantiates Account objects from their public keys. They are unable to sign\r
- * blocks or messages since they have no private key.\r
- * @param {string | Uint8Array<ArrayBuffer>[]} publicKeys - Public keys of the accounts\r
- * @returns {Account[]} Array of new Account objects\r
- */\r
- static load (publicKeys: string | Uint8Array<ArrayBuffer>[]): Account[]\r
+ constructor (publicKey: string | ArrayBuffer | Uint8Array<ArrayBuffer>)\r
/**\r
- * Instantiates an Account object from its public key. It is unable to sign\r
- * blocks or messages since it has no private key.\r
- * @param {KeyPair} keypair - Index and keys of the account\r
+ * Instantiates an Account object from its public or private key.\r
+ *\r
+ * If the key is indicated as private, then it is copied locally, used to\r
+ * derive the corresponding public key, and finally zeroed out; the user is\r
+ * responsible for securely handling the original input.\r
+ * @param {(string | Uint8Array<ArrayBuffer>)} key - Public or private key of the account\r
+ * @param {string} type - Indicates which type the key is\r
* @returns {Account} A new Account object\r
*/\r
- static load (keypair: KeyPair): Account\r
- /**\r
- * Instantiates Account objects from their public keys. They are unable to sign\r
- * blocks or messages since they have no private key.\r
- * @param {KeyPair[]} keypairs - Indexes and keys of the accounts\r
- * @returns {Account[]} Array of new Account objects\r
- */\r
- static load (keypairs: KeyPair[]): Account[]\r
- /**\r
- * Instantiates Account objects from their private keys which are used to\r
- * derive public keys and then discarded.\r
- * @param {(string | Uint8Array<ArrayBuffer> | KeyPair | (string | Uint8Array<ArrayBuffer> | KeyPair)[])} input - Indexes and keys of the accounts\r
- * @returns {(Account | Account[])} Array of new Account objects\r
- */\r
- static load (input: string | Uint8Array<ArrayBuffer> | KeyPair | (string | Uint8Array<ArrayBuffer> | KeyPair)[], type?: 'private'): Account | Account[] {\r
- const isInputArray = Array.isArray(input)\r
- const inputs = isInputArray ? input : [input]\r
- if (this.#isKeyPairs(inputs) && type === 'private') {\r
- const r = this.#fromPrivate(inputs)\r
- return isInputArray ? r : r[0]\r
+ constructor (key: string | ArrayBuffer | Uint8Array<ArrayBuffer>, type: 'public' | 'private')\r
+ constructor (input: unknown, type: unknown = 'public') {\r
+ if (type === 'private') {\r
+ try {\r
+ if (typeof input === 'string' && RegExp(`^[A-F0-9]{${ACCOUNT_KEY_HEX_LENGTH}}$`, 'i').test(input)) {\r
+ input = hex.toBytes(input)\r
+ }\r
+ if (input instanceof Uint8Array) {\r
+ input = input.buffer\r
+ }\r
+ if (!(input instanceof ArrayBuffer) || input.byteLength !== ACCOUNT_KEY_BYTE_LENGTH) {\r
+ throw new TypeError('Invalid private key')\r
+ }\r
+ const prv = new Uint8Array(input.slice())\r
+ try {\r
+ this.#publicKey = nano25519_derive(prv)\r
+ this.#address = new Address(this.#publicKey)\r
+ } finally {\r
+ prv.fill(0)\r
+ }\r
+ } catch (err) {\r
+ throw new Error('Failed to import Account from private key', { cause: err })\r
+ }\r
} else {\r
- return isInputArray ? this.#fromPublic(inputs) : this.#fromPublic(inputs)[0]\r
+ try {\r
+ if (input instanceof Uint8Array) {\r
+ input = input.buffer\r
+ }\r
+ if (typeof input !== 'string' && !(input instanceof ArrayBuffer)) {\r
+ throw new TypeError('Invalid argument')\r
+ }\r
+ this.#address = new Address(input)\r
+ this.#publicKey = this.#address.toPublicKey()\r
+ } catch (err) {\r
+ console.error(err)\r
+ throw new TypeError('Failed to import Account from public data', { cause: { err } })\r
+ }\r
}\r
}\r
\r
static validate (address: string): asserts address is string {\r
return _validate(address)\r
}\r
-\r
- /**\r
- * Instantiates an Account object from its private key which is used to derive\r
- * the corresponding public key and then discarded.\r
- *\r
- * @param {KeyPair} keypairs - Indexes and keys of the accounts\r
- * @returns {Promise<Account[]>} Promise for new Account objects\r
- */\r
- static #fromPrivate (keypairs: KeyPair[]): Account[] {\r
- try {\r
- const accounts: Account[] = []\r
- for (let keypair of keypairs) {\r
- let { index, privateKey } = keypair\r
- if (index == null) {\r
- throw new RangeError('Index missing for Account')\r
- }\r
- if (typeof privateKey === 'string' && RegExp(`^[A-F0-9]{${ACCOUNT_KEY_HEX_LENGTH}}$`, 'i').test(privateKey)) {\r
- privateKey = hex.toBytes(privateKey)\r
- }\r
- if (!(privateKey instanceof Uint8Array) || privateKey.every(v => v === 0)) {\r
- throw new TypeError('Invalid private key')\r
- }\r
- if (privateKey.byteLength !== ACCOUNT_KEY_BYTE_LENGTH) {\r
- throw new TypeError(`Private key must be ${ACCOUNT_KEY_BYTE_LENGTH} bytes`)\r
- }\r
- const publicKey = nano25519_derive(privateKey)\r
- const address = new Address(publicKey)\r
- this.#isInternal = true\r
- const account = new this(address, publicKey, index)\r
- this.#isInternal = false\r
- accounts.push(account)\r
- }\r
- return accounts\r
- } catch (err) {\r
- throw new Error('Failed to import Accounts from private keys', { cause: err })\r
- }\r
- }\r
-\r
- /**\r
- * Instantiates Account objects from public data, each specifying either its\r
- * public key or its Nano address.\r
- *\r
- * @param {(string | Uint8Array<ArrayBuffer>|KeyPair)[]} input - Public keys or addresses of the accounts\r
- * @returns {Account[]} The instantiated Account objects\r
- */\r
- static #fromPublic (input: (string | Uint8Array<ArrayBuffer> | KeyPair)[] | unknown): Account[] {\r
- try {\r
- const keypairs = this.#isKeyPairs(input)\r
- ? input\r
- : this.#isKeys(input)\r
- ? input.map(i => { return { publicKey: i } as KeyPair })\r
- : []\r
- if (keypairs.length === 0) {\r
- throw new TypeError('Invalid public input for Account')\r
- }\r
-\r
- const accounts: Account[] = []\r
- for (let keypair of keypairs) {\r
- if (keypair.publicKey == null) {\r
- throw new TypeError('Account address or public key is required', { cause: keypair.publicKey })\r
- }\r
- const { index } = keypair\r
- const address = new Address(keypair.publicKey)\r
- const publicKey = address.toPublicKey()\r
- this.#isInternal = true\r
- const account = new this(address, publicKey, index)\r
- this.#isInternal = false\r
- accounts.push(account)\r
- }\r
- return accounts\r
- } catch (err) {\r
- console.error(err)\r
- throw new TypeError('Failed to import Account from public data', { cause: { err } })\r
- }\r
- }\r
-\r
- static #isKey (input: unknown): input is string | Uint8Array<ArrayBuffer> {\r
- return typeof input === 'string' || (input instanceof Uint8Array && 'buffer' in input)\r
- }\r
-\r
- static #isKeys (input: unknown): input is (string | Uint8Array<ArrayBuffer>)[] {\r
- if (Array.isArray(input)) {\r
- for (const i of input) {\r
- if (!this.#isKey(i)) {\r
- return false\r
- }\r
- }\r
- }\r
- return true\r
- }\r
-\r
- static #isKeyPair (input: unknown): input is KeyPair {\r
- if (typeof input === 'object') {\r
- const obj = input as Record<string, unknown>\r
- if ('index' in obj && typeof obj.index === 'number') {\r
- return true\r
- }\r
- if ('publicKey' in obj && this.#isKey(obj.publicKey)) {\r
- return true\r
- }\r
- if ('privateKey' in obj && this.#isKey(obj.privateKey)) {\r
- return true\r
- }\r
- }\r
- return false\r
- }\r
-\r
- static #isKeyPairs (input: unknown): input is KeyPair[] {\r
- if (Array.isArray(input)) {\r
- for (const i of input) {\r
- if (!this.#isKeyPair(i)) {\r
- return false\r
- }\r
- }\r
- }\r
- return true\r
- }\r
}\r
\r
assert.equal(account.publicKey, NANO_TEST_VECTORS.PUBLIC_0)\r
assert.equal(account.address, NANO_TEST_VECTORS.ADDRESS_0)\r
- assert.equal(account.index, 0)\r
\r
const accounts = await wallet.accounts()\r
assert.exists(accounts.get(0))\r
assert.exists(account1)\r
assert.equal(account1.publicKey, NANO_TEST_VECTORS.PUBLIC_1)\r
assert.equal(account1.address, NANO_TEST_VECTORS.ADDRESS_1)\r
- assert.equal(account1.index, 1)\r
\r
const account2 = accounts.get(2)\r
assert.exists(account2)\r
assert.equal(account2.publicKey, NANO_TEST_VECTORS.PUBLIC_2)\r
assert.equal(account2.address, NANO_TEST_VECTORS.ADDRESS_2)\r
- assert.equal(account2.index, 2)\r
\r
await assert.resolves(wallet.destroy())\r
})\r
for (let i = 0x70000000; i < 0x7000000f; i++) {\r
const a = accounts.get(i)\r
assert.exists(a)\r
- assert.equal(a.index, i)\r
assert.exists(a.address)\r
assert.exists(a.publicKey)\r
}\r
\r
assert.equal(account.publicKey, NANO_TEST_VECTORS.PUBLIC_0)\r
assert.equal(account.address, NANO_TEST_VECTORS.ADDRESS_0)\r
- assert.equal(account.index, 0)\r
\r
const accounts = await restored.accounts()\r
assert.exists(accounts.get(0))\r
\r
assert.equal(account.publicKey, NANO_TEST_VECTORS.BLAKE2B_PUBLIC_1)\r
assert.equal(account.address, NANO_TEST_VECTORS.BLAKE2B_ADDRESS_1)\r
- assert.equal(account.index, 1)\r
\r
const accounts = await wallet.accounts(1)\r
const account1 = accounts.get(1)\r
assert.exists(account2)\r
assert.exists(account2.publicKey)\r
assert.exists(account2.address)\r
- assert.equal(account2.index, 2)\r
\r
const account3 = accounts.get(3)\r
assert.exists(account3)\r
assert.exists(account3.publicKey)\r
assert.exists(account3.address)\r
- assert.equal(account3.index, 3)\r
\r
await assert.resolves(wallet.destroy())\r
})\r
for (let i = 0x70000000; i < 0x7000000f; i++) {\r
const a = accounts.get(i)\r
assert.exists(a)\r
- assert.equal(a.index, i)\r
assert.exists(a.address)\r
assert.exists(a.publicKey)\r
}\r
\r
assert.equal(account.publicKey, NANO_TEST_VECTORS.BLAKE2B_PUBLIC_1)\r
assert.equal(account.address, NANO_TEST_VECTORS.BLAKE2B_ADDRESS_1)\r
- assert.equal(account.index, 1)\r
\r
const accounts = await restored.accounts(1)\r
assert.exists(accounts.get(1))\r
const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
const account = await wallet.account()\r
- if (account.index == null) {\r
- throw new Error('Account index missing')\r
- }\r
const data = crypto.randomUUID()\r
- const signature = await wallet.sign(account.index, data)\r
+ const signature = await wallet.sign(0, data)\r
\r
assert.ok(await Tools.verify(account.publicKey, signature, data))\r
await assert.resolves(wallet.destroy())\r
const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
const account = await wallet.account()\r
- if (account.index == null) {\r
- throw new Error('Account index missing')\r
- }\r
const sendBlock = await new Block(account.address, '5618869000000000000000000000000', '92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D', 'nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou')\r
.send('nano_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p', '2000000000000000000000000000000')\r
- .sign(wallet, account.index)\r
+ .sign(wallet, 0)\r
\r
assert.ok(await sendBlock.verify(account.publicKey))\r
await assert.resolves(wallet.destroy())\r
const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
const account = await wallet.account()\r
- if (account.index == null) {\r
- throw new Error('Account index missing')\r
- }\r
- assert.equal(account.index, 0)\r
\r
const sendBlock = await new Block(account.address, '5618869000000000000000000000000', '92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D', 'nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou')\r
.send('nano_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p', '2000000000000000000000000000000')\r
- .sign(wallet, account.index)\r
+ .sign(wallet, 0)\r
assert.ok(await sendBlock.verify(account.publicKey))\r
\r
- const wrongAccount = Account.load('nano_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p')\r
+ const wrongAccount = new Account('nano_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p')\r
assert.equal(await sendBlock.verify(wrongAccount.publicKey), false)\r
\r
await assert.resolves(wallet.destroy())\r