Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ (signer-solana) [DSDK-564]: Add solana SignDataTask and utils #459

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fresh-walls-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/device-signer-kit-solana": patch
---

Add solana SignDataTask and utils
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ApduResponse,
isSuccessCommandResult,
} from "@ledgerhq/device-management-kit";
import { Just } from "purify-ts";

import { SignOffChainMessageCommand } from "./SignOffChainMessageCommand";

Expand Down Expand Up @@ -50,7 +51,7 @@ describe("SignOffChainMessageCommand", () => {

expect(isSuccessCommandResult(parsed)).toBe(true);
if (isSuccessCommandResult(parsed)) {
expect(parsed.data).toEqual(signature);
expect(parsed.data).toEqual(Just(signature));
} else {
fail("Expected success result");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import {
GlobalCommandErrorHandler,
InvalidStatusWordError,
} from "@ledgerhq/device-management-kit";
import { Just, type Maybe, Nothing } from "purify-ts";

const SIGNATURE_LENGTH = 64;
import { type Signature } from "@api/model/Signature";

type SignOffChainMessageCommandResponse = Uint8Array;
const SIGNATURE_LENGTH = 64;

type SignOffChainMessageCommandArgs = {
message: Uint8Array;
export type SignOffChainMessageCommandResponse = Maybe<Signature>;
export type SignOffChainMessageCommandArgs = {
readonly message: Uint8Array;
};

export class SignOffChainMessageCommand
Expand Down Expand Up @@ -57,6 +59,12 @@ export class SignOffChainMessageCommand
});
}

if (parser.getUnparsedRemainingLength() === 0) {
return CommandResultFactory({
data: Nothing,
});
}

const signature = parser.extractFieldByLength(SIGNATURE_LENGTH);
if (!signature) {
return CommandResultFactory({
Expand All @@ -65,7 +73,7 @@ export class SignOffChainMessageCommand
}

return CommandResultFactory({
data: signature,
data: Just(signature),
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { type InternalApi } from "@ledgerhq/device-management-kit";

const sendCommandMock = jest.fn();
const apiGetDeviceSessionStateMock = jest.fn();
const apiGetDeviceSessionStateObservableMock = jest.fn();
const setDeviceSessionStateMock = jest.fn();
const getMetadataForAppHashesMock = jest.fn();

export function makeDeviceActionInternalApiMock(): jest.Mocked<InternalApi> {
return {
sendCommand: sendCommandMock,
getDeviceSessionState: apiGetDeviceSessionStateMock,
getDeviceSessionStateObservable: apiGetDeviceSessionStateObservableMock,
setDeviceSessionState: setDeviceSessionStateMock,
getMetadataForAppHashes: getMetadataForAppHashesMock,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
APDU_MAX_PAYLOAD,
ByteArrayBuilder,
type Command,
type CommandResult,
CommandResultFactory,
type InternalApi,
InvalidStatusWordError,
isSuccessCommandResult,
} from "@ledgerhq/device-management-kit";

export type SendCommandInChunksTaskArgs<T> = {
data: Uint8Array;
commandFactory: CommandFactory<T>;
};

export type CommandFactory<T> = <V extends ChunkableCommandArgs>(
args: ChunkableCommandArgs,
) => Command<T, V>;

export type ChunkableCommandArgs = {
chunkedData: Uint8Array;
};

export class SendCommandInChunksTask<T> {
constructor(
private api: InternalApi,
private args: SendCommandInChunksTaskArgs<T>,
) {}

async run(): Promise<CommandResult<T, void>> {
const { data: fullPayload, commandFactory } = this.args;

const dataBuffer = new ByteArrayBuilder(fullPayload.length)
.addBufferToData(fullPayload)
.build();

for (
let offset = 0;
offset < dataBuffer.length;
offset += APDU_MAX_PAYLOAD
) {
const isLastChunk = offset + APDU_MAX_PAYLOAD >= dataBuffer.length;
const result = await this.api.sendCommand(
commandFactory({
chunkedData: dataBuffer.slice(offset, offset + APDU_MAX_PAYLOAD),
}),
);

if (!isSuccessCommandResult(result)) {
return result;
}

if (isLastChunk) {
return CommandResultFactory({
data: result.data,
});
}
}

throw new InvalidStatusWordError("No result after processing all chunks");
}
}
Loading