@swapkit/wallet-hardware 4.8.0 → 4.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/keepkey/index.cjs.map +2 -2
  2. package/dist/keepkey/index.js.map +2 -2
  3. package/dist/types/keepkey/chains/utxo.d.ts +5 -379
  4. package/dist/types/keepkey/chains/utxo.d.ts.map +1 -1
  5. package/package.json +5 -7
  6. package/src/index.ts +1 -0
  7. package/src/keepkey/chains/cosmos.ts +69 -0
  8. package/src/keepkey/chains/evm.ts +141 -0
  9. package/src/keepkey/chains/mayachain.ts +98 -0
  10. package/src/keepkey/chains/ripple.ts +88 -0
  11. package/src/keepkey/chains/thorchain.ts +93 -0
  12. package/src/keepkey/chains/utxo.ts +364 -0
  13. package/src/keepkey/coins.ts +67 -0
  14. package/src/keepkey/index.ts +174 -0
  15. package/src/ledger/clients/cosmos.ts +84 -0
  16. package/src/ledger/clients/evm.ts +186 -0
  17. package/src/ledger/clients/near.ts +63 -0
  18. package/src/ledger/clients/sui.ts +130 -0
  19. package/src/ledger/clients/thorchain/common.ts +93 -0
  20. package/src/ledger/clients/thorchain/helpers.ts +120 -0
  21. package/src/ledger/clients/thorchain/index.ts +87 -0
  22. package/src/ledger/clients/thorchain/lib.ts +258 -0
  23. package/src/ledger/clients/thorchain/utils.ts +69 -0
  24. package/src/ledger/clients/tron.ts +85 -0
  25. package/src/ledger/clients/utxo-legacy-adapter.ts +71 -0
  26. package/src/ledger/clients/utxo-psbt.ts +145 -0
  27. package/src/ledger/clients/utxo.ts +359 -0
  28. package/src/ledger/clients/xrp.ts +50 -0
  29. package/src/ledger/cosmosTypes.ts +98 -0
  30. package/src/ledger/helpers/getLedgerAddress.ts +76 -0
  31. package/src/ledger/helpers/getLedgerClient.ts +124 -0
  32. package/src/ledger/helpers/getLedgerTransport.ts +102 -0
  33. package/src/ledger/helpers/index.ts +3 -0
  34. package/src/ledger/index.ts +546 -0
  35. package/src/ledger/interfaces/CosmosLedgerInterface.ts +54 -0
  36. package/src/ledger/types.ts +42 -0
  37. package/src/trezor/evmSigner.ts +210 -0
  38. package/src/trezor/index.ts +847 -0
@@ -0,0 +1,69 @@
1
+ import { base64 } from "@scure/base";
2
+ import { SwapKitError } from "@swapkit/helpers";
3
+
4
+ export const getSignature = (signatureArray: any) => {
5
+ // Check Type Length Value encoding
6
+ if (signatureArray.length < 64) {
7
+ throw new SwapKitError("wallet_ledger_invalid_signature", { reason: "Too short" });
8
+ }
9
+ if (signatureArray[0] !== 0x30) {
10
+ throw new SwapKitError("wallet_ledger_invalid_signature", { reason: "TLV encoding: expected first byte 0x30" });
11
+ }
12
+ if (signatureArray[1] + 2 !== signatureArray.length) {
13
+ throw new SwapKitError("wallet_ledger_invalid_signature", { reason: "signature length does not match TLV" });
14
+ }
15
+ if (signatureArray[2] !== 0x02) {
16
+ throw new SwapKitError("wallet_ledger_invalid_signature", { reason: "TLV encoding: expected length type 0x02" });
17
+ }
18
+
19
+ // r signature
20
+ const rLength = signatureArray[3];
21
+ let rSignature = signatureArray.slice(4, rLength + 4);
22
+
23
+ // Drop leading zero on some 'r' signatures that are 33 bytes.
24
+ if (rSignature.length === 33 && rSignature[0] === 0) {
25
+ rSignature = rSignature.slice(1, 33);
26
+ } else if (rSignature.length === 33) {
27
+ throw new SwapKitError("wallet_ledger_invalid_signature", { reason: "r too long" });
28
+ }
29
+
30
+ // add leading zero's to pad to 32 bytes
31
+ while (rSignature.length < 32) {
32
+ rSignature.unshift(0);
33
+ }
34
+
35
+ // s signature
36
+ if (signatureArray[rLength + 4] !== 0x02) {
37
+ throw new SwapKitError("wallet_ledger_invalid_signature", {
38
+ reason: "TLV encoding: expected length type 0x02 for s",
39
+ });
40
+ }
41
+
42
+ const sLength = signatureArray[rLength + 5];
43
+
44
+ if (4 + rLength + 2 + sLength !== signatureArray.length) {
45
+ throw new SwapKitError("wallet_ledger_invalid_signature", {
46
+ reason: "TLV byte lengths do not match message length",
47
+ });
48
+ }
49
+
50
+ let sSignature = signatureArray.slice(rLength + 6, signatureArray.length);
51
+
52
+ // Drop leading zero on 's' signatures that are 33 bytes. This shouldn't occur since ledger signs using "Small s" math. But just to be sure...
53
+ if (sSignature.length === 33 && sSignature[0] === 0) {
54
+ sSignature = sSignature.slice(1, 33);
55
+ } else if (sSignature.length === 33) {
56
+ throw new SwapKitError("wallet_ledger_invalid_signature", { reason: "s too long" });
57
+ }
58
+
59
+ // add leading zero's to pad to 32 bytes
60
+ while (sSignature.length < 32) {
61
+ sSignature.unshift(0);
62
+ }
63
+
64
+ if (rSignature.length !== 32 || sSignature.length !== 32) {
65
+ throw new SwapKitError("wallet_ledger_invalid_signature", { reason: "must be 32 bytes each" });
66
+ }
67
+
68
+ return base64.encode(Buffer.concat([rSignature, sSignature]));
69
+ };
@@ -0,0 +1,85 @@
1
+ import type TronApp from "@ledgerhq/hw-app-trx";
2
+ import {
3
+ type DerivationPathArray,
4
+ derivationPathToString,
5
+ NetworkDerivationPath,
6
+ SwapKitError,
7
+ } from "@swapkit/helpers";
8
+ import type { TronSignedTransaction, TronSigner, TronTransaction } from "@swapkit/toolboxes/tron";
9
+
10
+ import { getLedgerTransport } from "../helpers/getLedgerTransport";
11
+
12
+ export class TronLedgerInterface implements TronSigner {
13
+ derivationPath: string;
14
+ ledgerApp: InstanceType<typeof TronApp> | null = null;
15
+ ledgerTimeout = 50000;
16
+
17
+ constructor(derivationPath?: DerivationPathArray | string) {
18
+ this.derivationPath =
19
+ typeof derivationPath === "string"
20
+ ? derivationPath
21
+ : derivationPathToString(derivationPath || NetworkDerivationPath.TRON);
22
+ }
23
+
24
+ checkOrCreateTransportAndLedger = async () => {
25
+ if (this.ledgerApp) return;
26
+ await this.createTransportAndLedger();
27
+ };
28
+
29
+ createTransportAndLedger = async () => {
30
+ const transport = await getLedgerTransport();
31
+ const TronApp = (await import("@ledgerhq/hw-app-trx")).default;
32
+
33
+ this.ledgerApp = new TronApp(transport);
34
+ };
35
+
36
+ getAddress = async (): Promise<string> => {
37
+ const response = await this.getAddressAndPubKey();
38
+ if (!response) throw new SwapKitError("wallet_ledger_failed_to_get_address");
39
+ return response.address;
40
+ };
41
+
42
+ getAddressAndPubKey = async () => {
43
+ await this.createTransportAndLedger();
44
+ const result = await this.ledgerApp?.getAddress(this.derivationPath);
45
+
46
+ if (!result) throw new SwapKitError("wallet_ledger_failed_to_get_address");
47
+
48
+ return { address: result.address, publicKey: result.publicKey };
49
+ };
50
+
51
+ showAddressAndPubKey = async () => {
52
+ await this.createTransportAndLedger();
53
+ return this.ledgerApp?.getAddress(this.derivationPath, true);
54
+ };
55
+
56
+ signTransaction = async (transaction: TronTransaction): Promise<TronSignedTransaction> => {
57
+ await this.createTransportAndLedger();
58
+
59
+ if (!this.ledgerApp) {
60
+ throw new SwapKitError("wallet_ledger_transport_error");
61
+ }
62
+
63
+ // Tron transactions need to be serialized before signing
64
+ const serializedTx = JSON.stringify(transaction);
65
+
66
+ try {
67
+ const signature = await this.ledgerApp.signTransaction(
68
+ this.derivationPath,
69
+ serializedTx,
70
+ [], // Token signatures array - empty for native TRX transfers
71
+ );
72
+
73
+ if (!signature) {
74
+ throw new SwapKitError("wallet_ledger_signing_error");
75
+ }
76
+
77
+ // Return the signed transaction in Tron's expected format
78
+ return { ...transaction, signature: [signature] };
79
+ } catch (error) {
80
+ throw new SwapKitError("wallet_ledger_signing_error", { error });
81
+ }
82
+ };
83
+ }
84
+
85
+ export const TronLedger = (derivationPath?: DerivationPathArray) => new TronLedgerInterface(derivationPath);
@@ -0,0 +1,71 @@
1
+ import { hex } from "@scure/base";
2
+ import type { UTXOChain } from "@swapkit/helpers";
3
+ import type { UTXOType } from "@swapkit/toolboxes/utxo";
4
+ import type { Transaction } from "@swapkit/utxo-signer";
5
+
6
+ /**
7
+ * Extract per-input metadata from a V3 PSBT in the shape the legacy
8
+ * `@ledgerhq/hw-app-btc.createPaymentTransaction` adapter expects.
9
+ *
10
+ * For segwit inputs the SwapKit V3 API populates `witnessUtxo`; for legacy
11
+ * (BCH/DOGE/DASH) it populates `nonWitnessUtxo` with the full prior-tx bytes.
12
+ * We re-encode the parsed `nonWitnessUtxo` back to hex via `RawTx.encode` so
13
+ * `btcApp.splitTransaction(hex)` can consume it.
14
+ *
15
+ * Single-address account assumption: all inputs share our derivation path.
16
+ */
17
+ export async function extractInputsFromPsbt(tx: Transaction): Promise<UTXOType[]> {
18
+ const { RawTx } = await import("@swapkit/utxo-signer");
19
+ const inputs: UTXOType[] = [];
20
+
21
+ for (let i = 0; i < tx.inputsLength; i++) {
22
+ const input = tx.getInput(i);
23
+
24
+ if (!input.txid || input.index === undefined) {
25
+ throw new Error(`PSBT input ${i} is missing txid/index`);
26
+ }
27
+
28
+ const txHex = input.nonWitnessUtxo ? hex.encode(RawTx.encode(input.nonWitnessUtxo)) : "";
29
+ const witnessUtxo = input.witnessUtxo
30
+ ? { script: input.witnessUtxo.script, value: Number(input.witnessUtxo.amount) }
31
+ : undefined;
32
+
33
+ inputs.push({
34
+ hash: hex.encode(input.txid),
35
+ index: input.index,
36
+ txHex,
37
+ value: witnessUtxo?.value ?? 0,
38
+ witnessUtxo,
39
+ } as UTXOType);
40
+ }
41
+
42
+ return inputs;
43
+ }
44
+
45
+ /**
46
+ * Build a toolbox-compatible signer from the existing legacy Ledger UTXO
47
+ * client. The toolbox synthesizes `signAndBroadcastTransaction` on top of
48
+ * `signer.signTransaction(tx) → Transaction`.
49
+ */
50
+ export function createLegacyPsbtSigner({
51
+ legacyClient,
52
+ chain: _chain,
53
+ address,
54
+ }: {
55
+ legacyClient: { signTransaction: (tx: Transaction, inputUtxos: UTXOType[]) => Promise<string> };
56
+ chain: UTXOChain;
57
+ address: string;
58
+ }) {
59
+ return {
60
+ getAddress: async () => address,
61
+ signTransaction: async (tx: Transaction): Promise<Transaction> => {
62
+ const inputUtxos = await extractInputsFromPsbt(tx);
63
+ const signedTxHex = await legacyClient.signTransaction(tx, inputUtxos);
64
+
65
+ const { Transaction: TxClass } = await import("@swapkit/utxo-signer");
66
+ // `Transaction.fromRaw` parses a serialised tx (no PSBT envelope) — exactly
67
+ // what `createPaymentTransaction` returns.
68
+ return TxClass.fromRaw(hex.decode(signedTxHex));
69
+ },
70
+ };
71
+ }
@@ -0,0 +1,145 @@
1
+ import { base64 } from "@scure/base";
2
+ import { HDKey } from "@scure/bip32";
3
+ import { type DerivationPathArray, derivationPathToString, getWalletFormatFor, SwapKitError } from "@swapkit/helpers";
4
+ import type { Transaction } from "@swapkit/utxo-signer";
5
+
6
+ import { getLedgerTransport } from "../helpers/getLedgerTransport";
7
+
8
+ type SupportedCoin = "bitcoin" | "litecoin";
9
+
10
+ type DefaultDescriptorTemplate = "wpkh(@0/**)" | "tr(@0/**)" | "sh(wpkh(@0/**))" | "pkh(@0/**)";
11
+
12
+ function templateForFormat(format: ReturnType<typeof getWalletFormatFor>): DefaultDescriptorTemplate {
13
+ switch (format) {
14
+ case "bech32":
15
+ return "wpkh(@0/**)";
16
+ case "p2sh":
17
+ return "sh(wpkh(@0/**))";
18
+ case "legacy":
19
+ return "pkh(@0/**)";
20
+ default:
21
+ return "wpkh(@0/**)";
22
+ }
23
+ }
24
+
25
+ function pathToString(path: DerivationPathArray | string): string {
26
+ return typeof path === "string" ? path : derivationPathToString(path);
27
+ }
28
+
29
+ function pathToNumberArray(path: string): number[] {
30
+ return path
31
+ .replace(/^m\//, "")
32
+ .split("/")
33
+ .filter(Boolean)
34
+ .map((p) => {
35
+ const hardened = p.endsWith("'");
36
+ const num = Number.parseInt(hardened ? p.slice(0, -1) : p, 10);
37
+ return hardened ? (num | 0x80000000) >>> 0 : num;
38
+ });
39
+ }
40
+
41
+ const BaseLedgerPsbtUTXO = ({ chain }: { chain: SupportedCoin }) => {
42
+ let appClient: import("ledger-bitcoin").AppClient | undefined;
43
+ let masterFingerprint: string | undefined;
44
+
45
+ async function getAppClient() {
46
+ if (!appClient) {
47
+ const transport = await getLedgerTransport();
48
+ const { AppClient } = await import("ledger-bitcoin");
49
+ appClient = new AppClient(transport);
50
+ }
51
+ return appClient;
52
+ }
53
+
54
+ async function getFingerprint() {
55
+ if (!masterFingerprint) {
56
+ const app = await getAppClient();
57
+ masterFingerprint = await app.getMasterFingerprint();
58
+ }
59
+ return masterFingerprint;
60
+ }
61
+
62
+ return (derivationPathArray?: DerivationPathArray | string) => {
63
+ // Single-address account: change == index == 0 by default.
64
+ const derivationPath = derivationPathArray ? pathToString(derivationPathArray) : "84'/0'/0'/0/0";
65
+ const accountPath = derivationPath.split("/").slice(0, 3).join("/");
66
+ const leafSegments = derivationPath.split("/").slice(3);
67
+ const change = Number(leafSegments[0] ?? 0);
68
+ const addressIndex = Number(leafSegments[1] ?? 0);
69
+ const format = getWalletFormatFor(derivationPath);
70
+ const template = templateForFormat(format);
71
+
72
+ let cachedAccountXpub: string | undefined;
73
+ let cachedLeafPubkey: Uint8Array | undefined;
74
+
75
+ async function buildPolicy() {
76
+ const app = await getAppClient();
77
+ const fpr = await getFingerprint();
78
+ if (!cachedAccountXpub) {
79
+ cachedAccountXpub = await app.getExtendedPubkey(`m/${accountPath}`);
80
+ }
81
+ const { DefaultWalletPolicy } = await import("ledger-bitcoin");
82
+ const policy = new DefaultWalletPolicy(template, `[${fpr}/${accountPath}]${cachedAccountXpub}`);
83
+ return { app, fpr, policy, xpub: cachedAccountXpub };
84
+ }
85
+
86
+ async function getLeafPubkey() {
87
+ if (!cachedLeafPubkey) {
88
+ const { xpub } = await buildPolicy();
89
+ const accountKey = HDKey.fromExtendedKey(xpub);
90
+ const leaf = accountKey.derive(`m/${change}/${addressIndex}`);
91
+ if (!leaf.publicKey) {
92
+ throw new SwapKitError("wallet_ledger_get_address_error", {
93
+ message: `Cannot derive leaf pubkey for ${chain}`,
94
+ });
95
+ }
96
+ cachedLeafPubkey = leaf.publicKey;
97
+ }
98
+ return cachedLeafPubkey;
99
+ }
100
+
101
+ return {
102
+ connect: async () => {
103
+ await getAppClient();
104
+ },
105
+ getAddress: async () => {
106
+ const { app, policy } = await buildPolicy();
107
+ const address = await app.getWalletAddress(policy, null, change, addressIndex, false);
108
+ if (!address) {
109
+ throw new SwapKitError("wallet_ledger_get_address_error", {
110
+ message: `Cannot get ${chain} address from ledger derivation path: ${derivationPath}`,
111
+ });
112
+ }
113
+ return address;
114
+ },
115
+ getExtendedPublicKey: async (path = `m/${accountPath}`) => {
116
+ const app = await getAppClient();
117
+ return app.getExtendedPubkey(path);
118
+ },
119
+ signTransaction: async (tx: Transaction): Promise<Transaction> => {
120
+ const { app, policy, fpr } = await buildPolicy();
121
+ const fingerprintBE = Number.parseInt(fpr, 16) >>> 0;
122
+ const pathNumbers = pathToNumberArray(derivationPath);
123
+ const leafPubkey = await getLeafPubkey();
124
+
125
+ // Single-address account: every input is owned by the same key + path.
126
+ for (let i = 0; i < tx.inputsLength; i++) {
127
+ tx.updateInput(i, { bip32Derivation: [[leafPubkey, { fingerprint: fingerprintBE, path: pathNumbers }]] });
128
+ }
129
+
130
+ const psbtB64 = base64.encode(tx.toPSBT(0));
131
+ const sigs = await app.signPsbt(psbtB64, policy, null);
132
+
133
+ for (const [idx, partial] of sigs) {
134
+ tx.updateInput(idx, { partialSig: [[new Uint8Array(partial.pubkey), new Uint8Array(partial.signature)]] });
135
+ }
136
+
137
+ tx.finalize();
138
+ return tx;
139
+ },
140
+ };
141
+ };
142
+ };
143
+
144
+ export const BitcoinPsbtLedger = BaseLedgerPsbtUTXO({ chain: "bitcoin" });
145
+ export const LitecoinPsbtLedger = BaseLedgerPsbtUTXO({ chain: "litecoin" });