Skip to content

Commit

Permalink
✨ (signer-btc): Implement getWalletAddress device action and sample a…
Browse files Browse the repository at this point in the history
…pp getWalletAddress
  • Loading branch information
fAnselmi-Ledger committed Jan 10, 2025
1 parent 808e5a6 commit 4d3fb61
Show file tree
Hide file tree
Showing 20 changed files with 867 additions and 80 deletions.
5 changes: 5 additions & 0 deletions .changeset/fuzzy-eagles-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/device-signer-kit-bitcoin": minor
---

Implement get wallet device action and sample app getWalletAddress
44 changes: 44 additions & 0 deletions apps/sample/src/components/SignerBtcView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {
type GetExtendedDAIntermediateValue,
type GetExtendedPublicKeyDAError,
type GetExtendedPublicKeyDAOutput,
type GetWalletAddressDAError,
type GetWalletAddressDAIntermediateValue,
type GetWalletAddressDAOutput,
SignerBtcBuilder,
type SignMessageDAError,
type SignMessageDAIntermediateValue,
Expand Down Expand Up @@ -114,6 +117,47 @@ export const SignerBtcView: React.FC<{ sessionId: string }> = ({
SignPsbtDAError,
SignPsbtDAIntermediateValue
>,
{
title: "Get wallet address",
description:
"Perform all the actions necessary to get the device's Bitcoin wallet address",
executeDeviceAction: ({
checkOnDevice,
change,
addressIndex,
derivationPath,
}) => {
if (!signer) {
throw new Error("Signer not initialized");
}

return signer.getWalletAddress(
new DefaultWallet(
derivationPath,
DefaultDescriptorTemplate.NATIVE_SEGWIT,
),
addressIndex,
{ checkOnDevice, change },
);
},
initialValues: {
checkOnDevice: false,
change: false,
derivationPath: DEFAULT_DERIVATION_PATH,
addressIndex: 0,
},
deviceModelId,
} satisfies DeviceActionProps<
GetWalletAddressDAOutput,
{
checkOnDevice: boolean;
change: boolean;
addressIndex: number;
derivationPath: string;
},
GetWalletAddressDAError,
GetWalletAddressDAIntermediateValue
>,
],
[deviceModelId, signer],
);
Expand Down
9 changes: 8 additions & 1 deletion packages/signer/signer-btc/src/api/SignerBtc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { type AddressOptions } from "@api/model/AddressOptions";
import { type Psbt } from "@api/model/Psbt";
import { type Wallet } from "@api/model/Wallet";

import { type GetWalletAddressDAReturnType } from "./app-binder/GetWalletAddressDeviceActionTypes";
import { WalletAddressOptions } from "./model/WalletAddressOptions";

export interface SignerBtc {
getExtendedPublicKey: (
derivationPath: string,
Expand All @@ -15,6 +18,10 @@ export interface SignerBtc {
message: string,
) => SignMessageDAReturnType;
signPsbt: (wallet: Wallet, psbt: Psbt) => SignPsbtDAReturnType;
// getAddress: (wallet: Wallet, options?: AddressOptions) => Promise<string>;
getWalletAddress: (
wallet: Wallet,
addressIndex: number,
options: WalletAddressOptions,
) => GetWalletAddressDAReturnType;
// signTransaction: (wallet: Wallet, psbt: Psbt) => Promise<Uint8Array>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
type CommandErrorResult,
type DeviceActionState,
type ExecuteDeviceActionReturnType,
type OpenAppDAError,
type OpenAppDARequiredInteraction,
} from "@ledgerhq/device-management-kit";

import { type WalletAddress } from "@api/model/Wallet";
import { type Wallet as ApiWallet } from "@api/model/Wallet";
import { type BtcErrorCodes } from "@internal/app-binder/command/utils/bitcoinAppErrors";

export type GetWalletAddressDAOutput = WalletAddress;

export type GetWalletAddressDAInput = {
checkOnDevice: boolean;
wallet: ApiWallet;
change: boolean;
addressIndex: number;
};

export type GetWalletAddressDAError =
| OpenAppDAError
| CommandErrorResult<BtcErrorCodes>["error"];

type GetWalletAddressDARequiredInteraction = OpenAppDARequiredInteraction;

export type GetWalletAddressDAIntermediateValue = {
requiredUserInteraction: GetWalletAddressDARequiredInteraction;
};

export type GetWalletAddressDAState = DeviceActionState<
GetWalletAddressDAOutput,
GetWalletAddressDAError,
GetWalletAddressDAIntermediateValue
>;

export type GetWalletAddressDAInternalState = {
readonly error: GetWalletAddressDAError | null;
readonly walletAddress: WalletAddress | null;
};

export type GetWalletAddressDAReturnType = ExecuteDeviceActionReturnType<
GetWalletAddressDAOutput,
GetWalletAddressDAError,
GetWalletAddressDAIntermediateValue
>;
1 change: 1 addition & 0 deletions packages/signer/signer-btc/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { type SignerBtc } from "./SignerBtc";
export { SignerBtcBuilder } from "./SignerBtcBuilder";
export * from "@api/app-binder/GetExtendedPublicKeyDeviceActionTypes";
export * from "@api/app-binder/GetWalletAddressDeviceActionTypes";
export type {
SignMessageDAError,
SignMessageDAInput,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type WalletAddressOptions = {
checkOnDevice?: boolean;
change?: boolean;
};
18 changes: 15 additions & 3 deletions packages/signer/signer-btc/src/internal/DefaultSignerBtc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import { type SignMessageDAReturnType } from "@api/app-binder/SignMessageDeviceA
import { type AddressOptions } from "@api/model/AddressOptions";
import { type Psbt } from "@api/model/Psbt";
import { type Wallet } from "@api/model/Wallet";
import { type WalletAddressOptions } from "@api/model/WalletAddressOptions";
import { type SignerBtc } from "@api/SignerBtc";
import { useCasesTypes } from "@internal/use-cases/di/useCasesTypes";
import { type GetExtendedPublicKeyUseCase } from "@internal/use-cases/get-extended-public-key/GetExtendedPublicKeyUseCase";
import { type SignPsbtUseCase } from "@internal/use-cases/sign-psbt/SignPsbtUseCase";

import { type GetWalletAddressUseCase } from "./use-cases/get-wallet-address/GetWalletAddressUseCase";
import { type SignMessageUseCase } from "./use-cases/sign-message/SignMessageUseCase";
import { makeContainer } from "./di";

Expand All @@ -28,6 +30,16 @@ export class DefaultSignerBtc implements SignerBtc {
this._container = makeContainer({ dmk, sessionId });
}

getWalletAddress(
wallet: Wallet,
addressIndex: number,
{ checkOnDevice = false, change = false }: WalletAddressOptions,
) {
return this._container
.get<GetWalletAddressUseCase>(useCasesTypes.GetWalletAddressUseCase)
.execute(checkOnDevice, wallet, change, addressIndex);
}

signPsbt(wallet: Wallet, psbt: Psbt) {
return this._container
.get<SignPsbtUseCase>(useCasesTypes.SignPsbtUseCase)
Expand All @@ -46,11 +58,11 @@ export class DefaultSignerBtc implements SignerBtc {
}

signMessage(
_derivationPath: string,
_message: string,
derivationPath: string,
message: string,
): SignMessageDAReturnType {
return this._container
.get<SignMessageUseCase>(useCasesTypes.SignMessageUseCase)
.execute(_derivationPath, _message);
.execute(derivationPath, message);
}
}
40 changes: 31 additions & 9 deletions packages/signer/signer-btc/src/internal/app-binder/BtcAppBinder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,23 @@ import {
GetExtendedPublicKeyDAInput,
GetExtendedPublicKeyDAReturnType,
} from "@api/app-binder/GetExtendedPublicKeyDeviceActionTypes";
import { SignMessageDAReturnType } from "@api/app-binder/SignMessageDeviceActionTypes";
import { SignPsbtDAReturnType } from "@api/app-binder/SignPsbtDeviceActionTypes";
import { Psbt } from "@api/model/Psbt";
import { Wallet } from "@api/model/Wallet";
import {
GetWalletAddressDAInput,
GetWalletAddressDAReturnType,
} from "@api/app-binder/GetWalletAddressDeviceActionTypes";
import {
SignMessageDAInput,
SignMessageDAReturnType,
} from "@api/app-binder/SignMessageDeviceActionTypes";
import {
SignPsbtDAInput,
SignPsbtDAReturnType,
} from "@api/app-binder/SignPsbtDeviceActionTypes";
import { GetExtendedPublicKeyCommand } from "@internal/app-binder/command/GetExtendedPublicKeyCommand";
import { SignPsbtDeviceAction } from "@internal/app-binder/device-action/SignPsbt/SignPsbtDeviceAction";
import { externalTypes } from "@internal/externalTypes";

import { GetWalletAddressDeviceAction } from "./device-action/GetWalletAddress/GetWalletAddressDeviceAction";
import { SignMessageDeviceAction } from "./device-action/SignMessage/SignMessageDeviceAction";

@injectable()
Expand All @@ -44,10 +53,7 @@ export class BtcAppBinder {
});
}

signMessage(args: {
derivationPath: string;
message: string;
}): SignMessageDAReturnType {
signMessage(args: SignMessageDAInput): SignMessageDAReturnType {
return this.dmk.executeDeviceAction({
sessionId: this.sessionId,
deviceAction: new SignMessageDeviceAction({
Expand All @@ -59,7 +65,7 @@ export class BtcAppBinder {
});
}

signPsbt(args: { psbt: Psbt; wallet: Wallet }): SignPsbtDAReturnType {
signPsbt(args: SignPsbtDAInput): SignPsbtDAReturnType {
return this.dmk.executeDeviceAction({
sessionId: this.sessionId,
deviceAction: new SignPsbtDeviceAction({
Expand All @@ -70,4 +76,20 @@ export class BtcAppBinder {
}),
});
}

getWalletAddress(
args: GetWalletAddressDAInput,
): GetWalletAddressDAReturnType {
return this.dmk.executeDeviceAction({
sessionId: this.sessionId,
deviceAction: new GetWalletAddressDeviceAction({
input: {
wallet: args.wallet,
checkOnDevice: args.checkOnDevice,
change: args.change,
addressIndex: args.addressIndex,
},
}),
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import {
type GetExtendedPublicKeyCommandArgs,
} from "./GetExtendedPublicKeyCommand";

const GET_EXTENDED_PUBLIC_KEY_APDU_WITH_DISPLAY = new Uint8Array([
const GET_EXTENDED_PUBLIC_KEY_APDU_WITH_checkOnDevice = new Uint8Array([
0xe1, 0x00, 0x00, 0x00, 0x0e, 0x01, 0x03, 0x80, 0x00, 0x00, 0x54, 0x80, 0x00,
0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
]);

const GET_EXTENDED_PUBLIC_KEY_APDU_WITHOUT_DISPLAY = new Uint8Array([
const GET_EXTENDED_PUBLIC_KEY_APDU_WITHOUT_checkOnDevice = new Uint8Array([
0xe1, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x80, 0x00, 0x00, 0x54, 0x80, 0x00,
0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
]);
Expand Down Expand Up @@ -60,11 +60,11 @@ describe("GetExtendedPublicKeyCommand", () => {

//THEN
expect(apdu.getRawApdu()).toEqual(
GET_EXTENDED_PUBLIC_KEY_APDU_WITH_DISPLAY,
GET_EXTENDED_PUBLIC_KEY_APDU_WITH_checkOnDevice,
);
});

it("should return the correct APDU without display", () => {
it("should return the correct APDU without checkOnDevice", () => {
// GIVEN
command = new GetExtendedPublicKeyCommand({
...defaultArgs,
Expand All @@ -76,7 +76,7 @@ describe("GetExtendedPublicKeyCommand", () => {

//THEN
expect(apdu.getRawApdu()).toEqual(
GET_EXTENDED_PUBLIC_KEY_APDU_WITHOUT_DISPLAY,
GET_EXTENDED_PUBLIC_KEY_APDU_WITHOUT_checkOnDevice,
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const USER_DENIED_STATUS = new Uint8Array([0x69, 0x85]);
describe("GetWalletAddressCommand", () => {
let command: GetWalletAddressCommand;
const defaultArgs: GetWalletAddressCommandArgs = {
display: true,
checkOnDevice: true,
walletId: Uint8Array.from("walletIdBuffer", (c) => c.charCodeAt(0)),
walletHmac: Uint8Array.from("walletHmacBuffer", (c) => c.charCodeAt(0)),
change: false,
Expand All @@ -38,7 +38,7 @@ describe("GetWalletAddressCommand", () => {
0x00, // P1
0x01, // P2
0x24, // Length of data: 36 bytes
0x01, // display: true
0x01, // checkOnDevice: true
...Uint8Array.from("walletIdBuffer", (c) => c.charCodeAt(0)),
...Uint8Array.from("walletHmacBuffer", (c) => c.charCodeAt(0)),
0x00, // change: false
Expand All @@ -52,7 +52,7 @@ describe("GetWalletAddressCommand", () => {

it("should return correct APDU for different arguments", () => {
const args: GetWalletAddressCommandArgs = {
display: false,
checkOnDevice: false,
walletId: Uint8Array.from("anotherWalletId", (c) => c.charCodeAt(0)),
walletHmac: Uint8Array.from("anotherWalletHmac", (c) =>
c.charCodeAt(0),
Expand All @@ -68,7 +68,7 @@ describe("GetWalletAddressCommand", () => {
0x00, // P1
0x01, // P2
0x26, // Length of data
0x00, // display: false
0x00, // checkOnDevice: false
...Uint8Array.from("anotherWalletId", (c) => c.charCodeAt(0)),
...Uint8Array.from("anotherWalletHmac", (c) => c.charCodeAt(0)),
0x01, // change: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
export type GetWalletAddressCommandResponse = ApduResponse;

export type GetWalletAddressCommandArgs = {
readonly display: boolean;
readonly checkOnDevice: boolean;
readonly walletId: Uint8Array;
readonly walletHmac: Uint8Array;
readonly change: boolean;
Expand Down Expand Up @@ -55,7 +55,7 @@ export class GetWalletAddressCommand
p1: 0x00,
p2: PROTOCOL_VERSION,
})
.addBufferToData(Uint8Array.from([this.args.display ? 1 : 0]))
.addBufferToData(Uint8Array.from([this.args.checkOnDevice ? 1 : 0]))
.addBufferToData(this.args.walletId)
.addBufferToData(this.args.walletHmac)
.addBufferToData(Uint8Array.from([this.args.change ? 1 : 0]))
Expand Down
Loading

0 comments on commit 4d3fb61

Please sign in to comment.