]> git.codecow.com Git - libnemo.git/commitdiff
Fix Ledger sign implementation and block tests.
authorChris Duncan <chris@zoso.dev>
Wed, 6 Aug 2025 19:34:30 +0000 (12:34 -0700)
committerChris Duncan <chris@zoso.dev>
Wed, 6 Aug 2025 19:34:30 +0000 (12:34 -0700)
src/lib/ledger.ts

index 40aa05c1811ca4cab93bf4379808de0ac65614a8..f232a0811294f68e2e89d1a121dabc3fef44d135 100644 (file)
@@ -6,7 +6,7 @@ import { default as TransportBLE } from '@ledgerhq/hw-transport-web-ble'
 import { default as TransportUSB } from '@ledgerhq/hw-transport-webusb'\r
 import { default as TransportHID } from '@ledgerhq/hw-transport-webhid'\r
 import { Account, AccountList } from './account'\r
-import { ChangeBlock, ReceiveBlock, SendBlock } from './block'\r
+import { Block } from './block'\r
 import { BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET, LEDGER_ADPU_CODES, LEDGER_STATUS_CODES } from './constants'\r
 import { bytes, dec, hex } from './convert'\r
 import { Database } from './database'\r
@@ -253,7 +253,16 @@ export class Ledger extends Wallet {
        * @param {object} block - Block data to sign\r
        * @returns {Promise<string>} Signature\r
        */\r
-       async sign (index: number, block: SendBlock | ReceiveBlock | ChangeBlock, frontier?: SendBlock | ReceiveBlock | ChangeBlock): Promise<string> {\r
+       async sign (index: number, block: Block): Promise<Uint8Array<ArrayBuffer>>\r
+       /**\r
+       * Sign a block with the Ledger device.\r
+       *\r
+       * @param {number} index - Account number\r
+       * @param {object} block - Block data to sign\r
+       * @returns {Promise<string>} Signature\r
+       */\r
+       async sign (index: number, block: Block, format?: 'hex', frontier?: Block): Promise<string>\r
+       async sign (index: number, block: Block, format?: 'hex', frontier?: Block): Promise<string | Uint8Array<ArrayBuffer>> {\r
                if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) {\r
                        throw new TypeError('Invalid account index')\r
                }\r
@@ -296,7 +305,7 @@ export class Ledger extends Wallet {
        * @param {number} index - Account number\r
        * @param {object} block - JSON-formatted block data\r
        */\r
-       async updateCache (index: number, block: ChangeBlock | ReceiveBlock | SendBlock): Promise<LedgerResponse>\r
+       async updateCache (index: number, block: Block): Promise<LedgerResponse>\r
        /**\r
        * Update cache from a block hash by calling out to a node. Suitable for online\r
        * use only.\r
@@ -346,7 +355,7 @@ export class Ledger extends Wallet {
                const testWallet = await Wallet.import('BIP-44', '', secret)\r
                await testWallet.unlock('')\r
                const testAccount = await testWallet.account(0)\r
-               const testOpenBlock = new ReceiveBlock(\r
+               const testOpenBlock = new Block(\r
                        testAccount.address,\r
                        '0',\r
                        testAccount.address,\r
@@ -355,17 +364,12 @@ export class Ledger extends Wallet {
                        '0'\r
                )\r
                await testWallet.sign(0, testOpenBlock)\r
-               const testSendBlock = new SendBlock(\r
-                       testAccount.address,\r
-                       '0',\r
-                       testAccount.address,\r
-                       '0',\r
-                       testAccount.address,\r
-                       testOpenBlock.hash\r
-               )\r
-               const testSignature = await testWallet.sign(0, testOpenBlock)\r
+               const testSendBlock = new Block(testAccount.address, '0', bytes.toHex(testOpenBlock.hash), testAccount.address)\r
+                       .send(0)\r
+                       .to(testAccount.address)\r
+               const testSignature = await testWallet.sign(0, testOpenBlock, 'hex')\r
                try {\r
-                       const signature = await this.sign(0, testSendBlock, testOpenBlock)\r
+                       const signature = await this.sign(0, testSendBlock, 'hex', testOpenBlock)\r
                        return signature === testSignature\r
                } catch (err) {\r
                        throw new Error('Failed to verify wallet', { cause: err })\r
@@ -471,13 +475,16 @@ export class Ledger extends Wallet {
        * @param {any} block - Block data to cache\r
        * @returns Status of command\r
        */\r
-       async #cacheBlock (index: number = 0, block: ChangeBlock | ReceiveBlock | SendBlock): Promise<LedgerResponse> {\r
+       async #cacheBlock (index: number = 0, block: Block): Promise<LedgerResponse> {\r
                if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) {\r
                        throw new TypeError('Invalid account index')\r
                }\r
-               if (!(block instanceof ChangeBlock) && !(block instanceof ReceiveBlock) && !(block instanceof SendBlock)) {\r
+               if (!(block instanceof Block)) {\r
                        throw new TypeError('Invalid block format')\r
                }\r
+               if (!(block.link instanceof Uint8Array)) {\r
+                       throw new TypeError('Invalid block link')\r
+               }\r
                if (!block.signature) {\r
                        throw new ReferenceError('Cannot cache unsigned block')\r
                }\r
@@ -486,7 +493,7 @@ export class Ledger extends Wallet {
                const coin = dec.toBytes(BIP44_COIN_NANO + HARDENED_OFFSET, 4)\r
                const account = dec.toBytes(index + HARDENED_OFFSET, 4)\r
                const previous = hex.toBytes(block.previous === block.account.publicKey ? '0' : block.previous, 32)\r
-               const link = hex.toBytes(block.link, 32)\r
+               const link = block.link\r
                const representative = hex.toBytes(block.representative.publicKey, 32)\r
                const balance = hex.toBytes(block.balance.toString(16), 16)\r
                const signature = hex.toBytes(block.signature, 64)\r
@@ -509,20 +516,24 @@ export class Ledger extends Wallet {
        * @param {object} block - Block data to sign\r
        * @returns {Promise} Status, signature, and block hash\r
        */\r
-       async #signBlock (index: number, block: SendBlock | ReceiveBlock | ChangeBlock): Promise<LedgerSignResponse> {\r
+       async #signBlock (index: number, block: Block): Promise<LedgerSignResponse> {\r
                if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) {\r
                        throw new TypeError('Invalid account index')\r
                }\r
+               if (!(block.link instanceof Uint8Array)) {\r
+                       throw new TypeError('Invalid block link')\r
+               }\r
 \r
                const account = dec.toBytes(index + HARDENED_OFFSET, 4)\r
                const previous = hex.toBytes(block.previous, 32)\r
-               const link = hex.toBytes(block.link, 32)\r
+               const link = block.link\r
                const representative = hex.toBytes(block.representative.publicKey, 32)\r
                const balance = hex.toBytes(BigInt(block.balance).toString(16), 16)\r
                const data = new Uint8Array([...Ledger.#derivationPath, ...account, ...previous, ...link, ...representative, ...balance])\r
 \r
                const transport = await Ledger.DynamicTransport.create(Ledger.openTimeout, Ledger.listenTimeout)\r
-               const response = await transport.send(LEDGER_ADPU_CODES.class, LEDGER_ADPU_CODES.signBlock, LEDGER_ADPU_CODES.paramUnused, LEDGER_ADPU_CODES.paramUnused, data as Buffer)\r
+               const response = await transport\r
+                       .send(LEDGER_ADPU_CODES.class, LEDGER_ADPU_CODES.signBlock, LEDGER_ADPU_CODES.paramUnused, LEDGER_ADPU_CODES.paramUnused, data as Buffer)\r
                        .catch(err => dec.toBytes(err.statusCode)) as Uint8Array\r
                await transport.close()\r
 \r
@@ -533,7 +544,7 @@ export class Ledger extends Wallet {
                        return { status, signature: null }\r
                }\r
                if (response.byteLength === 98) {\r
-                       const hash = bytes.toHex(response.slice(0, 32))\r
+                       const hash = response.slice(0, 32)\r
                        const signature = bytes.toHex(response.slice(32, 96))\r
                        return { status, signature, hash }\r
                }\r