Skip to content

Commit

Permalink
✨ (signer-solana) [DSDK-553]: Create sign off chain message command (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
fAnselmi-Ledger authored Oct 29, 2024
2 parents c118d57 + 6394fc9 commit f3d03c8
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/tall-books-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/device-signer-kit-solana": patch
---

Added SignOffChainMsg
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
ApduBuilder,
ApduResponse,
isSuccessCommandResult,
} from "@ledgerhq/device-management-kit";

import { SignOffChainMessageCommand } from "./SignOffChainMessageCommand";

describe("SignOffChainMessageCommand", () => {
let command: SignOffChainMessageCommand;
const MESSAGE = new Uint8Array(
Buffer.from("Solana SignOffChainMessage", "utf-8"),
);
const SIGNATURE_LENGTH = 64;

beforeEach(() => {
command = new SignOffChainMessageCommand({
message: MESSAGE,
});
jest.clearAllMocks();
jest.requireActual("@ledgerhq/device-management-kit");
});

describe("getApdu", () => {
it("should return the correct APDU", () => {
const apdu = command.getApdu();

const expectedApdu = new ApduBuilder({
cla: 0xe0,
ins: 0x07,
p1: 0x01,
p2: 0x00,
})
.addBufferToData(MESSAGE)
.build();

expect(apdu.getRawApdu()).toEqual(expectedApdu.getRawApdu());
});
});

describe("parseResponse", () => {
it("should parse the response correctly", () => {
const signature = new Uint8Array(SIGNATURE_LENGTH).fill(0x01);
const parsed = command.parseResponse(
new ApduResponse({
data: signature,
statusCode: new Uint8Array([0x90, 0x00]),
}),
);

expect(isSuccessCommandResult(parsed)).toBe(true);
if (isSuccessCommandResult(parsed)) {
expect(parsed.data).toEqual(signature);
} else {
fail("Expected success result");
}
});

describe("error handling", () => {
it("should return error if response is not success", () => {
const result = command.parseResponse(
new ApduResponse({
statusCode: new Uint8Array([0x6a, 0x82]),
data: new Uint8Array(0),
}),
);

expect(isSuccessCommandResult(result)).toBe(false);
if (!isSuccessCommandResult(result)) {
expect(result.error).toEqual(
expect.objectContaining({
message: "Unexpected device exchange error happened.",
}),
);
} else {
fail("Expected error");
}
});

it("should return error if signature is missing or incomplete", () => {
const incompleteSignature = new Uint8Array(SIGNATURE_LENGTH - 1).fill(
0x01,
);
const result = command.parseResponse(
new ApduResponse({
data: incompleteSignature,
statusCode: new Uint8Array([0x90, 0x00]),
}),
);
expect(isSuccessCommandResult(result)).toBe(false);
if (!isSuccessCommandResult(result)) {
if (
typeof result.error.originalError === "object" &&
result.error.originalError !== null &&
"message" in result.error.originalError
) {
expect(result.error.originalError.message).toBe(
"Signature is missing or incomplete",
);
}
} else {
fail("Expected error");
}
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {
type Apdu,
ApduBuilder,
ApduParser,
type ApduResponse,
type Command,
type CommandResult,
CommandResultFactory,
CommandUtils,
GlobalCommandErrorHandler,
InvalidStatusWordError,
} from "@ledgerhq/device-management-kit";

const SIGNATURE_LENGTH = 64;

type SignOffChainMessageCommandResponse = Uint8Array;

type SignOffChainMessageCommandArgs = {
message: Uint8Array;
};

export class SignOffChainMessageCommand
implements
Command<SignOffChainMessageCommandResponse, SignOffChainMessageCommandArgs>
{
args: SignOffChainMessageCommandArgs;

constructor(args: SignOffChainMessageCommandArgs) {
this.args = args;
}

getApdu(): Apdu {
return new ApduBuilder({
cla: 0xe0,
ins: 0x07,
p1: 0x01,
p2: 0x00,
})
.addBufferToData(this.args.message)
.build();
}

parseResponse(
response: ApduResponse,
): CommandResult<SignOffChainMessageCommandResponse> {
if (!CommandUtils.isSuccessResponse(response)) {
return CommandResultFactory({
error: GlobalCommandErrorHandler.handle(response),
});
}

const parser = new ApduParser(response);

if (!parser.testMinimalLength(SIGNATURE_LENGTH)) {
return CommandResultFactory({
error: new InvalidStatusWordError("Signature is missing or incomplete"),
});
}

const signature = parser.extractFieldByLength(SIGNATURE_LENGTH);
if (!signature) {
return CommandResultFactory({
error: new InvalidStatusWordError("Unable to extract signature"),
});
}

return CommandResultFactory({
data: signature,
});
}
}

0 comments on commit f3d03c8

Please sign in to comment.