Skip to content

Commit

Permalink
[SWAP] Add new functions for trusted service integration (#8440)
Browse files Browse the repository at this point in the history
* feat(exch): add new functions for trusted service integration

Signed-off-by: Stéphane Prohaszka <[email protected]>

* feat: add pki certificate apdu for app-exchange

Signed-off-by: Stéphane Prohaszka <[email protected]>

* feat: remove unused apdu

Signed-off-by: Stéphane Prohaszka <[email protected]>

---------

Signed-off-by: Stéphane Prohaszka <[email protected]>
  • Loading branch information
sprohaszka-ledger authored Dec 3, 2024
1 parent 2177148 commit 0096a2c
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 35 deletions.
130 changes: 95 additions & 35 deletions libs/ledgerjs/packages/hw-app-exchange/src/Exchange.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(""));
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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");
Expand All @@ -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");
Expand All @@ -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("");
Expand Down Expand Up @@ -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");
Expand All @@ -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");
Expand All @@ -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"),
);
});
});
27 changes: 27 additions & 0 deletions libs/ledgerjs/packages/hw-app-exchange/src/Exchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -334,6 +336,31 @@ export default class Exchange {
maybeThrowProtocolError(result);
}

async getChallenge(): Promise<number> {
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<void> {
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([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ export default class MockTransport extends Transport {
exchange(_apdu: Buffer): Promise<Buffer> {
return Promise.resolve(this.preRecordResponse);
}

setNewResponse = (buffer: Buffer) => (this.preRecordResponse = buffer);
}
5 changes: 5 additions & 0 deletions libs/ledgerjs/packages/hw-transport-mocker/src/RecordStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 0096a2c

Please sign in to comment.