From: Chris Duncan Date: Thu, 7 Aug 2025 19:14:17 +0000 (-0700) Subject: Allow block signing to return block immediately instead of a promise if a precalculat... X-Git-Tag: v0.10.5~43^2~54 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=d5c68583bb2c51888b1db4a15dd6bc2e26e13dc3;p=libnemo.git Allow block signing to return block immediately instead of a promise if a precalculated signature is provided. Update type definitions. --- diff --git a/README.md b/README.md index 2e34f7c..126fa52 100644 --- a/README.md +++ b/README.md @@ -66,16 +66,15 @@ password. Refer to the documentation on each class factory method for specific usage. ```javascript -import { Bip44Wallet, Blake2bWallet } from 'libnemo' +import { Wallet } from 'libnemo' -const wallet = await Bip44Wallet.create(password) -const wallet = await Bip44Wallet.fromEntropy(password, entropy, salt?) -const wallet = await Bip44Wallet.fromMnemonic(password, mnemonic, salt?) -const wallet = await Bip44Wallet.fromSeed(password, seed) +const wallet = await Wallet.create(password) +const wallet = await Wallet.import(password, mnemonic, salt?) +const wallet = await Wallet.import(password, seed) -const wallet = await Bip44Wallet.create(password) -const wallet = await Bip44Wallet.fromSeed(password, seed) -const wallet = await Bip44Wallet.fromMnemonic(password, mnemonic) +const wallet = await Wallet.create(password) +const wallet = await Wallet.import(password, seed) +const wallet = await Wallet.import(password, mnemonic) ``` ```javascript diff --git a/src/lib/block.ts b/src/lib/block.ts index b88d360..44fed06 100644 --- a/src/lib/block.ts +++ b/src/lib/block.ts @@ -365,7 +365,7 @@ export class Block { * * @param {string} [key] - 64-byte hexadecimal signature */ - async sign (signature: string): Promise + sign (signature: string): Block /** * Signs the block using a private key. If successful, the result is stored in * the `signature` property of the block. @@ -392,53 +392,46 @@ export class Block { * @param {object} [frontier] - JSON of frontier block for offline signing */ async sign (index: number, frontier?: Block): Promise - async sign (input: unknown, param?: unknown): Promise { - try { - if (typeof input !== 'string' && typeof input !== 'number' && !(input instanceof Wallet)) { - throw new TypeError('Invalid input') - - } else if (typeof input === 'string') { - if (/^[A-F0-9]{128}$/.test(input)) { - this.signature = input - } else if (/^[A-F0-9]{64}$/.test(input)) { + sign (input: unknown, param?: unknown): Block | Promise { + if (typeof input === 'string' && /^[A-F0-9]{128}$/.test(input)) { + this.signature = input + return this + } + return new Promise(async (resolve, reject) => { + try { + if (typeof input !== 'number' && typeof input !== 'string' && !(input instanceof Wallet)) { + throw new TypeError('Invalid signing input') + } else if (typeof input === 'string' && /^[A-F0-9]{64}$/.test(input)) { const sig = await NanoNaCl.detached(this.hash, hex.toBytes(input)) this.signature = bytes.toHex(sig) - } else { - throw new TypeError('Invalid signing input') - } - return this - - } else if (input instanceof Wallet && typeof param === 'number') { - const wallet = input - const sig = await wallet.sign(param, this, 'hex') - if (this.signature !== sig) { - throw new Error('Wallet signature does not match block signature') - } - return this - - } else if (typeof input === 'number') { - const index = input - const { Ledger } = await import('./ledger') - const ledger = await Ledger.create() - await ledger.connect() - if (param && param instanceof Block) { - try { - await ledger.updateCache(index, param) - } catch (err) { - console.warn('Error updating Ledger cache of previous block, attempting signature anyway', err) + } else if (input instanceof Wallet && typeof param === 'number') { + const wallet = input + const sig = await wallet.sign(param, this, 'hex') + if (this.signature !== sig) { + throw new Error('Wallet signature does not match block signature') + } + } else if (typeof input === 'number') { + const index = input + const { Ledger } = await import('./ledger') + const ledger = await Ledger.create() + await ledger.connect() + if (param && param instanceof Block) { + try { + await ledger.updateCache(index, param) + } catch (err) { + console.warn('Error updating Ledger cache of previous block, attempting signature anyway', err) + } } + this.signature = await ledger.sign(index, this) + } else { + throw new TypeError('invalid key for block signature', { cause: typeof input }) } - this.signature = await ledger.sign(index, this) - return this - - } else { - throw new TypeError('invalid key for block signature', { cause: typeof input }) + resolve(this) + } catch (err) { + console.error(err) + reject(new Error('Failed to sign block', { cause: err })) } - } catch (err) { - console.error(err) - throw new Error('Failed to sign block', { cause: err }) - } - + }) } /** diff --git a/src/lib/ledger.ts b/src/lib/ledger.ts index 962cfee..0b9a00a 100644 --- a/src/lib/ledger.ts +++ b/src/lib/ledger.ts @@ -355,9 +355,9 @@ export class Ledger extends Wallet { const testWallet = await Wallet.import('BIP-44', '', secret) await testWallet.unlock('') const testAccount = await testWallet.account(0) - const testOpenBlock = new Block(testAccount.address, '0', testAccount.publicKey, testAccount.address,) + const testOpenBlock = await new Block(testAccount.address, '0', testAccount.publicKey, testAccount.address) .receive(testAccount.publicKey, 0) - await testWallet.sign(0, testOpenBlock) + .sign(testWallet, 0) const testSendBlock = new Block(testAccount.address, '0', bytes.toHex(testOpenBlock.hash), testAccount.address) .send(testAccount.address, 0) const testSignature = await testWallet.sign(0, testOpenBlock, 'hex') diff --git a/src/lib/tools.ts b/src/lib/tools.ts index 94e8119..9035785 100644 --- a/src/lib/tools.ts +++ b/src/lib/tools.ts @@ -107,7 +107,7 @@ export async function sign (key: Key, ...input: string[]): Promise { * them all to a single recipient address. Hardware wallets are unsupported. * * @param {Rpc|string|URL} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks -* @param {Blake2bWallet|Bip44Wallet|Ledger} wallet - Wallet from which to sweep funds +* @param {(Wallet|Ledger)} wallet - Wallet from which to sweep funds * @param {string} recipient - Destination address for all swept funds * @param {number} from - Starting account index to sweep * @param {number} to - Ending account index to sweep @@ -136,10 +136,11 @@ export async function sweep ( const accounts = await wallet.refresh(rpc, from, to) for (const account of accounts) { if (account.representative?.address && account.frontier && account.index) { - const block = await (await new Block(account) - .send(recipientAccount, account.balance ?? 0n) - .pow()) - .sign(wallet, account.index) + const block = new Block(account).send(recipientAccount, account.balance ?? 0n) + await Promise.all([ + block.pow(), + block.sign(wallet, account.index) + ]) const blockRequest: Promise = new Promise(async (resolve) => { try { const hash = await block.process(rpc) diff --git a/src/types.d.ts b/src/types.d.ts index 571de1f..21201e1 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -79,49 +79,23 @@ export declare class Account { */ static import (keypairs: KeyPair[]): Account[] /** - * Instantiates an Account object from its private key which is then encrypted - * and stored in IndexedDB. The corresponding public key will automatically be - * derived and saved. + * Instantiates an Account object from its private key which is used to derive + * a public key and then discarded. * - * @param {KeyPair} keypair - Index and keys of the account - * @param {Key} password - Used to encrypt the private key + * @param {KeyPair} keypair - Index and key of the account + * @param {string} type - Indicates a private key * @returns {Promise} Promise for a new Account object */ - static import (keypair: KeyPair, password: Key): Promise + static import (keypair: KeyPair, type: 'private'): Promise /** - * Instantiates Account objects from their private keys which are then - * encrypted and stored in IndexedDB. The corresponding public keys will - * automatically be derived and saved. + * Instantiates Account objects from their private keys which are used to + * derive public keys and then discarded. * * @param {KeyPair[]} keypairs - Indexes and keys of the accounts - * @param {Key} password - Used to encrypt the private keys + * @param {string} type - Indicates private keys * @returns {Promise} Promise for array of new Account objects */ - static import (keypairs: KeyPair[], password: Key): Promise - /** - * USING THIS METHOD IS DISCOURAGED. This library works in its entirety without - * exposing the private keys of accounts. - * - * Retrieves and decrypts the private key of the Account. The same password - * used to lock it must be used to unlock it. If derived from a wallet, the - * password for the account is the wallet seed. - * - * @param {Key} password Used previously to lock the Account - * @returns Private key bytes as a Uint8Array - */ - export (password: Key): Promise> - /** - * USING THIS METHOD IS DISCOURAGED. This library works in its entirety without - * exposing the private keys of accounts. - * - * Retrieves and decrypts the private key of the Account. The same password - * used to lock it must be used to unlock it. If derived from a wallet, the - * password for the account is the wallet seed. - * - * @param {Key} password Used previously to lock the Account - * @returns Private key bytes as a hexadecimal string - */ - export (password: Key, format: 'hex'): Promise + static import (keypairs: KeyPair[], type: 'private'): Promise /** * Refreshes the account from its current state on the network. * @@ -132,15 +106,6 @@ export declare class Account { */ refresh (rpc: Rpc | string | URL): Promise /** - * Signs a block using the private key of the account. The signature is - * appended to the signature field of the block before being returned. - * - * @param {(Block)} block - The block data to be hashed and signed - * @param {Key} password - Required to decrypt the private key for signing - * @returns {Promise} Hexadecimal-formatted 64-byte signature - */ - sign (block: Block, password: Key): Promise - /** * Validates a Nano address with 'nano' and 'xrb' prefixes * Derived from https://github.com/alecrios/nano-address-validator * @@ -149,7 +114,6 @@ export declare class Account { */ static validate (address: unknown): asserts address is string } - export declare class AccountList extends Object { [index: number]: Account get length (): number; @@ -270,7 +234,20 @@ export declare class Block { link?: Uint8Array signature?: string work?: string - constructor (account: string | Account, balance?: bigint | string, previous?: string, representative?: string | Account, signature?: string) + /** + * Initialize a block with the current state of an account so that it can + * subsequently be configured as a change, receive, or send transaction. + * + * All parameters are eventually required in order to initialize the block, but + * but if `account` is an Account class object, its properties can be used for + * the other parameters instead of passing them into the constructor. + * + * @param {(string|Account)} account - Target of the transaction; can include `balance`, `frontier`, `representative` + * @param {(bigint|number|string)} [balance] - Current balance of the target account + * @param {string} [previous] - Current frontier block hash of the target account + * @param {(string|Account)} [representative] - Current representative of the target account + */ + constructor (account: string | Account, balance?: bigint | number | string, previous?: string, representative?: string | Account) /** * Calculates the block hash using Blake2b. * @@ -286,25 +263,13 @@ export declare class Block { [key: string]: string } /** - * Set the subtype and link to configure this as a change representative block. - * - * @returns {Block} This block so that additional calls can be chained - */ - change (): Block - /** - * Set a block hash as the source send of a receive block. + * Set the subtype, link, and target account to configure this as a change + * representative block. * - * @param {string} hash - Hash of send block to be received - * @returns {Block} This block so that additional calls can be chained + * @param {(string|Account)} account - Account to choose as representative, or its address or public key + * @returns {Block} This block with link, representative, and subtype configured */ - from (hash: string): Block - /** - * Set a send block as the source of a receive block. - * - * @param {Block} block - Send block to be received - * @returns {Block} This block so that additional calls can be chained - */ - from (block: Block): Block + change (representative: string | Account): Block /** * Calculates proof-of-work using a pool of Web Workers. * @@ -322,57 +287,40 @@ export declare class Block { */ process (rpc: Rpc): Promise /** - * Set the amount of nano that this block will receive from a paired send. - * - * @param {bigint} amount - Amount that was sent from sender in raw - * @returns {Block} This block so that additional calls can be chained - */ - receive (amount: bigint): Block - /** - * Set the amount of nano that this block will receive from a paired send. + * Set the amount of nano that this block will receive from a corresponding + * send block. * - * @param {number} amount - Amount that was sent from sender in nano (10³⁰ raw) - * @returns {Block} This block so that additional calls can be chained + * @param {(string|Block)} sendBlock - Corresponding send block or its hash + * @param {(bigint|number|string)} amount - Amount to be received from sender + * @param {string} [unit] - Unit of measure for amount. Default: "NANO" (10³⁰ RAW) + * @returns {Block} This block with balance, link, and subtype configured */ - receive (amount: number): Block - /** - * Set the amount of nano that this block will receive from a paired send. - * - * @param {string} amount - Amount that was sent from sender in raw - * @returns {Block} This block so that additional calls can be chained - */ - receive (amount: string): Block + receive (sendBlock: string | Block, amount: bigint | number | string, unit?: string): Block /** * Set the amount of nano that this block will send to a recipient account. * - * @param {bigint} amount - Amount to send to recipient in raw - * @returns {Block} This block so that additional calls can be chained + * @param {(string|Account)} account - Account to target or its address or public key + * @param {(bigint|number|string)} amount - Amount to send to recipient + * @param {string} [unit] - Unit of measure for amount. Default: "NANO" (10³⁰ RAW) + * @returns {Block} This block with balance, link, and subtype configured */ - send (amount: bigint): Block + send (account: string | Account, amount: bigint | number | string, unit?: string): Block /** - * Set the amount of nano that this block will send to a recipient account. + * Sets the `signature` property of the block to a precalculated value. * - * @param {number} amount - Amount to send to recipient in nano (10³⁰ raw) - * @returns {Block} This block so that additional calls can be chained + * @param {string} [key] - 64-byte hexadecimal signature */ - send (amount: number): Block - /** - * Set the amount of nano that this block will send to a recipient account. - * - * @param {string} amount - Amount to send to recipient in raw - * @returns {Block} This block so that additional calls can be chained - */ - send (amount: string): Block + sign (signature: string): Block /** * Signs the block using a private key. If successful, the result is stored in - * the object's `signature` property. + * the `signature` property of the block. * - * @param {string} [key] - Hexadecimal-formatted private key to use for signing + * @param {string} [key] - 32-byte hexadecimal private key to use for signing */ sign (key: string): Promise /** * Signs the block using a Wallet. If successful, the result is stored in - * the object's `signature` property. The wallet must be unlocked prior to + * the `signature` property of the block. The wallet must be unlocked prior to * signing. * * @param {Wallet} wallet - Wallet to use for signing @@ -381,32 +329,15 @@ export declare class Block { sign (wallet: Wallet, index: number): Promise /** * Signs the block using a Ledger hardware wallet. If that fails, an error is - * thrown with the status code from the device. - * - * If successful, the result is stored in the object's `signature` - * property. + * thrown with the status code from the device. If successful, the result is + * stored in the `signature` property of the block. The wallet must be unlocked + * prior to signing. * * @param {number} index - Account index between 0x0 and 0x7fffffff * @param {object} [frontier] - JSON of frontier block for offline signing */ sign (index: number, frontier?: Block): Promise /** - * Set the recipient of a send block or the target representative of a change - * block. - * - * @param {string} account - Address or public key of Account to target - * @returns {Block} This block so that additional calls can be chained - */ - to (account: string): Block - /** - * Set the recipient of a send block or the target representative of a change - * block. - * - * @param {Account} account - Account to target - * @returns {Block} This block so that additional calls can be chained - */ - to (account: Account): Block - /** * Verifies the signature of the block. If a key is not provided, the public * key of the block's account will be used if it exists. *