From 0096a2cd3d24e89d4bbb53da8b0e7c47abd026e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= <104785785+sprohaszka-ledger@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:59:14 +0100 Subject: [PATCH] [SWAP] Add new functions for trusted service integration (#8440) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(exch): add new functions for trusted service integration Signed-off-by: Stéphane Prohaszka * feat: add pki certificate apdu for app-exchange Signed-off-by: Stéphane Prohaszka * feat: remove unused apdu Signed-off-by: Stéphane Prohaszka --------- Signed-off-by: Stéphane Prohaszka --- .../hw-app-exchange/src/Exchange.test.ts | 130 +++++++++++++----- .../packages/hw-app-exchange/src/Exchange.ts | 27 ++++ .../hw-transport-mocker/src/MockTransport.ts | 2 + .../hw-transport-mocker/src/RecordStore.ts | 5 + 4 files changed, 129 insertions(+), 35 deletions(-) diff --git a/libs/ledgerjs/packages/hw-app-exchange/src/Exchange.test.ts b/libs/ledgerjs/packages/hw-app-exchange/src/Exchange.test.ts index ae29a93fa363..5ebab9c6edbd 100644 --- a/libs/ledgerjs/packages/hw-app-exchange/src/Exchange.test.ts +++ b/libs/ledgerjs/packages/hw-app-exchange/src/Exchange.test.ts @@ -7,7 +7,7 @@ import { import Exchange, { createExchange, ExchangeTypes, RateTypes } from "./Exchange"; import BigNumber from "bignumber.js"; -describe("Contrustructor", () => { +describe("Constructor", () => { [ExchangeTypes.Fund, ExchangeTypes.Sell, ExchangeTypes.Swap].forEach(exchangeType => { it(`Exchange (value of ${exchangeType}) init with default rate types of fixed`, async () => { const transport = await openTransportReplayer(RecordStore.fromString("")); @@ -27,21 +27,22 @@ describe("Contrustructor", () => { describe("startNewTransaction", () => { const recordStore = new RecordStore(); + const mockTransport = new MockTransport(Buffer.from([0, 0x90, 0x00])); + const transport = createTransportRecorder(mockTransport, recordStore); - afterEach(() => { - recordStore.queue = []; + beforeEach(() => { + recordStore.clearStore(); }); it("sends a correct sequence of APDU when Swap", async () => { // Given const mockNonceResponse = "2ac38f187c"; // Response of length 10 - const mockTransport = new MockTransport( + mockTransport.setNewResponse( Buffer.concat([ Buffer.from(mockNonceResponse), Buffer.from([0x90, 0x00]), // StatusCodes.OK ]), ); - const transport = createTransportRecorder(mockTransport, recordStore); const exchange = new Exchange(new transport(mockTransport), ExchangeTypes.Swap); // When @@ -66,13 +67,12 @@ describe("startNewTransaction", () => { ])("returns a base64 nonce when $name", async ({ name, type }) => { // Given const mockNonceResponse = "2ac38f187c2ac38f187c2ac38f187c7c"; // Response of length 10 - const mockTransport = new MockTransport( + mockTransport.setNewResponse( Buffer.concat([ Buffer.from(mockNonceResponse), Buffer.from([0x90, 0x00]), // StatusCodes.OK ]), ); - const transport = createTransportRecorder(mockTransport, recordStore); const exchange = new Exchange(new transport(mockTransport), type); // When @@ -99,13 +99,12 @@ describe("startNewTransaction", () => { ])("returns a 32 bytes nonce when $name", async ({ name, type }) => { // Given const mockNonceResponse = Buffer.from("2ac38f187c2ac38f187c2ac38f187c7c"); // Response of 32 bytes - const mockTransport = new MockTransport( + mockTransport.setNewResponse( Buffer.concat([ mockNonceResponse, Buffer.from([0x90, 0x00]), // StatusCodes.OK ]), ); - const transport = createTransportRecorder(mockTransport, recordStore); const exchange = new Exchange(new transport(mockTransport), ExchangeTypes.SwapNg); // When @@ -123,27 +122,30 @@ describe("startNewTransaction", () => { expect(result).toEqual(mockNonceResponse.toString("hex")); }); - // TOFIX: TransportError is not recognize as an Error type - xit("throws an error if status is not ok", async () => { + it("throws an error if status is not ok", async () => { // Given const mockResponse = "2ac38f187c"; // Response of length 10 - const mockTransport = new MockTransport( + mockTransport.setNewResponse( Buffer.concat([Buffer.from(mockResponse), Buffer.from([0x6a, 0x80])]), ); - const transport = createTransportRecorder(mockTransport, recordStore); const exchange = new Exchange(new transport(mockTransport), ExchangeTypes.Swap); // When & Then - expect(async () => await exchange.startNewTransaction()).toThrowError(); + await expect(exchange.startNewTransaction()).rejects.toThrowError(); }); }); describe("setPartnerKey", () => { + const recordStore = new RecordStore(); + const mockTransport = new MockTransport(Buffer.from([0, 0x90, 0x00])); + const transport = createTransportRecorder(mockTransport, recordStore); + + beforeEach(() => { + recordStore.clearStore(); + }); + it("sends legacy info", async () => { // Given - const recordStore = new RecordStore(); - const mockTransport = new MockTransport(Buffer.from([0, 0x90, 0x00])); - const transport = createTransportRecorder(mockTransport, recordStore); const exchange = new Exchange(new transport(mockTransport), ExchangeTypes.Swap); const info = { name: "LTX", @@ -166,9 +168,6 @@ describe("setPartnerKey", () => { it("sends NG info", async () => { // Given - const recordStore = new RecordStore(); - const mockTransport = new MockTransport(Buffer.from([0, 0x90, 0x00])); - const transport = createTransportRecorder(mockTransport, recordStore); const exchange = createExchange(new transport(mockTransport), ExchangeTypes.SwapNg); const info = { name: "LTX", @@ -191,11 +190,16 @@ describe("setPartnerKey", () => { }); describe("processTransaction", () => { + const recordStore = new RecordStore(); + const mockTransport = new MockTransport(Buffer.from([0, 0x90, 0x00])); + const transport = createTransportRecorder(mockTransport, recordStore); + + beforeEach(() => { + recordStore.clearStore(); + }); + it("sends legacy info", async () => { // Given - const recordStore = new RecordStore(); - const mockTransport = new MockTransport(Buffer.from([0, 0x90, 0x00])); - const transport = createTransportRecorder(mockTransport, recordStore); const exchange = new Exchange(new transport(mockTransport), ExchangeTypes.Swap); const tx = Buffer.from("1234567890abcdef", "hex"); @@ -215,9 +219,6 @@ describe("processTransaction", () => { it("sends NG info", async () => { // Given - const recordStore = new RecordStore(); - const mockTransport = new MockTransport(Buffer.from([0, 0x90, 0x00])); - const transport = createTransportRecorder(mockTransport, recordStore); const exchange = createExchange(new transport(mockTransport), ExchangeTypes.SwapNg); const tx = Buffer.from("1234567890abcdef", "hex"); @@ -237,9 +238,6 @@ describe("processTransaction", () => { it("sends NG info of larger than 255 bytes", async () => { // Given - const recordStore = new RecordStore(); - const mockTransport = new MockTransport(Buffer.from([0, 0x90, 0x00])); - const transport = createTransportRecorder(mockTransport, recordStore); const exchange = createExchange(new transport(mockTransport), ExchangeTypes.SwapNg); const value = Array(200).fill("abc").join(""); @@ -278,11 +276,16 @@ describe("processTransaction", () => { }); describe("checkTransactionSignature", () => { + const recordStore = new RecordStore(); + const mockTransport = new MockTransport(Buffer.from([0, 0x90, 0x00])); + const transport = createTransportRecorder(mockTransport, recordStore); + + beforeEach(() => { + recordStore.clearStore(); + }); + it("sends legacy info", async () => { // Given - const recordStore = new RecordStore(); - const mockTransport = new MockTransport(Buffer.from([0, 0x90, 0x00])); - const transport = createTransportRecorder(mockTransport, recordStore); const exchange = new Exchange(new transport(mockTransport), ExchangeTypes.Swap); const txSig = Buffer.from("1234567890abcdef", "hex"); @@ -300,9 +303,6 @@ describe("checkTransactionSignature", () => { it("sends NG info", async () => { // Given - const recordStore = new RecordStore(); - const mockTransport = new MockTransport(Buffer.from([0, 0x90, 0x00])); - const transport = createTransportRecorder(mockTransport, recordStore); const exchange = createExchange(new transport(mockTransport), ExchangeTypes.SwapNg); const txSig = Buffer.from("1234567890abcdef", "hex"); @@ -318,3 +318,63 @@ describe("checkTransactionSignature", () => { expect(recordStore.queue[0][0]).toBe(expectCommand + hexEncodedInfoExpected); }); }); + +describe("getChallenge", () => { + const recordStore = new RecordStore(); + const mockTransport = new MockTransport(Buffer.from([0, 0x90, 0x00])); + const transport = createTransportRecorder(mockTransport, recordStore); + + it("returns a new Challenge value", async () => { + // Given + const mockNonceResponse = Buffer.from([0xff, 0xff, 0xff, 0xff]); // Response of 32 bytes + mockTransport.setNewResponse( + Buffer.concat([ + mockNonceResponse, + Buffer.from([0x90, 0x00]), // StatusCodes.OK + ]), + ); + const exchange = createExchange(new transport(mockTransport), ExchangeTypes.SwapNg); + + // When + const result = await exchange.getChallenge(); + + // Then + const expectCommand = Buffer.from([ + 0xe0, + 0x10, + RateTypes.Fixed, + ExchangeTypes.SwapNg, + 0x00, // Data + ]).toString("hex"); + expect(recordStore.queue[0][0]).toBe(expectCommand); + expect(result).toEqual(4_294_967_295); + }); +}); + +describe("sendTrustedDescriptor", () => { + const recordStore = new RecordStore(); + const mockTransport = new MockTransport(Buffer.from([0, 0x90, 0x00])); + const transport = createTransportRecorder(mockTransport, recordStore); + + beforeEach(() => { + recordStore.clearStore(); + }); + + it("sends the expected command", async () => { + // Given + const descriptor = Buffer.from([0x00, 0x0a, 0xff]); + const descriptorLengthInHex = "03"; + const exchange = new Exchange(new transport(mockTransport), ExchangeTypes.Swap); + + // When + await exchange.sendTrustedDescriptor(descriptor); + + // Then + const expectCommand = Buffer.from([0xe0, 0x11, RateTypes.Fixed, ExchangeTypes.Swap]).toString( + "hex", + ); + expect(recordStore.queue[0][0]).toBe( + expectCommand + descriptorLengthInHex + descriptor.toString("hex"), + ); + }); +}); diff --git a/libs/ledgerjs/packages/hw-app-exchange/src/Exchange.ts b/libs/ledgerjs/packages/hw-app-exchange/src/Exchange.ts index b8b452f1e4b5..dd891e6b23ee 100644 --- a/libs/ledgerjs/packages/hw-app-exchange/src/Exchange.ts +++ b/libs/ledgerjs/packages/hw-app-exchange/src/Exchange.ts @@ -35,6 +35,8 @@ const CHECK_REFUND_ADDRESS = 0x09; // const CHECK_REFUND_ADDRESS_NO_DISPLAY = 0x0c; const SIGN_COIN_TRANSACTION = 0x0a; const CHECK_ASSET_IN_AND_DISPLAY = 0x0b; +const GET_CHALLENGE = 0x10; +const SEND_TRUSTED_NAME_DESCRIPTOR = 0x11; // Extension for PROCESS_TRANSACTION_RESPONSE APDU const P2_NONE = 0x00 << 4; @@ -334,6 +336,31 @@ export default class Exchange { maybeThrowProtocolError(result); } + async getChallenge(): Promise { + const result: Buffer = await this.transport.send( + 0xe0, + GET_CHALLENGE, + this.transactionRate, + this.transactionType, + Buffer.alloc(0), + this.allowedStatuses, + ); + maybeThrowProtocolError(result); + return result.slice(0, 4).readUInt32BE(); + } + + async sendTrustedDescriptor(buffer: Buffer): Promise { + const result: Buffer = await this.transport.send( + 0xe0, + SEND_TRUSTED_NAME_DESCRIPTOR, + this.transactionRate, + this.transactionType, + buffer, + this.allowedStatuses, + ); + maybeThrowProtocolError(result); + } + private getPartnerKeyInfo({ name, curve, publicKey }: PartnerKeyInfo): Buffer { if (this.isExchangeTypeNg()) { return Buffer.concat([ diff --git a/libs/ledgerjs/packages/hw-transport-mocker/src/MockTransport.ts b/libs/ledgerjs/packages/hw-transport-mocker/src/MockTransport.ts index 62cae61ef4ff..20026710ac52 100644 --- a/libs/ledgerjs/packages/hw-transport-mocker/src/MockTransport.ts +++ b/libs/ledgerjs/packages/hw-transport-mocker/src/MockTransport.ts @@ -11,4 +11,6 @@ export default class MockTransport extends Transport { exchange(_apdu: Buffer): Promise { return Promise.resolve(this.preRecordResponse); } + + setNewResponse = (buffer: Buffer) => (this.preRecordResponse = buffer); } diff --git a/libs/ledgerjs/packages/hw-transport-mocker/src/RecordStore.ts b/libs/ledgerjs/packages/hw-transport-mocker/src/RecordStore.ts index 958f6186c60b..4e9e0be68fc1 100644 --- a/libs/ledgerjs/packages/hw-transport-mocker/src/RecordStore.ts +++ b/libs/ledgerjs/packages/hw-transport-mocker/src/RecordStore.ts @@ -81,6 +81,11 @@ export class RecordStore { */ isEmpty = (): boolean => this.queue.length === 0; + /** + * Clear store history + */ + clearStore = () => (this.queue = []); + /** * Record an APDU (used by createTransportRecorder) * @param {Buffer} apdu input