// SPDX-License-Identifier: GPL-3.0-or-later\r
\r
import { Blake2b } from './blake2b'\r
-import { ACCOUNT_KEY_LENGTH, ALPHABET, PREFIX, PREFIX_LEGACY } from './constants'\r
+import { ACCOUNT_KEY_BYTE_LENGTH, ACCOUNT_KEY_HEX_LENGTH, ALPHABET, PREFIX, PREFIX_LEGACY } from './constants'\r
import { base32, bytes, hex, utf8 } from './convert'\r
import { Pool } from './pool'\r
import { Rpc } from './rpc'\r
export class Account {\r
static #isInternal: boolean = false\r
static #poolSafe: Pool\r
- #a: string\r
+\r
+ #address: string\r
+ #locked: boolean\r
#pub: string\r
- #prv: string | null\r
- #i?: number\r
- #f?: string\r
- #b?: bigint\r
- #r?: bigint\r
- #rep?: Account\r
- #w?: bigint\r
+ #prv: Uint8Array<ArrayBuffer>\r
+\r
+ #balance?: bigint\r
+ #frontier?: string\r
+ #index?: number\r
+ #receivable?: bigint\r
+ #representative?: Account\r
+ #weight?: bigint\r
\r
- get address () { return `${PREFIX}${this.#a}` }\r
+ get address () { return `${PREFIX}${this.#address}` }\r
+ get isLocked () { return this.#locked }\r
+ get isUnLocked () { return !this.#locked }\r
get publicKey () { return this.#pub }\r
- get privateKey () { return this.#prv }\r
- get index () { return this.#i }\r
- get frontier () { return this.#f }\r
- get balance () { return this.#b }\r
- get receivable () { return this.#r }\r
- get representative () { return this.#rep }\r
- get weight () { return this.#w }\r
+ get privateKey () { return bytes.toHex(this.#prv) }\r
\r
- set frontier (v) { this.#f = v }\r
- set balance (v) { this.#b = v ? BigInt(v) : undefined }\r
- set receivable (v) { this.#r = v ? BigInt(v) : undefined }\r
+ get balance () { return this.#balance }\r
+ get frontier () { return this.#frontier }\r
+ get index () { return this.#index }\r
+ get receivable () { return this.#receivable }\r
+ get representative () { return this.#representative }\r
+ get weight () { return this.#weight }\r
+\r
+ set balance (v) { this.#balance = v ? BigInt(v) : undefined }\r
+ set frontier (v) { this.#frontier = v }\r
+ set receivable (v) { this.#receivable = v ? BigInt(v) : undefined }\r
set representative (v) {\r
if (v?.constructor === Account) {\r
- this.#rep = v\r
+ this.#representative = v\r
} else if (typeof v === 'string') {\r
- this.#rep = Account.fromAddress(v)\r
+ this.#representative = Account.fromAddress(v)\r
} else {\r
throw new TypeError(`Invalid argument for account representative: ${v}`)\r
}\r
}\r
- set weight (v) { this.#w = v ? BigInt(v) : undefined }\r
+ set weight (v) { this.#weight = v ? BigInt(v) : undefined }\r
\r
- constructor (address: string, publicKey: string, privateKey?: string, index?: number) {\r
+ constructor (address: string, publicKey: string, privateKey: Uint8Array<ArrayBuffer>, index?: number) {\r
if (!Account.#isInternal) {\r
throw new Error(`Account cannot be instantiated directly. Use factory methods instead.`)\r
}\r
if (index !== undefined && typeof index !== 'number') {\r
throw new TypeError(`Invalid index ${index} when creating Account ${address}`)\r
}\r
- this.#a = address\r
+ this.#address = address\r
.replace(PREFIX, '')\r
.replace(PREFIX_LEGACY, '')\r
+ this.#index = index\r
+ this.#locked = false\r
this.#pub = publicKey\r
- this.#prv = privateKey ?? null\r
- this.#i = index\r
+ this.#prv = privateKey\r
Account.#poolSafe ??= new Pool(SafeWorker)\r
Account.#isInternal = false\r
}\r
* allow garbage collection.\r
*/\r
async destroy (): Promise<void> {\r
+ this.#prv.fill(0)\r
await Account.#poolSafe.assign({\r
method: 'destroy',\r
name: this.#pub\r
})\r
- this.#prv = null\r
- this.#i = undefined\r
- this.#f = undefined\r
- this.#b = undefined\r
- this.#r = undefined\r
- this.#rep = undefined\r
- this.#w = undefined\r
+ this.#index = undefined\r
+ this.#frontier = undefined\r
+ this.#balance = undefined\r
+ this.#receivable = undefined\r
+ this.#representative = undefined\r
+ this.#weight = undefined\r
}\r
\r
/**\r
Account.#isInternal = true\r
Account.validate(address)\r
const publicKey = Account.#addressToKey(address)\r
- const account = new this(address, publicKey, undefined, index)\r
+ const account = new this(address, publicKey, new Uint8Array(32), index)\r
return account\r
}\r
\r
Account.#isInternal = true\r
Account.#validateKey(publicKey)\r
const address = Account.#keyToAddress(publicKey)\r
- const account = new this(address, publicKey, undefined, index)\r
+ const account = new this(address, publicKey, new Uint8Array(32), index)\r
return account\r
}\r
\r
/**\r
- * Instantiates an Account object from its private key. The\r
- * corresponding public key will automatically be derived and saved.\r
+ * Instantiates an Account object from its private key. The corresponding\r
+ * public key will automatically be derived and saved.\r
*\r
* @param {string} 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 fromPrivateKey (privateKey: string, index?: number): Account {\r
+ static fromPrivateKey (privateKey: string | Uint8Array<ArrayBuffer>, index?: number): Account {\r
+ if (typeof privateKey === 'string') privateKey = hex.toBytes(privateKey)\r
Account.#isInternal = true\r
Account.#validateKey(privateKey)\r
const publicKey = NanoNaCl.convert(privateKey)\r
- const account = Account.fromPublicKey(publicKey, index)\r
- account.#prv = privateKey.toUpperCase()\r
+ const address = Account.#keyToAddress(publicKey)\r
+ const account = new this(address, publicKey, privateKey, index)\r
return account\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): Promise<boolean> {\r
if (typeof password === 'string') {\r
password = utf8.toBytes(password)\r
throw new Error('Failed to unlock wallet')\r
}\r
try {\r
- const data: { id: string, privateKey: string | null } = {\r
+ const data: { id: string, privateKey: Uint8Array } = {\r
id: this.#pub,\r
- privateKey: null\r
- }\r
- if (this.#prv != null) {\r
- data.privateKey = this.#prv\r
+ privateKey: this.#prv\r
}\r
const response = (await Account.#poolSafe.assign({\r
- method: 'put',\r
+ method: 'set',\r
name: this.#pub,\r
password,\r
data\r
}))[0]\r
- const success = response.result\r
+ const success = response?.result\r
if (!success) {\r
throw null\r
}\r
} finally {\r
password.fill(0)\r
}\r
- this.#prv = null\r
+ this.#prv.fill(0)\r
+ this.#locked = true\r
return true\r
}\r
\r
+ /**\r
+ * Unlocks the account using the same password as used prior to lock it.\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): Promise<boolean> {\r
if (typeof password === 'string') {\r
password = utf8.toBytes(password)\r
name: this.#pub,\r
password\r
}))[0]\r
- const { id, privateKey } = response.result\r
- if (id !== this.#pub) {\r
+ const { id, privateKey } = response?.result\r
+ if (id == null || id !== this.#pub) {\r
throw null\r
}\r
- if (privateKey != null) {\r
- this.#prv = privateKey\r
- }\r
+ this.#prv.set(privateKey)\r
} catch (err) {\r
console.error(`Failed to unlock account ${this.address}`, err)\r
return false\r
} finally {\r
password.fill(0)\r
}\r
+ this.#locked = false\r
return true\r
}\r
\r
if (frontier == null) {\r
throw new Error('Account not found')\r
}\r
- this.#b = BigInt(balance)\r
- this.#f = frontier\r
- this.#r = BigInt(receivable)\r
- this.#rep = Account.fromAddress(representative)\r
- this.#w = BigInt(weight)\r
+ this.#balance = BigInt(balance)\r
+ this.#frontier = frontier\r
+ this.#receivable = BigInt(receivable)\r
+ this.#representative = Account.fromAddress(representative)\r
+ this.#weight = BigInt(weight)\r
}\r
\r
static #addressToKey (v: string): string {\r
return `${PREFIX}${encodedPublicKey}${encodedChecksum}`\r
}\r
\r
- static #validateKey (key: string): void {\r
+ static #validateKey (key: unknown): asserts key is (string | Uint8Array) {\r
if (key === undefined) {\r
throw new TypeError(`Key is undefined`)\r
}\r
- if (typeof key !== 'string') {\r
- throw new TypeError(`Key must be a string`)\r
+ if (typeof key !== 'string' && !(key instanceof Uint8Array)) {\r
+ throw new TypeError(`Key must be a string or Uint8Array`)\r
}\r
- if (key.length !== ACCOUNT_KEY_LENGTH) {\r
- throw new TypeError(`Key must be ${ACCOUNT_KEY_LENGTH} characters`)\r
+ if (typeof key === 'string') {\r
+ if (key.length !== ACCOUNT_KEY_HEX_LENGTH) {\r
+ throw new TypeError(`Key must be ${ACCOUNT_KEY_HEX_LENGTH} characters`)\r
+ }\r
+ if (!/^[A-Fa-f0-9]{64}$/i.test(key)) {\r
+ throw new RangeError(`Key is not a valid hexadecimal value`)\r
+ }\r
}\r
- if (!/^[0-9a-fA-F]+$/i.test(key)) {\r
- throw new RangeError('Key is not a valid hexadecimal value')\r
+ if (key instanceof Uint8Array) {\r
+ if (key.byteLength !== ACCOUNT_KEY_BYTE_LENGTH) {\r
+ throw new TypeError(`Key must be ${ACCOUNT_KEY_BYTE_LENGTH} BYTES`)\r
+ }\r
+ if (key.every(v => v === 0)) {\r
+ throw new TypeError(`Key is not a valid byte array`)\r
+ }\r
}\r
}\r
}\r