From 9cd56f73427a80e022718e161e5bb172d29fe27f Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Wed, 6 Aug 2025 12:34:30 -0700 Subject: [PATCH] Fix Ledger sign implementation and block tests. --- src/lib/ledger.ts | 53 ++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/src/lib/ledger.ts b/src/lib/ledger.ts index 40aa05c..f232a08 100644 --- a/src/lib/ledger.ts +++ b/src/lib/ledger.ts @@ -6,7 +6,7 @@ import { default as TransportBLE } from '@ledgerhq/hw-transport-web-ble' import { default as TransportUSB } from '@ledgerhq/hw-transport-webusb' import { default as TransportHID } from '@ledgerhq/hw-transport-webhid' import { Account, AccountList } from './account' -import { ChangeBlock, ReceiveBlock, SendBlock } from './block' +import { Block } from './block' import { BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET, LEDGER_ADPU_CODES, LEDGER_STATUS_CODES } from './constants' import { bytes, dec, hex } from './convert' import { Database } from './database' @@ -253,7 +253,16 @@ export class Ledger extends Wallet { * @param {object} block - Block data to sign * @returns {Promise} Signature */ - async sign (index: number, block: SendBlock | ReceiveBlock | ChangeBlock, frontier?: SendBlock | ReceiveBlock | ChangeBlock): Promise { + async sign (index: number, block: Block): Promise> + /** + * Sign a block with the Ledger device. + * + * @param {number} index - Account number + * @param {object} block - Block data to sign + * @returns {Promise} Signature + */ + async sign (index: number, block: Block, format?: 'hex', frontier?: Block): Promise + async sign (index: number, block: Block, format?: 'hex', frontier?: Block): Promise> { if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) { throw new TypeError('Invalid account index') } @@ -296,7 +305,7 @@ export class Ledger extends Wallet { * @param {number} index - Account number * @param {object} block - JSON-formatted block data */ - async updateCache (index: number, block: ChangeBlock | ReceiveBlock | SendBlock): Promise + async updateCache (index: number, block: Block): Promise /** * Update cache from a block hash by calling out to a node. Suitable for online * use only. @@ -346,7 +355,7 @@ 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 ReceiveBlock( + const testOpenBlock = new Block( testAccount.address, '0', testAccount.address, @@ -355,17 +364,12 @@ export class Ledger extends Wallet { '0' ) await testWallet.sign(0, testOpenBlock) - const testSendBlock = new SendBlock( - testAccount.address, - '0', - testAccount.address, - '0', - testAccount.address, - testOpenBlock.hash - ) - const testSignature = await testWallet.sign(0, testOpenBlock) + const testSendBlock = new Block(testAccount.address, '0', bytes.toHex(testOpenBlock.hash), testAccount.address) + .send(0) + .to(testAccount.address) + const testSignature = await testWallet.sign(0, testOpenBlock, 'hex') try { - const signature = await this.sign(0, testSendBlock, testOpenBlock) + const signature = await this.sign(0, testSendBlock, 'hex', testOpenBlock) return signature === testSignature } catch (err) { throw new Error('Failed to verify wallet', { cause: err }) @@ -471,13 +475,16 @@ export class Ledger extends Wallet { * @param {any} block - Block data to cache * @returns Status of command */ - async #cacheBlock (index: number = 0, block: ChangeBlock | ReceiveBlock | SendBlock): Promise { + async #cacheBlock (index: number = 0, block: Block): Promise { if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) { throw new TypeError('Invalid account index') } - if (!(block instanceof ChangeBlock) && !(block instanceof ReceiveBlock) && !(block instanceof SendBlock)) { + if (!(block instanceof Block)) { throw new TypeError('Invalid block format') } + if (!(block.link instanceof Uint8Array)) { + throw new TypeError('Invalid block link') + } if (!block.signature) { throw new ReferenceError('Cannot cache unsigned block') } @@ -486,7 +493,7 @@ export class Ledger extends Wallet { const coin = dec.toBytes(BIP44_COIN_NANO + HARDENED_OFFSET, 4) const account = dec.toBytes(index + HARDENED_OFFSET, 4) const previous = hex.toBytes(block.previous === block.account.publicKey ? '0' : block.previous, 32) - const link = hex.toBytes(block.link, 32) + const link = block.link const representative = hex.toBytes(block.representative.publicKey, 32) const balance = hex.toBytes(block.balance.toString(16), 16) const signature = hex.toBytes(block.signature, 64) @@ -509,20 +516,24 @@ export class Ledger extends Wallet { * @param {object} block - Block data to sign * @returns {Promise} Status, signature, and block hash */ - async #signBlock (index: number, block: SendBlock | ReceiveBlock | ChangeBlock): Promise { + async #signBlock (index: number, block: Block): Promise { if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) { throw new TypeError('Invalid account index') } + if (!(block.link instanceof Uint8Array)) { + throw new TypeError('Invalid block link') + } const account = dec.toBytes(index + HARDENED_OFFSET, 4) const previous = hex.toBytes(block.previous, 32) - const link = hex.toBytes(block.link, 32) + const link = block.link const representative = hex.toBytes(block.representative.publicKey, 32) const balance = hex.toBytes(BigInt(block.balance).toString(16), 16) const data = new Uint8Array([...Ledger.#derivationPath, ...account, ...previous, ...link, ...representative, ...balance]) const transport = await Ledger.DynamicTransport.create(Ledger.openTimeout, Ledger.listenTimeout) - const response = await transport.send(LEDGER_ADPU_CODES.class, LEDGER_ADPU_CODES.signBlock, LEDGER_ADPU_CODES.paramUnused, LEDGER_ADPU_CODES.paramUnused, data as Buffer) + const response = await transport + .send(LEDGER_ADPU_CODES.class, LEDGER_ADPU_CODES.signBlock, LEDGER_ADPU_CODES.paramUnused, LEDGER_ADPU_CODES.paramUnused, data as Buffer) .catch(err => dec.toBytes(err.statusCode)) as Uint8Array await transport.close() @@ -533,7 +544,7 @@ export class Ledger extends Wallet { return { status, signature: null } } if (response.byteLength === 98) { - const hash = bytes.toHex(response.slice(0, 32)) + const hash = response.slice(0, 32) const signature = bytes.toHex(response.slice(32, 96)) return { status, signature, hash } } -- 2.47.3