From c56a35ca87ce1420019897009527e674d5f10595 Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Fri, 29 Nov 2024 10:31:44 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=A9=B9=20(signer-eth):=20StartTransac?= =?UTF-8?q?tion=20cmd=20should=20contain=20a=20signature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/app-binder/command/StartTransactionCommand.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/signer/signer-eth/src/internal/app-binder/command/StartTransactionCommand.ts b/packages/signer/signer-eth/src/internal/app-binder/command/StartTransactionCommand.ts index 42ed2ad32..6458824e1 100644 --- a/packages/signer/signer-eth/src/internal/app-binder/command/StartTransactionCommand.ts +++ b/packages/signer/signer-eth/src/internal/app-binder/command/StartTransactionCommand.ts @@ -13,7 +13,7 @@ import { GlobalCommandErrorHandler, InvalidStatusWordError, } from "@ledgerhq/device-management-kit"; -import { Just, Nothing } from "purify-ts"; +import { Just } from "purify-ts"; import { type SignTransactionCommandResponse } from "./SignTransactionCommand"; @@ -53,7 +53,9 @@ export class StartTransactionCommand // The data is returned only for the last chunk const v = parser.extract8BitUInt(); if (v === undefined) { - return CommandResultFactory({ data: Nothing }); + return CommandResultFactory({ + error: new InvalidStatusWordError("V is missing"), + }); } const r = parser.encodeToHexaString( From b7e36da883228af58cedaeb26588e261d6480246 Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Mon, 2 Dec 2024 09:20:11 +0100 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20(signer-eth):=20Update?= =?UTF-8?q?=20error=20message=20for=20invalid=20chaincode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/internal/app-binder/command/GetAddressCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/signer/signer-eth/src/internal/app-binder/command/GetAddressCommand.ts b/packages/signer/signer-eth/src/internal/app-binder/command/GetAddressCommand.ts index 9f228c4ec..54cee1001 100644 --- a/packages/signer/signer-eth/src/internal/app-binder/command/GetAddressCommand.ts +++ b/packages/signer/signer-eth/src/internal/app-binder/command/GetAddressCommand.ts @@ -111,7 +111,7 @@ export class GetAddressCommand if (this.args.returnChainCode) { if (parser.testMinimalLength(CHAIN_CODE_LENGTH) === false) { return CommandResultFactory({ - error: new InvalidStatusWordError("Invalid Ethereum address"), + error: new InvalidStatusWordError("Invalid Chaincode"), }); } From 3dc67c91e56436bc7a01e4263c587fd36c4d2a53 Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Mon, 2 Dec 2024 10:08:36 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=85=20(signer-eth):=20Add=20missing?= =?UTF-8?q?=20TU=20for=20generic=20parser?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/GetAddressCommand.test.ts | 140 +++++++++++++++++- .../command/ProvideEnumCommand.test.ts | 88 ++++++++++- ...TransactionFieldDescriptionCommand.test.ts | 92 +++++++++++- ...ovideTransactionInformationCommand.test.ts | 92 +++++++++++- .../command/StartTransactionCommand.test.ts | 134 +++++++++++++++++ .../command/StoreTransactionCommand.test.ts | 94 ++++++++++++ .../shared/utils/PayloadUtils.test.ts | 34 +++++ 7 files changed, 654 insertions(+), 20 deletions(-) create mode 100644 packages/signer/signer-eth/src/internal/app-binder/command/StartTransactionCommand.test.ts create mode 100644 packages/signer/signer-eth/src/internal/app-binder/command/StoreTransactionCommand.test.ts create mode 100644 packages/signer/signer-eth/src/internal/shared/utils/PayloadUtils.test.ts diff --git a/packages/signer/signer-eth/src/internal/app-binder/command/GetAddressCommand.test.ts b/packages/signer/signer-eth/src/internal/app-binder/command/GetAddressCommand.test.ts index 2436df7a8..bca51efe7 100644 --- a/packages/signer/signer-eth/src/internal/app-binder/command/GetAddressCommand.test.ts +++ b/packages/signer/signer-eth/src/internal/app-binder/command/GetAddressCommand.test.ts @@ -1,6 +1,7 @@ import { ApduResponse, CommandResultFactory, + InvalidStatusWordError, isSuccessCommandResult, } from "@ledgerhq/device-management-kit"; @@ -183,8 +184,8 @@ describe("GetAddressCommand", () => { ); }); - describe("error handling", () => { - it("should return an error if the response is not successfull", () => { + describe("should return an error", () => { + it("when the response is not successfull", () => { const response = new ApduResponse({ statusCode: Uint8Array.from([0x6d, 0x00]), data: new Uint8Array(0), @@ -192,6 +193,141 @@ describe("GetAddressCommand", () => { const result = command.parseResponse(response); expect(isSuccessCommandResult(result)).toBe(false); }); + + it("when publicKeyLength is invalid", () => { + // GIVEN + const response = { + data: Uint8Array.from([]), // Invalid public key length + statusCode: Uint8Array.from([0x90, 0x00]), // Success status code + }; + + // WHEN + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + fail("Expected an error"); + } else { + expect(result.error).toBeInstanceOf(InvalidStatusWordError); + expect(result.error.originalError).toEqual( + new Error("Public key length is missing"), + ); + } + }); + + it("when publicKey is invalid", () => { + // GIVEN + const response = { + data: Uint8Array.from([0x01]), // Invalid public key + statusCode: Uint8Array.from([0x90, 0x00]), // Success status code + }; + + // WHEN + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + fail("Expected an error"); + } else { + expect(result.error).toBeInstanceOf(InvalidStatusWordError); + expect(result.error.originalError).toEqual( + new Error("Public key is missing"), + ); + } + }); + + it("when addressLength is invalid", () => { + // GIVEN + const response = { + data: Uint8Array.from([0x20, ...Array(32).fill(0x02)]), // Invalid address length + statusCode: Uint8Array.from([0x90, 0x00]), // Success status code + }; + + // WHEN + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + fail("Expected an error"); + } else { + expect(result.error).toBeInstanceOf(InvalidStatusWordError); + expect(result.error.originalError).toEqual( + new Error("Ethereum address length is missing"), + ); + } + }); + + it("when address is missing", () => { + // GIVEN + const response = { + data: Uint8Array.from([0x20, ...Array(32).fill(0x02), 0x01]), // Invalid address + statusCode: Uint8Array.from([0x90, 0x00]), // Success status code + }; + + // WHEN + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + fail("Expected an error"); + } else { + expect(result.error).toBeInstanceOf(InvalidStatusWordError); + expect(result.error.originalError).toEqual( + new Error("Ethereum address is missing"), + ); + } + }); + + it("when the address is invalid", () => { + // GIVEN + const response = { + data: Uint8Array.from([ + 0x20, + ...Array(32).fill(0x02), + 0x01, + 0x02, + ]), // Invalid address + statusCode: Uint8Array.from([0x90, 0x00]), // Success status code + }; + + // WHEN + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + fail("Expected an error"); + } else { + expect(result.error).toBeInstanceOf(InvalidStatusWordError); + expect(result.error.originalError).toEqual( + new Error("Invalid Ethereum address"), + ); + } + }); + + it("when chainCode is invalid", () => { + // GIVEN + const response = { + data: LNX_RESPONSE_DATA_GOOD_WITH_CHAIN_CODE.slice(0, -1), // Invalid chainCode + statusCode: Uint8Array.from([0x90, 0x00]), // Success status code + }; + + // WHEN + const commandWithChainCode = new GetAddressCommand({ + ...defaultArgs, + returnChainCode: true, + }); + const result = commandWithChainCode.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + fail("Expected an error"); + } else { + expect(result.error).toBeInstanceOf(InvalidStatusWordError); + expect(result.error.originalError).toEqual( + new Error("Invalid Chaincode"), + ); + } + }); }); }); }); diff --git a/packages/signer/signer-eth/src/internal/app-binder/command/ProvideEnumCommand.test.ts b/packages/signer/signer-eth/src/internal/app-binder/command/ProvideEnumCommand.test.ts index d2592e45a..fcd94addf 100644 --- a/packages/signer/signer-eth/src/internal/app-binder/command/ProvideEnumCommand.test.ts +++ b/packages/signer/signer-eth/src/internal/app-binder/command/ProvideEnumCommand.test.ts @@ -1,15 +1,91 @@ +import { + type ApduResponse, + isSuccessCommandResult, + UnknownDeviceExchangeError, +} from "@ledgerhq/device-management-kit"; + +import { + ProvideEnumCommand, + type ProvideEnumCommandArgs, +} from "./ProvideEnumCommand"; + describe("ProvideEnumCommand", () => { describe("getApdu", () => { - it("", () => { - // TODO: Implement test - expect(true).toBe(true); + it("should return the raw APDU for the first chunk", () => { + // GIVEN + const args: ProvideEnumCommandArgs = { + data: Uint8Array.from([0x01, 0x02, 0x03]), + isFirstChunk: true, + }; + + // WHEN + const command = new ProvideEnumCommand(args); + const apdu = command.getApdu(); + + // THEN + expect(apdu.getRawApdu()).toStrictEqual( + Uint8Array.from([0xe0, 0x24, 0x01, 0x00, 0x03, 0x01, 0x02, 0x03]), + ); + }); + + it("should return the raw APDU for the subsequent chunk", () => { + // GIVEN + const args: ProvideEnumCommandArgs = { + data: Uint8Array.from([0x04, 0x05, 0x06]), + isFirstChunk: false, + }; + + // WHEN + const command = new ProvideEnumCommand(args); + const apdu = command.getApdu(); + + // THEN + expect(apdu.getRawApdu()).toStrictEqual( + Uint8Array.from([0xe0, 0x24, 0x00, 0x00, 0x03, 0x04, 0x05, 0x06]), + ); }); }); describe("parseResponse", () => { - it("", () => { - // TODO: Implement test - expect(true).toBe(true); + it("should return an error if the response status code is invalid", () => { + // GIVEN + const response: ApduResponse = { + data: Uint8Array.from([]), + statusCode: Uint8Array.from([0x6d, 0x00]), // Invalid status code + }; + + // WHEN + const command = new ProvideEnumCommand({ + data: new Uint8Array(0), + isFirstChunk: true, + }); + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + throw new Error("Expected an error"); + } else { + expect(result.error).toBeDefined(); + expect(result.error).toBeInstanceOf(UnknownDeviceExchangeError); + } + }); + + it("should return a success result if the response status code is valid", () => { + // GIVEN + const response: ApduResponse = { + data: Uint8Array.from([]), + statusCode: Uint8Array.from([0x90, 0x00]), // Success status code + }; + + // WHEN + const command = new ProvideEnumCommand({ + data: new Uint8Array(0), + isFirstChunk: true, + }); + const result = command.parseResponse(response); + + // THEN + expect(isSuccessCommandResult(result)).toBe(true); }); }); }); diff --git a/packages/signer/signer-eth/src/internal/app-binder/command/ProvideTransactionFieldDescriptionCommand.test.ts b/packages/signer/signer-eth/src/internal/app-binder/command/ProvideTransactionFieldDescriptionCommand.test.ts index 38a7f1c86..37d350541 100644 --- a/packages/signer/signer-eth/src/internal/app-binder/command/ProvideTransactionFieldDescriptionCommand.test.ts +++ b/packages/signer/signer-eth/src/internal/app-binder/command/ProvideTransactionFieldDescriptionCommand.test.ts @@ -1,15 +1,95 @@ +import { + type ApduResponse, + isSuccessCommandResult, + UnknownDeviceExchangeError, +} from "@ledgerhq/device-management-kit"; + +import { + ProvideTransactionFieldDescriptionCommand, + type ProvideTransactionFieldDescriptionCommandArgs, +} from "./ProvideTransactionFieldDescriptionCommand"; + describe("ProvideTransactionFieldDescriptionCommand", () => { describe("getApdu", () => { - it("", () => { - // TODO: Implement test - expect(true).toBe(true); + it("should return the raw APDU for the first chunk", () => { + // GIVEN + const args: ProvideTransactionFieldDescriptionCommandArgs = { + data: Uint8Array.from([0x01, 0x02, 0x03]), + isFirstChunk: true, + }; + + // WHEN + const command = new ProvideTransactionFieldDescriptionCommand(args); + const apdu = command.getApdu(); + + // THEN + expect(apdu.getRawApdu()).toStrictEqual( + Uint8Array.from([0xe0, 0x28, 0x01, 0x00, 0x03, 0x01, 0x02, 0x03]), + ); + }); + + it("should return the raw APDU for the subsequent chunk", () => { + // GIVEN + const args: ProvideTransactionFieldDescriptionCommandArgs = { + data: Uint8Array.from([0x04, 0x05, 0x06]), + isFirstChunk: false, + }; + + // WHEN + const command = new ProvideTransactionFieldDescriptionCommand(args); + const apdu = command.getApdu(); + + // THEN + expect(apdu.getRawApdu()).toStrictEqual( + Uint8Array.from([0xe0, 0x28, 0x00, 0x00, 0x03, 0x04, 0x05, 0x06]), + ); }); }); describe("parseResponse", () => { - it("", () => { - // TODO: Implement test - expect(true).toBe(true); + it("should return an error if the response status code is invalid", () => { + // GIVEN + const response: ApduResponse = { + data: Uint8Array.from([]), + statusCode: Uint8Array.from([0x6d, 0x00]), // Invalid status code + }; + + // WHEN + const command = new ProvideTransactionFieldDescriptionCommand({ + data: new Uint8Array(0), + isFirstChunk: true, + }); + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + throw new Error("Expected an error"); + } else { + expect(result.error).toBeDefined(); + expect(result.error).toBeInstanceOf(UnknownDeviceExchangeError); + } + }); + + it("should return a success result if the response status code is valid", () => { + // GIVEN + const response: ApduResponse = { + data: Uint8Array.from([]), + statusCode: Uint8Array.from([0x90, 0x00]), // Success status code + }; + + // WHEN + const command = new ProvideTransactionFieldDescriptionCommand({ + data: new Uint8Array(0), + isFirstChunk: true, + }); + const result = command.parseResponse(response); + + // THEN + if (!isSuccessCommandResult(result)) { + throw new Error("Expected a success result"); + } else { + expect(result.data).toBeUndefined(); + } }); }); }); diff --git a/packages/signer/signer-eth/src/internal/app-binder/command/ProvideTransactionInformationCommand.test.ts b/packages/signer/signer-eth/src/internal/app-binder/command/ProvideTransactionInformationCommand.test.ts index f9c76db9b..518b34f1d 100644 --- a/packages/signer/signer-eth/src/internal/app-binder/command/ProvideTransactionInformationCommand.test.ts +++ b/packages/signer/signer-eth/src/internal/app-binder/command/ProvideTransactionInformationCommand.test.ts @@ -1,15 +1,95 @@ +import { + type ApduResponse, + isSuccessCommandResult, + UnknownDeviceExchangeError, +} from "@ledgerhq/device-management-kit"; + +import { + ProvideTransactionInformationCommand, + type ProvideTransactionInformationCommandArgs, +} from "./ProvideTransactionInformationCommand"; + describe("ProvideTransactionInformationCommand", () => { describe("getApdu", () => { - it("", () => { - // TODO: Implement test - expect(true).toBe(true); + it("should return the raw APDU for the first chunk", () => { + // GIVEN + const args: ProvideTransactionInformationCommandArgs = { + data: Uint8Array.from([0x01, 0x02, 0x03]), + isFirstChunk: true, + }; + + // WHEN + const command = new ProvideTransactionInformationCommand(args); + const apdu = command.getApdu(); + + // THEN + expect(apdu.getRawApdu()).toStrictEqual( + Uint8Array.from([0xe0, 0x26, 0x01, 0x00, 0x03, 0x01, 0x02, 0x03]), + ); + }); + + it("should return the raw APDU for the subsequent chunk", () => { + // GIVEN + const args: ProvideTransactionInformationCommandArgs = { + data: Uint8Array.from([0x04, 0x05, 0x06]), + isFirstChunk: false, + }; + + // WHEN + const command = new ProvideTransactionInformationCommand(args); + const apdu = command.getApdu(); + + // THEN + expect(apdu.getRawApdu()).toStrictEqual( + Uint8Array.from([0xe0, 0x26, 0x00, 0x00, 0x03, 0x04, 0x05, 0x06]), + ); }); }); describe("parseResponse", () => { - it("", () => { - // TODO: Implement test - expect(true).toBe(true); + it("should return an error if the response status code is invalid", () => { + // GIVEN + const response: ApduResponse = { + data: Uint8Array.from([]), + statusCode: Uint8Array.from([0x6d, 0x00]), // Invalid status code + }; + + // WHEN + const command = new ProvideTransactionInformationCommand({ + data: new Uint8Array(0), + isFirstChunk: true, + }); + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + throw new Error("Expected an error"); + } else { + expect(result.error).toBeDefined(); + expect(result.error).toBeInstanceOf(UnknownDeviceExchangeError); + } + }); + + it("should return a success result if the response status code is valid", () => { + // GIVEN + const response: ApduResponse = { + data: Uint8Array.from([]), + statusCode: Uint8Array.from([0x90, 0x00]), // Success status code + }; + + // WHEN + const command = new ProvideTransactionInformationCommand({ + data: new Uint8Array(0), + isFirstChunk: true, + }); + const result = command.parseResponse(response); + + // THEN + if (!isSuccessCommandResult(result)) { + throw new Error("Expected a success result"); + } else { + expect(result.data).toBeUndefined(); + } }); }); }); diff --git a/packages/signer/signer-eth/src/internal/app-binder/command/StartTransactionCommand.test.ts b/packages/signer/signer-eth/src/internal/app-binder/command/StartTransactionCommand.test.ts new file mode 100644 index 000000000..c8c664287 --- /dev/null +++ b/packages/signer/signer-eth/src/internal/app-binder/command/StartTransactionCommand.test.ts @@ -0,0 +1,134 @@ +import { + InvalidStatusWordError, + isSuccessCommandResult, + UnknownDeviceExchangeError, +} from "@ledgerhq/device-management-kit"; + +import { StartTransactionCommand } from "./StartTransactionCommand"; + +describe("StartTransactionCommand", () => { + describe("getApdu", () => { + it("should return the raw APDU", () => { + // WHEN + const command = new StartTransactionCommand(); + const apdu = command.getApdu(); + + // THEN + expect(apdu.getRawApdu()).toStrictEqual( + Uint8Array.from([0xe0, 0x04, 0x00, 0x02, 0x00]), + ); + }); + }); + + describe("parseResponse", () => { + it("should return an error if the response status code is invalid", () => { + // GIVEN + const response = { + data: Uint8Array.from([]), + statusCode: Uint8Array.from([0x6d, 0x00]), // Invalid status code + }; + + // WHEN + const command = new StartTransactionCommand(); + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + fail("Expected an error"); + } else { + expect(result.error).toBeInstanceOf(UnknownDeviceExchangeError); + } + }); + + it("should return the correct response", () => { + // GIVEN + const response = { + data: Uint8Array.from([ + 0x01, + ...Array(32).fill(0x02), + ...Array(32).fill(0x03), + ]), // Some data + statusCode: Uint8Array.from([0x90, 0x00]), // Success status code + }; + + // WHEN + const command = new StartTransactionCommand(); + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + expect(result.data.extract()).toEqual({ + r: "0x0202020202020202020202020202020202020202020202020202020202020202", + s: "0x0303030303030303030303030303030303030303030303030303030303030303", + v: 1, + }); + } else { + fail("Expected a success"); + } + }); + + it("should return an error if v is not valid", () => { + // GIVEN + const response = { + data: Uint8Array.from([]), // No data + statusCode: Uint8Array.from([0x90, 0x00]), // Success status code + }; + + // WHEN + const command = new StartTransactionCommand(); + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + fail("Expected an error"); + } else { + expect(result.error).toBeInstanceOf(InvalidStatusWordError); + expect(result.error.originalError).toEqual(new Error("V is missing")); + } + }); + + it("should return an error if r is not valid", () => { + // GIVEN + const response = { + data: Uint8Array.from([0x01, ...Array(20).fill(0x02)]), // Invalid r + statusCode: Uint8Array.from([0x90, 0x00]), // Success status code + }; + + // WHEN + const command = new StartTransactionCommand(); + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + fail("Expected an error"); + } else { + expect(result.error).toBeInstanceOf(InvalidStatusWordError); + expect(result.error.originalError).toEqual(new Error("R is missing")); + } + }); + + it("should return an error if s is not valid", () => { + // GIVEN + const response = { + data: Uint8Array.from([ + 0x01, + ...Array(32).fill(0x02), + ...Array(20).fill(0x02), + ]), // Invalid s + statusCode: Uint8Array.from([0x90, 0x00]), // Success status code + }; + + // WHEN + const command = new StartTransactionCommand(); + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + fail("Expected an error"); + } else { + expect(result.error).toBeInstanceOf(InvalidStatusWordError); + expect(result.error.originalError).toEqual(new Error("S is missing")); + } + }); + }); +}); diff --git a/packages/signer/signer-eth/src/internal/app-binder/command/StoreTransactionCommand.test.ts b/packages/signer/signer-eth/src/internal/app-binder/command/StoreTransactionCommand.test.ts new file mode 100644 index 000000000..4629eb1ee --- /dev/null +++ b/packages/signer/signer-eth/src/internal/app-binder/command/StoreTransactionCommand.test.ts @@ -0,0 +1,94 @@ +import { + type ApduResponse, + isSuccessCommandResult, + UnknownDeviceExchangeError, +} from "@ledgerhq/device-management-kit"; + +import { + StoreTransactionCommand, + type StoreTransactionCommandArgs, +} from "./StoreTransactionCommand"; + +describe("StoreTransactionCommand", () => { + describe("getApdu", () => { + it("should return the raw APDU for the first chunk", () => { + // GIVEN + const args: StoreTransactionCommandArgs = { + serializedTransaction: Uint8Array.from([0x01, 0x02, 0x03]), + isFirstChunk: true, + }; + + // WHEN + const command = new StoreTransactionCommand(args); + const apdu = command.getApdu(); + + // THEN + expect(apdu.getRawApdu()).toStrictEqual( + Uint8Array.from([0xe0, 0x04, 0x00, 0x01, 0x03, 0x01, 0x02, 0x03]), + ); + }); + + it("should return the raw APDU for the subsequent chunk", () => { + // GIVEN + const args: StoreTransactionCommandArgs = { + serializedTransaction: Uint8Array.from([0x04, 0x05, 0x06]), + isFirstChunk: false, + }; + + // WHEN + const command = new StoreTransactionCommand(args); + const apdu = command.getApdu(); + + // THEN + expect(apdu.getRawApdu()).toStrictEqual( + Uint8Array.from([0xe0, 0x04, 0x80, 0x01, 0x03, 0x04, 0x05, 0x06]), + ); + }); + }); + + describe("parseResponse", () => { + it("should return an error if the response status code is invalid", () => { + // GIVEN + const response: ApduResponse = { + data: Uint8Array.from([]), + statusCode: Uint8Array.from([0x6d, 0x00]), // Invalid status code + }; + + // WHEN + const command = new StoreTransactionCommand({ + serializedTransaction: new Uint8Array(0), + isFirstChunk: true, + }); + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + throw new Error("Expected error"); + } else { + expect(result.error).toBeInstanceOf(UnknownDeviceExchangeError); + } + }); + + it("should return the correct response", () => { + // GIVEN + const response: ApduResponse = { + data: Uint8Array.from([]), + statusCode: Uint8Array.from([0x90, 0x00]), // Success status code + }; + + // WHEN + const command = new StoreTransactionCommand({ + serializedTransaction: new Uint8Array(0), + isFirstChunk: true, + }); + const result = command.parseResponse(response); + + // THEN + if (isSuccessCommandResult(result)) { + expect(result.data).toBeUndefined(); + } else { + throw new Error("Expected success"); + } + }); + }); +}); diff --git a/packages/signer/signer-eth/src/internal/shared/utils/PayloadUtils.test.ts b/packages/signer/signer-eth/src/internal/shared/utils/PayloadUtils.test.ts new file mode 100644 index 000000000..625c1675b --- /dev/null +++ b/packages/signer/signer-eth/src/internal/shared/utils/PayloadUtils.test.ts @@ -0,0 +1,34 @@ +import { PayloadUtils } from "./PayloadUtils"; + +describe("PayloadUtils", () => { + describe("getBufferFromPayload", () => { + it("should return null if the payload is empty", () => { + // GIVEN + const payload = ""; + // WHEN + const buffer = PayloadUtils.getBufferFromPayload(payload); + // THEN + expect(buffer).toBeNull(); + }); + + it("should return null if the payload is invalid", () => { + // GIVEN + const payload = "invalid"; + // WHEN + const buffer = PayloadUtils.getBufferFromPayload(payload); + // THEN + expect(buffer).toBeNull(); + }); + + it("should return the buffer from the payload", () => { + // GIVEN + const payload = "010203"; + // WHEN + const buffer = PayloadUtils.getBufferFromPayload(payload); + // THEN + expect(buffer).toStrictEqual( + Uint8Array.from([0x00, 0x03, 0x01, 0x02, 0x03]), + ); + }); + }); +}); From ebd01e87731c8483d8ab934d8b591158644d25cf Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Mon, 2 Dec 2024 10:36:39 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=94=96=20(chore):=20Add=20changeset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/famous-stingrays-smash.md | 5 +++++ .changeset/great-hotels-boil.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/famous-stingrays-smash.md create mode 100644 .changeset/great-hotels-boil.md diff --git a/.changeset/famous-stingrays-smash.md b/.changeset/famous-stingrays-smash.md new file mode 100644 index 000000000..21a7a881a --- /dev/null +++ b/.changeset/famous-stingrays-smash.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/device-signer-kit-ethereum": patch +--- + +Return an error if StartTransaction cmd does not contain a signature diff --git a/.changeset/great-hotels-boil.md b/.changeset/great-hotels-boil.md new file mode 100644 index 000000000..fae6b8ecf --- /dev/null +++ b/.changeset/great-hotels-boil.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/device-signer-kit-ethereum": patch +--- + +Update error message for invalid chaincode