-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ (signer-solana) [DSDK-550]: Add GetPubKey command (#441)
- Loading branch information
Showing
6 changed files
with
239 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@ledgerhq/device-signer-kit-solana": minor | ||
--- | ||
|
||
Add GetPubKey command |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
export type PublicKey = Uint8Array[32]; | ||
export type PublicKey = string; |
126 changes: 126 additions & 0 deletions
126
packages/signer/signer-solana/src/internal/app-binder/command/GetPubKeyCommand.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import { | ||
ApduResponse, | ||
CommandResultFactory, | ||
isSuccessCommandResult, | ||
} from "@ledgerhq/device-management-kit"; | ||
|
||
import { GetPubKeyCommand } from "./GetPubKeyCommand"; | ||
|
||
const GET_PUBKYEY_APDU_DEFAULT_PATH_WITH_CONFIRM = new Uint8Array([ | ||
0xe0, 0x05, 0x01, 0x00, 0x09, 0x02, 0x80, 0x00, 0x00, 0x2c, 0x80, 0x00, 0x01, | ||
0xf5, | ||
]); | ||
|
||
const GET_PUBKYEY_APDU_DEFAULT_PATH_WITHOUT_CONFIRM = new Uint8Array([ | ||
0xe0, 0x05, 0x00, 0x00, 0x09, 0x02, 0x80, 0x00, 0x00, 0x2c, 0x80, 0x00, 0x01, | ||
0xf5, | ||
]); | ||
|
||
const GET_PUBKEY_APDU_DIFFERENT_PATH = new Uint8Array([ | ||
0xe0, 0x05, 0x01, 0x00, 0x09, 0x02, 0x80, 0x00, 0x00, 0x2c, 0x80, 0x00, 0x01, | ||
0xf6, | ||
]); | ||
|
||
// D2PPQSYFe83nDzk96FqGumVU8JA7J8vj2Rhjc2oXzEi5 | ||
const GET_PUBKEY_APDU = new Uint8Array([ | ||
0xb2, 0xa7, 0x22, 0xdc, 0x18, 0xdd, 0x5c, 0x49, 0xc3, 0xf4, 0x8e, 0x9b, 0x07, | ||
0x26, 0xf1, 0x1b, 0xe6, 0x67, 0x86, 0xe9, 0x1c, 0xac, 0x57, 0x34, 0x98, 0xd6, | ||
0xee, 0x88, 0x39, 0x2c, 0xc9, 0x6a, 0x90, 0x00, | ||
]); | ||
|
||
const GET_PUBKEY_APDU_RESPONSE = new ApduResponse({ | ||
statusCode: Uint8Array.from([0x90, 0x00]), | ||
data: GET_PUBKEY_APDU, | ||
}); | ||
|
||
describe("GetPubKeyCommand", () => { | ||
let command: GetPubKeyCommand; | ||
const defaultArgs = { | ||
derivationPath: "44'/501'", | ||
checkOnDevice: true, | ||
}; | ||
|
||
beforeEach(() => { | ||
command = new GetPubKeyCommand(defaultArgs); | ||
jest.clearAllMocks(); | ||
jest.requireActual("@ledgerhq/device-management-kit"); | ||
}); | ||
|
||
describe("getApdu", () => { | ||
it("should return APDU", () => { | ||
const apdu = command.getApdu(); | ||
expect(apdu.getRawApdu()).toEqual( | ||
GET_PUBKYEY_APDU_DEFAULT_PATH_WITH_CONFIRM, | ||
); | ||
}); | ||
|
||
it("should return APDU without confirm", () => { | ||
command = new GetPubKeyCommand({ | ||
...defaultArgs, | ||
checkOnDevice: false, | ||
}); | ||
const apdu = command.getApdu(); | ||
expect(apdu.getRawApdu()).toEqual( | ||
GET_PUBKYEY_APDU_DEFAULT_PATH_WITHOUT_CONFIRM, | ||
); | ||
}); | ||
|
||
it("should return APDU with different path", () => { | ||
command = new GetPubKeyCommand({ | ||
...defaultArgs, | ||
derivationPath: "44'/502'", | ||
}); | ||
const apdu = command.getApdu(); | ||
expect(apdu.getRawApdu()).toEqual(GET_PUBKEY_APDU_DIFFERENT_PATH); | ||
}); | ||
}); | ||
|
||
describe("parseResponse", () => { | ||
it("should parse the response", () => { | ||
const parsed = command.parseResponse(GET_PUBKEY_APDU_RESPONSE); | ||
expect(parsed).toStrictEqual( | ||
CommandResultFactory({ | ||
data: "D2PPQSYFe83nDzk96FqGumVU8JA7J8vj2Rhjc2oXzEi5", | ||
}), | ||
); | ||
}); | ||
|
||
describe("error handling", () => { | ||
it("should return error if response is not success", () => { | ||
const response = new ApduResponse({ | ||
statusCode: Uint8Array.from([0x6a, 0x82]), | ||
data: new Uint8Array(0), | ||
}); | ||
const result = command.parseResponse(response); | ||
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 public key is missing", () => { | ||
const response = new ApduResponse({ | ||
statusCode: Uint8Array.from([0x90, 0x00]), | ||
data: new Uint8Array(0), | ||
}); | ||
const result = command.parseResponse(response); | ||
expect(isSuccessCommandResult(result)).toBe(false); | ||
if (!isSuccessCommandResult(result)) { | ||
expect(result.error.originalError).toEqual( | ||
expect.objectContaining({ | ||
message: "Public key is missing", | ||
}), | ||
); | ||
} else { | ||
fail("Expected error"); | ||
} | ||
}); | ||
}); | ||
}); | ||
}); |
86 changes: 86 additions & 0 deletions
86
packages/signer/signer-solana/src/internal/app-binder/command/GetPubKeyCommand.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { | ||
Apdu, | ||
ApduBuilder, | ||
type ApduBuilderArgs, | ||
ApduParser, | ||
ApduResponse, | ||
type Command, | ||
CommandResult, | ||
CommandResultFactory, | ||
CommandUtils, | ||
GlobalCommandErrorHandler, | ||
InvalidStatusWordError, | ||
} from "@ledgerhq/device-management-kit"; | ||
import { DerivationPathUtils } from "@ledgerhq/signer-utils"; | ||
import bs58 from "bs58"; | ||
|
||
import { PublicKey } from "@api/model/PublicKey"; | ||
|
||
const PUBKEY_LENGTH = 32; | ||
|
||
type GetPubKeyCommandResponse = PublicKey; | ||
type GetPubKeyCommandArgs = { | ||
derivationPath: string; | ||
checkOnDevice: boolean; | ||
}; | ||
|
||
export class GetPubKeyCommand | ||
implements Command<GetPubKeyCommandResponse, GetPubKeyCommandArgs> | ||
{ | ||
args: GetPubKeyCommandArgs; | ||
|
||
constructor(args: GetPubKeyCommandArgs) { | ||
this.args = args; | ||
} | ||
|
||
getApdu(): Apdu { | ||
const getPubKeyArgs: ApduBuilderArgs = { | ||
cla: 0xe0, | ||
ins: 0x05, | ||
p1: this.args.checkOnDevice ? 0x01 : 0x00, | ||
p2: 0x00, | ||
}; | ||
const builder = new ApduBuilder(getPubKeyArgs); | ||
const derivationPath = this.args.derivationPath; | ||
|
||
const path = DerivationPathUtils.splitPath(derivationPath); | ||
builder.add8BitUIntToData(path.length); | ||
path.forEach((element) => { | ||
builder.add32BitUIntToData(element); | ||
}); | ||
|
||
return builder.build(); | ||
} | ||
|
||
parseResponse( | ||
response: ApduResponse, | ||
): CommandResult<GetPubKeyCommandResponse> { | ||
const parser = new ApduParser(response); | ||
|
||
if (!CommandUtils.isSuccessResponse(response)) { | ||
return CommandResultFactory({ | ||
error: GlobalCommandErrorHandler.handle(response), | ||
}); | ||
} | ||
|
||
if (parser.testMinimalLength(PUBKEY_LENGTH) === false) { | ||
return CommandResultFactory({ | ||
error: new InvalidStatusWordError("Public key is missing"), | ||
}); | ||
} | ||
|
||
const buffer = parser.extractFieldByLength(PUBKEY_LENGTH); | ||
|
||
if (buffer === undefined) { | ||
return CommandResultFactory({ | ||
error: new InvalidStatusWordError("Unable to extract public key"), | ||
}); | ||
} | ||
|
||
const publicKey = bs58.encode(buffer); | ||
|
||
return CommandResultFactory({ | ||
data: publicKey, | ||
}); | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.