From b37d8c3ff927d4985e11eff29bf751c857fee8aa Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Thu, 28 Nov 2024 02:19:38 -0800 Subject: [PATCH] Account class is pretty simple overall, so having async factory methods is not very useful. Refactor them to be synchronous. --- src/lib/account.ts | 89 ++++++++++++++++++++++++++++++++++------------ src/lib/block.ts | 14 ++++---- src/lib/tools.ts | 2 +- src/lib/wallet.ts | 2 +- 4 files changed, 76 insertions(+), 31 deletions(-) diff --git a/src/lib/account.ts b/src/lib/account.ts index 636a84f..953fdef 100644 --- a/src/lib/account.ts +++ b/src/lib/account.ts @@ -15,6 +15,7 @@ import { nacl } from './workers/nano-nacl.js' * be fetched from the network. */ export class Account { + static #isInternal: boolean = false #a: string #pub: string #prv: string | null @@ -43,53 +44,97 @@ export class Account { if (v?.constructor === Account) { this.#rep = v } else if (typeof v === 'string') { - this.#rep = new Account(v) + this.#rep = Account.fromAddress(v) } else { throw new TypeError(`Invalid argument for account representative: ${v}`) } } set weight (v) { this.#w = v ? BigInt(v) : undefined } - constructor (address: string, index?: number) { - Account.validate(address) + constructor (address: string, publicKey: string, privateKey?: string, index?: number) { + if (!Account.#isInternal) { + throw new Error(`Account cannot be instantiated directly. Use factory methods instead.`) + } if (index !== undefined && typeof index !== 'number') { throw new TypeError(`Invalid index ${index} when creating Account ${address}`) } this.#a = address - .replace(PREFIX, '') - .replace(PREFIX_LEGACY, '') - this.#pub = Account.#addressToKey(this.#a) - this.#prv = null + this.#pub = publicKey + this.#prv = privateKey ?? null this.#i = index this.#s = new Safe() + Account.#isInternal = false + } + + /** + * Instantiates an Account object from its Nano address. + * + * @param {string} address - Address of the account + * @param {number} [index] - Account number used when deriving the address + * @returns {Promise} The instantiated Account object + */ + static fromAddress (address: string, index?: number): Account { + Account.#isInternal = true + Account.validate(address) + address = address + .replace(PREFIX, '') + .replace(PREFIX_LEGACY, '') + const publicKey = Account.#addressToKey(address) + const account = new this(address, publicKey, undefined, index) + return account } /** - * Asynchronously instantiates an Account object from its public key. + * Instantiates an Account object from its public key. * - * @param {string} key - Public key of the account + * @param {string} publicKey - Public key of the account * @param {number} [index] - Account number used when deriving the key * @returns {Promise} The instantiated Account object */ - static async fromPublicKey (key: string, index?: number): Promise { - Account.#validateKey(key) - const address = await Account.#keyToAddress(key) - const account = new this(address, index) + static fromPublicKey (publicKey: string, index?: number): Account { + Account.#isInternal = true + Account.#validateKey(publicKey) + const address = Account.#keyToAddress(publicKey) + const account = new this(address, publicKey, undefined, index) return account } /** - * Asynchronously instantiates an Account object from its private key. + * Instantiates an Account object from its private key. The + * corresponding public key will automatically be derived and saved. * - * @param {string} key - Private key of the account + * @param {string} privateKey - Private key of the account * @param {number} [index] - Account number used when deriving the key * @returns {Promise} A new Account object */ - static async fromPrivateKey (key: string, index?: number): Promise { - Account.#validateKey(key) - const { publicKey } = nacl.keyPair.fromSeed(hex.toBytes(key)) - const account = await Account.fromPublicKey(bytes.toHex(publicKey), index) - account.#prv = key.toUpperCase() + static fromPrivateKey (privateKey: string, index?: number): Account { + Account.#isInternal = true + Account.#validateKey(privateKey) + const { publicKey } = nacl.keyPair.fromSeed(hex.toBytes(privateKey)) + const account = Account.fromPublicKey(bytes.toHex(publicKey), index) + account.#prv = privateKey.toUpperCase() + return account + } + + /** + * Asynchronously instantiates an Account object from its public and private + * keys. + * + * WARNING: The validity of the keys is checked, but they are assumed to have + * been precalculated. Whether they are an actual matching pair is NOT checked! + * If unsure, use `Account.fromPrivateKey(key)` instead. + * + * @param {string} publicKey - Public key of the account + * @param {string} privateKey - Private key of the account + * @param {number} [index] - Account number used when deriving the key + * @returns {Promise} The instantiated Account object + */ + static fromKnownKeys (publicKey: string, privateKey: string, index?: number): Account { + Account.#isInternal = true + Account.#validateKey(publicKey) + Account.#validateKey(privateKey) + const account = Account.fromPublicKey(publicKey) + account.#prv = privateKey.toUpperCase() return account } @@ -180,7 +225,7 @@ export class Account { this.#b = BigInt(balance) this.#f = frontier this.#r = BigInt(receivable) - this.#rep = new Account(representative) + this.#rep = Account.fromAddress(representative) this.#w = BigInt(weight) } @@ -195,7 +240,7 @@ export class Account { return bytes.toHex(keyBytes) } - static async #keyToAddress (key: string): Promise { + static #keyToAddress (key: string): string { const publicKeyBytes = hex.toBytes(key) const checksum = new Blake2b(5).update(publicKeyBytes).digest() as Uint8Array checksum.reverse() diff --git a/src/lib/block.ts b/src/lib/block.ts index c5d2a88..14a244f 100644 --- a/src/lib/block.ts +++ b/src/lib/block.ts @@ -31,7 +31,7 @@ abstract class Block { if (account.constructor === Account) { this.account = account } else if (typeof account === 'string') { - this.account = new Account(account) + this.account = Account.fromAddress(account) } else { throw new TypeError('Invalid account') } @@ -226,8 +226,8 @@ export class SendBlock extends Block { constructor (sender: Account | string, balance: string, recipient: string, amount: string, representative: string, frontier: string, work?: string) { super(sender) this.previous = frontier - this.representative = new Account(representative) - this.link = new Account(recipient).publicKey + this.representative = Account.fromAddress(representative) + this.link = Account.fromAddress(recipient).publicKey this.work = work ?? '' const bigBalance = BigInt(balance) @@ -254,8 +254,8 @@ export class ReceiveBlock extends Block { constructor (recipient: string, balance: string, origin: string, amount: string, representative: string, frontier?: string, work?: string) { super(recipient) - this.previous = frontier ?? new Account(recipient).publicKey - this.representative = new Account(representative) + this.previous = frontier ?? Account.fromAddress(recipient).publicKey + this.representative = Account.fromAddress(representative) this.link = origin this.work = work ?? '' @@ -278,14 +278,14 @@ export class ChangeBlock extends Block { previous: string representative: Account balance: bigint - link: string = new Account(BURN_ADDRESS).publicKey + link: string = Account.fromAddress(BURN_ADDRESS).publicKey signature?: string work?: string constructor (account: string, balance: string, representative: string, frontier: string, work?: string) { super(account) this.previous = frontier - this.representative = new Account(representative) + this.representative = Account.fromAddress(representative) this.balance = BigInt(balance) this.work = work ?? '' diff --git a/src/lib/tools.ts b/src/lib/tools.ts index 2ef54b7..c4d5166 100644 --- a/src/lib/tools.ts +++ b/src/lib/tools.ts @@ -119,7 +119,7 @@ export async function sweep (rpc: Rpc | string | URL, wallet: Blake2bWallet | Bi const blockQueue: Promise[] = [] const results: { status: 'success' | 'error', address: string, message: string }[] = [] - const recipientAccount = new Account(recipient) + const recipientAccount = Account.fromAddress(recipient) const accounts = await wallet.refresh(rpc, from, to) for (const account of accounts) { if (account.representative?.address && account.frontier) { diff --git a/src/lib/wallet.ts b/src/lib/wallet.ts index 71d393f..2f0da5f 100644 --- a/src/lib/wallet.ts +++ b/src/lib/wallet.ts @@ -123,7 +123,7 @@ abstract class Wallet { for (const key of Object.keys(errors ?? {})) { const value = errors[key] if (value === 'Account not found') { - return new Account(key) + return Account.fromAddress(key) } } return await this.getNextNewAccount(rpc, batchSize, from + batchSize) -- 2.34.1