Skip to content

Commit

Permalink
Merge pull request #504 from enkryptcom/feat/hw-signer
Browse files Browse the repository at this point in the history
[WIP] feat: HW wallet support for BTC, LTC and DOGE
  • Loading branch information
kvhnuke committed Sep 9, 2024
2 parents a0fa430 + 40a70db commit 9dc801d
Show file tree
Hide file tree
Showing 31 changed files with 841 additions and 983 deletions.
16 changes: 7 additions & 9 deletions packages/extension/src/libs/utils/accounts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EnkryptAccount, NetworkNames, SignerType } from "@enkryptcom/types";
import PublicKeyRing from "../keyring/public-keyring";
import { getNetworkByName } from "./networks";
import { ProviderName } from "@/types/provider";

const getOtherSigners = (signers: SignerType[]): SignerType[] => {
const otherSigners: SignerType[] = [];
Expand All @@ -22,16 +23,13 @@ export const getAccountsByNetworkName = async (
const accounts = await keyring.getAccounts(network.signer);

const filtered = accounts.filter((account) => {
if (account.isHardware && account.HWOptions !== undefined) {
// Polkadot and Kusama ledger apps only work for those networks
if (
account.HWOptions.networkName === NetworkNames.Kusama ||
account.HWOptions.networkName === NetworkNames.Polkadot
) {
return account.HWOptions.networkName === networkName;
}
if (
account.isHardware &&
account.HWOptions !== undefined &&
network.provider !== ProviderName.ethereum
) {
return account.HWOptions.networkName === networkName;
}

return true;
});
return filtered.map((f) => {
Expand Down
9 changes: 9 additions & 0 deletions packages/extension/src/providers/bitcoin/libs/api-ss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ class API implements ProviderAPIInterface {
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
async init(): Promise<void> {}
async getRawTransaction(hash: string): Promise<string | null> {
return fetch(`${this.node}/api/v1/tx/${hash}/raw`)
.then((res) => res.json())
.then((tx: { hex: string; error: unknown }) => {
if ((tx as any).error) return null;
if (!tx.hex) return null;
return `0x${tx.hex}`;
});
}
async getTransactionStatus(hash: string): Promise<BTCRawInfo | null> {
return fetch(`${this.node}/api/v1/tx/${hash}`)
.then((res) => res.json())
Expand Down
9 changes: 9 additions & 0 deletions packages/extension/src/providers/bitcoin/libs/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ class API implements ProviderAPIInterface {
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
async init(): Promise<void> {}
async getRawTransaction(hash: string): Promise<string | null> {
return fetch(`${this.node}transaction/${hash}/raw`)
.then((res) => res.json())
.then((tx: { result: string; error: unknown }) => {
if ((tx as any).error) return null;
if (!tx.result) return null;
return `0x${tx.result}`;
});
}
async getTransactionStatus(hash: string): Promise<BTCRawInfo | null> {
return fetch(`${this.node}transaction/${hash}`)
.then((res) => res.json())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ import { DEFAULT_BTC_NETWORK, getNetworkByName } from "@/libs/utils/networks";
import { WindowPromiseHandler } from "@/libs/window-promise";
import { BitcoinNetwork } from "../types/bitcoin-network";
import { ProviderRequestOptions } from "@/types/provider";
import PublicKeyRing from "@/libs/keyring/public-keyring";
import { fromBase } from "@enkryptcom/utils";
import { getError } from "@/libs/error";
import { ErrorCodes } from "@/providers/ethereum/types";
import { getAccountsByNetworkName } from "@/libs/utils/accounts";
import AccountState from "../libs/accounts-state";
const windowPromise = WindowPromiseHandler(1);
Expand Down Expand Up @@ -109,9 +109,8 @@ onBeforeMount(async () => {
network.value = (await getNetworkByName(
Request.value.params![0]
)) as BitcoinNetwork;
const keyring = new PublicKeyRing();
Options.value = options;
const accounts = await keyring.getAccounts(network.value.signer);
const accounts = await getAccountsByNetworkName(network.value.name);
displayAddress.value = network.value.displayAddress(accounts[0].address);
identicon.value = network.value.identicon(accounts[0].address);
accountHeaderData.value = {
Expand Down
91 changes: 58 additions & 33 deletions packages/extension/src/providers/bitcoin/ui/libs/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { InternalMethods, InternalOnMessageResponse } from "@/types/messenger";
import { SignerTransactionOptions, SignerMessageOptions } from "../types";
import sendUsingInternalMessengers from "@/libs/messenger/internal-messenger";
import { hexToBuffer, bufferToHex } from "@enkryptcom/utils";
import { Psbt } from "bitcoinjs-lib";
import { Psbt, Transaction } from "bitcoinjs-lib";
import { BitcoinNetwork, PaymentType } from "../../types/bitcoin-network";
import { EnkryptAccount } from "@enkryptcom/types";
import { EnkryptAccount, HWwalletType } from "@enkryptcom/types";
import { signMessageOfBIP322Simple } from "../../libs/bip322-message-sign";
import { magicHash, toCompact } from "../../libs/sign-message-utils";
import HWwallet from "@enkryptcom/hw-wallets";
import type BitcoinAPI from "@/providers/bitcoin/libs/api";

const PSBTSigner = (account: EnkryptAccount, network: BitcoinNetwork) => {
return {
Expand All @@ -29,44 +31,67 @@ const PSBTSigner = (account: EnkryptAccount, network: BitcoinNetwork) => {
};
};

const TransactionSigner = (
const TransactionSigner = async (
options: SignerTransactionOptions
): Promise<Psbt> => {
): Promise<Transaction> => {
const { account, network, payload } = options;
if (account.isHardware) {
throw new Error("btc-hardware not implemented");
} else {
const tx = new Psbt({
network: network.networkInfo,
maximumFeeRate: network.networkInfo.maxFeeRate,
});
payload.inputs
.map((u) => {
const res: {
hash: string;
index: number;
witnessUtxo?: { script: Buffer; value: number };
nonWitnessUtxo?: Buffer;
} = {
hash: u.hash,
index: u.index,
const tx = new Psbt({
network: network.networkInfo,
maximumFeeRate: network.networkInfo.maxFeeRate,
});
payload.inputs
.map((u) => {
const res: {
hash: string;
index: number;
witnessUtxo?: { script: Buffer; value: number };
nonWitnessUtxo?: Buffer;
} = {
hash: u.hash,
index: u.index,
};
if (network.networkInfo.paymentType === PaymentType.P2WPKH) {
res.witnessUtxo = {
script: Buffer.from(u.witnessUtxo.script, "hex"),
value: u.witnessUtxo.value,
};
if (network.networkInfo.paymentType === PaymentType.P2WPKH) {
res.witnessUtxo = {
script: Buffer.from(u.witnessUtxo.script, "hex"),
value: u.witnessUtxo.value,
};
} else if (network.networkInfo.paymentType === PaymentType.P2PKH) {
res.nonWitnessUtxo = Buffer.from(u.raw, "hex");
}
return res;
} else if (network.networkInfo.paymentType === PaymentType.P2PKH) {
res.nonWitnessUtxo = Buffer.from(u.raw, "hex");
}
return res;
})
.forEach((input) => tx.addInput(input));
payload.outputs.forEach((output) => tx.addOutput(output));
if (account.isHardware) {
const hwwallets = new HWwallet();
const api = (await network.api()) as BitcoinAPI;
const txPromises = payload.inputs.map((u) => api.getRawTransaction(u.hash));
const rawTxs = await Promise.all(txPromises);
for (const t of rawTxs) {
if (t === null) throw new Error("bitcoin-signer: Invalid tx hash");
}
return hwwallets
.signTransaction({
transaction: {
rawTxs: rawTxs as string[],
psbtTx: tx,
},
networkName: network.name,
pathIndex: account.pathIndex.toString(),
pathType: {
basePath: account.basePath,
path: account.HWOptions!.pathTemplate,
},
wallet: account.walletType as unknown as HWwalletType,
})
.forEach((input) => tx.addInput(input));
payload.outputs.forEach((output) => tx.addOutput(output));
.then((strTx) => {
return Transaction.fromHex(strTx);
});
} else {
const signer = PSBTSigner(account, network);
return tx.signAllInputsAsync(signer).then(() => {
tx.finalizeAllInputs();
return tx;
return tx.extractTransaction();
});
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,8 +493,8 @@ const sendAction = async () => {
await Browser.windows.create({
url: Browser.runtime.getURL(
getUiPath(
`eth-hw-verify?id=${routedRoute.query.id}&txData=${routedRoute.query.txData}`,
ProviderName.ethereum
`btc-hw-verify?id=${routedRoute.query.id}&txData=${routedRoute.query.txData}`,
ProviderName.bitcoin
)
),
type: "popup",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ const sendAction = async () => {
})
.then((signedTx) => {
api
.broadcastTx(signedTx.extractTransaction().toHex())
.broadcastTx(signedTx.toHex())
.then(() => {
trackSendEvents(SendEventType.SendComplete, {
network: network.value.name,
Expand All @@ -172,7 +172,7 @@ const sendAction = async () => {
[
{
...txActivity,
...{ transactionHash: signedTx.extractTransaction().getId() },
...{ transactionHash: signedTx.getId() },
},
],
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ const pasteFromClipboard = () => {
position: relative;
margin: auto;
width: 100%;
max-height: 380px;
max-height: 330px;
}
h3 {
Expand Down
Loading

1 comment on commit 9dc801d

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.