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-btc) [DSDK-471]: SignPsbt task #581

Merged
merged 14 commits into from
Jan 14, 2025
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/blue-lemons-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/device-management-kit": patch
---

Fix CommandUtils static calls
5 changes: 5 additions & 0 deletions .changeset/breezy-plums-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/signer-utils": patch
---

Create CommandErrorHelper to handle command errors
5 changes: 5 additions & 0 deletions .changeset/nervous-points-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/device-signer-kit-bitcoin": minor
---

Create SignPsbt API
5 changes: 5 additions & 0 deletions .changeset/odd-spies-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/device-management-kit": patch
---

Expose CommandSuccessResult
5 changes: 5 additions & 0 deletions .changeset/young-horses-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/device-signer-kit-bitcoin": patch
---

Use CommandErrorHelper in BTC commands
2 changes: 1 addition & 1 deletion apps/sample/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
"@ledgerhq/device-management-kit": "workspace:*",
"@ledgerhq/device-management-kit-flipper-plugin-client": "workspace:*",
"@ledgerhq/device-mockserver-client": "workspace:*",
"@ledgerhq/device-signer-kit-bitcoin": "workspace:*",
"@ledgerhq/device-signer-kit-ethereum": "workspace:*",
"@ledgerhq/device-signer-kit-solana": "workspace:*",
"@ledgerhq/device-signer-kit-bitcoin": "workspace:*",
"@ledgerhq/device-transport-kit-mockserver": "workspace:*",
"@ledgerhq/device-transport-kit-web-ble": "workspace:*",
"@ledgerhq/device-transport-kit-web-hid": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "@ledgerhq/device-management-kit";
import { Flex, Icons, Tag, Text, Tooltip } from "@ledgerhq/react-ui";
import styled from "styled-components";
import { inspect } from "util";

import { type FieldType } from "@/hooks/useForm";

Expand Down Expand Up @@ -91,11 +92,11 @@ export function DeviceActionResponse<
wordBreak: "break-word",
}}
>
{JSON.stringify(
isError ? props.error : props.deviceActionState,
null,
2,
)}
{isError
? inspect(props.error, { depth: null })
: props.deviceActionState.status === DeviceActionStatus.Error
? inspect(props.deviceActionState.error, { depth: null })
: JSON.stringify(props.deviceActionState, null, 2)}
</Text>
{!isError &&
props.deviceActionState.status === DeviceActionStatus.Pending ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ export type DeviceActionProps<
debug?: boolean,
) => ExecuteDeviceActionReturnType<Output, Error, IntermediateValue>;
initialValues: Input;
InputValuesComponent?: React.FC<{
initialValues: Input;
onChange: (values: Input) => void;
valueSelector?: ValueSelector<FieldType>;
disabled?: boolean;
}>;
validateValues?: (args: Input) => boolean;
valueSelector?: ValueSelector<FieldType>;
deviceModelId: DeviceModelId;
Expand Down Expand Up @@ -94,6 +100,7 @@ export function DeviceActionTester<
executeDeviceAction,
valueSelector,
validateValues,
InputValuesComponent,
} = props;

const nonce = useRef(-1);
Expand Down Expand Up @@ -204,12 +211,21 @@ export function DeviceActionTester<
rowGap={3}
pointerEvents={loading ? "none" : "auto"}
>
<CommandForm
initialValues={values}
onChange={setValues}
valueSelector={valueSelector}
disabled={loading}
/>
{InputValuesComponent ? (
<InputValuesComponent
initialValues={values}
onChange={setValues}
valueSelector={valueSelector}
disabled={loading}
/>
) : (
<CommandForm
initialValues={values}
onChange={setValues}
valueSelector={valueSelector}
disabled={loading}
/>
)}
<Divider />
<Switch
checked={inspect}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useCallback, useEffect } from "react";
import { DefaultDescriptorTemplate } from "@ledgerhq/device-signer-kit-bitcoin";
import { Flex, Input, SelectInput, Text } from "@ledgerhq/react-ui";

import { useForm } from "@/hooks/useForm";

type SignPsbtInputValuesType = {
psbt: string;
path: string;
descriptorTemplate: DefaultDescriptorTemplate;
};

const descriptorTemplateToDerivationPath: Record<
DefaultDescriptorTemplate,
string
> = {
[DefaultDescriptorTemplate.TAPROOT]: "86'/0'/0'",
[DefaultDescriptorTemplate.NATIVE_SEGWIT]: "84'/0'/0'",
[DefaultDescriptorTemplate.NESTED_SEGWIT]: "49'/0'/0'",
[DefaultDescriptorTemplate.LEGACY]: "44'/0'/0'",
};

const descriptorTemplateToLabel = {
[DefaultDescriptorTemplate.TAPROOT]: "Taproot",
[DefaultDescriptorTemplate.NATIVE_SEGWIT]: "Native Segwit",
[DefaultDescriptorTemplate.NESTED_SEGWIT]: "Nested Segwit",
[DefaultDescriptorTemplate.LEGACY]: "Legacy",
};

export const SignPsbtDAInputValuesForm: React.FC<{
initialValues: SignPsbtInputValuesType;
onChange: (values: SignPsbtInputValuesType) => void;
disabled?: boolean;
}> = ({ initialValues, onChange, disabled }) => {
const { formValues, setFormValues, setFormValue } = useForm(initialValues);

useEffect(() => {
onChange(formValues);
}, [formValues, onChange]);

const onWalletDescriptorTemplateChange = useCallback(
(value: DefaultDescriptorTemplate) => {
const newValues = {
path: descriptorTemplateToDerivationPath[value],
descriptorTemplate: value,
};
setFormValues((prev) => ({ ...prev, ...newValues }));
},
[setFormValues],
);

return (
<Flex
flexDirection="column"
flex={1}
rowGap={6}
columnGap={6}
flexWrap="wrap"
>
<Flex flexDirection="row" alignItems="center" mb={4}>
<Text style={{ marginRight: 8 }}>Wallet address type</Text>
<SelectInput
options={Object.entries(DefaultDescriptorTemplate).map(
([_key, value]) => ({
label: descriptorTemplateToLabel[value],
value,
}),
)}
value={{
label: descriptorTemplateToLabel[formValues.descriptorTemplate],
value: formValues.descriptorTemplate,
}}
isMulti={false}
isSearchable={false}
onChange={(newVal) =>
newVal && onWalletDescriptorTemplateChange(newVal.value)
}
/>
</Flex>

<Input
id="path"
value={formValues.path}
placeholder="path"
onChange={(newVal) => setFormValue("path", newVal)}
disabled={disabled}
data-testid="input-text_path"
/>

<Input
id="psbt"
value={formValues.psbt}
placeholder="psbt"
onChange={(newVal) => setFormValue("psbt", newVal)}
disabled={disabled}
data-testid="input-text_psbt"
/>
</Flex>
);
};
37 changes: 37 additions & 0 deletions apps/sample/src/components/SignerBtcView/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import React, { useMemo } from "react";
import {
DefaultDescriptorTemplate,
DefaultWallet,
type GetExtendedDAIntermediateValue,
type GetExtendedPublicKeyDAError,
type GetExtendedPublicKeyDAOutput,
SignerBtcBuilder,
type SignMessageDAError,
type SignMessageDAIntermediateValue,
type SignMessageDAOutput,
type SignPsbtDAError,
type SignPsbtDAIntermediateValue,
type SignPsbtDAOutput,
} from "@ledgerhq/device-signer-kit-bitcoin";

import { DeviceActionsList } from "@/components/DeviceActionsView/DeviceActionsList";
import { type DeviceActionProps } from "@/components/DeviceActionsView/DeviceActionTester";
import { SignPsbtDAInputValuesForm } from "@/components/SignerBtcView/SignPsbtDAInputValusForm";
import { useDmk } from "@/providers/DeviceManagementKitProvider";

const DEFAULT_DERIVATION_PATH = "84'/0'/0'";
Expand Down Expand Up @@ -78,6 +84,37 @@ export const SignerBtcView: React.FC<{ sessionId: string }> = ({
SignMessageDAError,
SignMessageDAIntermediateValue
>,
{
title: "Sign psbt",
description:
"Perform all the actions necessary to sign a PSBT with the device",
executeDeviceAction: ({ descriptorTemplate, psbt, path }) => {
if (!signer) {
throw new Error("Signer not initialized");
}

return signer.signPsbt(
new DefaultWallet(path, descriptorTemplate),
psbt,
);
},
InputValuesComponent: SignPsbtDAInputValuesForm,
initialValues: {
descriptorTemplate: DefaultDescriptorTemplate.NATIVE_SEGWIT,
psbt: "",
path: DEFAULT_DERIVATION_PATH,
},
deviceModelId,
} satisfies DeviceActionProps<
SignPsbtDAOutput,
{
psbt: string;
path: string;
descriptorTemplate: DefaultDescriptorTemplate;
},
SignPsbtDAError,
SignPsbtDAIntermediateValue
>,
],
[deviceModelId, signer],
);
Expand Down
1 change: 1 addition & 0 deletions apps/sample/src/hooks/useForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ export function useForm<T extends Record<string, string | boolean | number>>(
return {
formValues,
setFormValue,
setFormValues,
};
}
2 changes: 1 addition & 1 deletion apps/sample/src/styles/globalstyles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const GlobalStyle = createGlobalStyle`
background-color: #000000;
}
body {
user-select: none;
user-select: text;
}
.no-scrollbar {
&::-webkit-scrollbar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export enum CommandResultStatus {
Error = "ERROR",
Success = "SUCCESS",
}
type CommandSuccessResult<Data> = {
export type CommandSuccessResult<Data> = {
status: CommandResultStatus.Success;
data: Data;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ export class CommandUtils {
}

static isSuccessResponse({ statusCode }: ApduResponse) {
if (!this.isValidStatusCode(statusCode)) {
if (!CommandUtils.isValidStatusCode(statusCode)) {
return false;
}

return statusCode[0] === 0x90 && statusCode[1] === 0x00;
}

static isLockedDeviceResponse({ statusCode }: ApduResponse) {
if (!this.isValidStatusCode(statusCode)) {
if (!CommandUtils.isValidStatusCode(statusCode)) {
return false;
}

Expand Down
1 change: 1 addition & 0 deletions packages/device-management-kit/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type { Command } from "@api/command/Command";
export type {
CommandErrorResult,
CommandResult,
CommandSuccessResult,
} from "@api/command/model/CommandResult";
export type { SendCommandUseCaseArgs } from "@api/command/use-case/SendCommandUseCase";
export type { DeviceModelId } from "@api/device/DeviceModel";
Expand Down
17 changes: 7 additions & 10 deletions packages/signer/signer-btc/src/api/SignerBtc.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
// import { type AddressOptions } from "@api/model/AddressOptions";
// import { type Psbt } from "@api/model/Psbt";
// import { type Signature } from "@api/model/Signature";
// import { type Wallet } from "@api/model/Wallet";
import { type GetExtendedPublicKeyDAReturnType } from "@api/app-binder/GetExtendedPublicKeyDeviceActionTypes";
import { type SignMessageDAReturnType } from "@api/app-binder/SignMessageDeviceActionTypes";
import { type SignPsbtDAReturnType } from "@api/app-binder/SignPsbtDeviceActionTypes";
import { type AddressOptions } from "@api/model/AddressOptions";
import {
type GetExtendedPublicKeyReturnType,
type SignMessageDAReturnType,
} from "@root/src";
import { type Psbt } from "@api/model/Psbt";
import { type Wallet } from "@api/model/Wallet";

export interface SignerBtc {
getExtendedPublicKey: (
derivationPath: string,
options: AddressOptions,
) => GetExtendedPublicKeyReturnType;
) => GetExtendedPublicKeyDAReturnType;
signMessage: (
derivationPath: string,
message: string,
) => SignMessageDAReturnType;
signPsbt: (wallet: Wallet, psbt: Psbt) => SignPsbtDAReturnType;
// getAddress: (wallet: Wallet, options?: AddressOptions) => Promise<string>;
// signPsbt: (wallet: Wallet, psbt: Psbt) => Promise<Psbt>;
// signTransaction: (wallet: Wallet, psbt: Psbt) => Promise<Uint8Array>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
type GetExtendedPublicKeyCommandArgs,
type GetExtendedPublicKeyCommandResponse,
} from "@internal/app-binder/command/GetExtendedPublicKeyCommand";
import { type BtcErrorCodes } from "@internal/app-binder/command/utils/bitcoinAppErrors";

type GetExtendedPublicKeyDARequiredInteraction =
| UserInteractionRequired.None
Expand All @@ -18,14 +19,15 @@ type GetExtendedPublicKeyDARequiredInteraction =
export type GetExtendedPublicKeyDAOutput =
SendCommandInAppDAOutput<GetExtendedPublicKeyCommandResponse>;

export type GetExtendedPublicKeyDAError = SendCommandInAppDAError;
export type GetExtendedPublicKeyDAError =
SendCommandInAppDAError<BtcErrorCodes>;

export type GetExtendedDAIntermediateValue =
SendCommandInAppDAIntermediateValue<GetExtendedPublicKeyDARequiredInteraction>;

export type GetExtendedPublicKeyDAInput = GetExtendedPublicKeyCommandArgs;

export type GetExtendedPublicKeyReturnType = ExecuteDeviceActionReturnType<
export type GetExtendedPublicKeyDAReturnType = ExecuteDeviceActionReturnType<
GetExtendedPublicKeyDAOutput,
GetExtendedPublicKeyDAError,
GetExtendedDAIntermediateValue
Expand Down
Loading