@exodus/hw-ledger 1.3.2 → 2.0.0

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,30 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [2.0.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hw-ledger@1.3.2...@exodus/hw-ledger@2.0.0) (2023-12-19)
7
+
8
+ ### ⚠ BREAKING CHANGES
9
+
10
+ - simplify transaction signing api for ledger (#4979)
11
+
12
+ ### Features
13
+
14
+ - add nested segwit to bitcoin ([#4981](https://github.com/ExodusMovement/exodus-hydra/issues/4981)) ([3721b60](https://github.com/ExodusMovement/exodus-hydra/commit/3721b607ea5176ad8461d4b97ac803e179f8a48f))
15
+ - add psbt tagging & sanitization to ledger ([#5059](https://github.com/ExodusMovement/exodus-hydra/issues/5059)) ([9e38c3b](https://github.com/ExodusMovement/exodus-hydra/commit/9e38c3b33c6beb67b1feee6eaf939c5a500da94a))
16
+ - simplify transaction signing api for ledger ([#4979](https://github.com/ExodusMovement/exodus-hydra/issues/4979)) ([30b1d45](https://github.com/ExodusMovement/exodus-hydra/commit/30b1d45602614c8b37cff926acaf830142fbf197))
17
+
18
+ ### Bug Fixes
19
+
20
+ - bump ledger packages ([#4990](https://github.com/ExodusMovement/exodus-hydra/issues/4990)) ([a3f60fc](https://github.com/ExodusMovement/exodus-hydra/commit/a3f60fc6dca2c0bbbdb37e6446d3b4f4438e2753))
21
+ - change polygon asset name to matic ([#4955](https://github.com/ExodusMovement/exodus-hydra/issues/4955)) ([e3d5ec3](https://github.com/ExodusMovement/exodus-hydra/commit/e3d5ec3e0d7e3f1a31082ca7bb77a13bb0ca3207))
22
+
23
+ ## [1.3.3](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hw-ledger@1.3.2...@exodus/hw-ledger@1.3.3) (2023-12-11)
24
+
25
+ ### Bug Fixes
26
+
27
+ - bump ledger packages ([#4990](https://github.com/ExodusMovement/exodus-hydra/issues/4990)) ([a3f60fc](https://github.com/ExodusMovement/exodus-hydra/commit/a3f60fc6dca2c0bbbdb37e6446d3b4f4438e2753))
28
+ - change polygon asset name to matic ([#4955](https://github.com/ExodusMovement/exodus-hydra/issues/4955)) ([e3d5ec3](https://github.com/ExodusMovement/exodus-hydra/commit/e3d5ec3e0d7e3f1a31082ca7bb77a13bb0ca3207))
29
+
6
30
  ## [1.3.2](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hw-ledger@1.3.1...@exodus/hw-ledger@1.3.2) (2023-12-05)
7
31
 
8
32
  ### Bug Fixes
package/README.md CHANGED
@@ -59,29 +59,40 @@ If you're on Linux then it's highly likely that you'll want to setup your udev r
59
59
 
60
60
  ### Installing Ledger Emulator (Recommended)
61
61
 
62
- Get the Docker images
62
+ Clone the speculos repository & change your current working directory
63
63
 
64
64
  ```bash
65
- docker pull ghcr.io/ledgerhq/speculos
66
- docker image tag ghcr.io/ledgerhq/speculos speculos
65
+ git clone https://github.com/LedgerHQ/speculos.git --branch v0.3.5 --single-branch --depth 1 speculos-0.3.5
66
+ cd speculos-0.3.5
67
67
  ```
68
68
 
69
- Clone the speculos repository & change your current working directory
69
+ Get the Docker image
70
+
71
+ ```bash
72
+ docker image pull ghcr.io/ledgerhq/speculos:sha-8241dc2 # v0.3.5
73
+ docker image tag ghcr.io/ledgerhq/speculos:sha-8241dc2 speculos
74
+ ```
75
+
76
+ Get the Docker image for ARM (M1/M2)
70
77
 
71
78
  ```bash
72
- git clone https://github.com/LedgerHQ/speculos.git
73
- cd speculos
79
+ docker image build -t ghcr.io/ledgerhq/speculos-builder:latest -f build.Dockerfile .
80
+ docker image build -t speculos:latest .
74
81
  ```
75
82
 
76
83
  Launch the emulator while within the `speculos` directory
77
84
 
78
85
  ```bash
79
- docker run --rm -it -v "$(pwd)"/apps:/speculos/apps \
80
- -p 1234:1234 -p 5000:5000 -p 40000:40000 -p 41000:41000 speculos \
81
- --model nanos ./apps/btc.elf --sdk 2.0 --seed "secret" --display headless --apdu-port 40000 --vnc-port 41000
86
+ docker container run --rm -it -v "$(pwd)"/apps:/speculos/apps:ro \
87
+ -p 127.0.0.1:4000:4000 -p 127.0.0.1:40000:40000 speculos \
88
+ --display headless --api-port 4000 --apdu-port 40000 --seed "secret" --model nanos --sdk 2.0 ./apps/btc.elf
82
89
  ```
83
90
 
84
- Now visit http://127.0.0.1:5000 to interact with the emulator.
91
+ Now visit http://127.0.0.1:4000 to interact with the emulator.
92
+
93
+ ### MacOS launch issues:
94
+
95
+ - `port XXXX is already in use`: re-map any available host port ([doc](https://docs.docker.com/network/#published-ports))
85
96
 
86
97
  ### Install udev rules for real devices (Linux only)
87
98
 
@@ -1,45 +1,69 @@
1
1
  import bs58 from 'bs58';
2
- import { AppClient, DefaultWalletPolicy, PsbtV2 } from '@exodus/ledger-bitcoin';
2
+ import { AppClient, DefaultWalletPolicy, PsbtV2, } from '@exodus/ledger-bitcoin';
3
3
  import { splitBip44DerivationPath } from './utils';
4
4
  import assert from 'minimalistic-assert';
5
+ import { assertPsbtOnlyHasAllowedDerivationPaths, tagPsbtWithDerivationPaths } from './utils/psbt';
5
6
  async function createHandler(transport) {
6
7
  const app = new AppClient(transport);
7
8
  const getWalletPolicy = async (purpose, coin, account) => {
8
9
  const fpr = await app.getMasterFingerprint();
9
10
  const xpub = await app.getExtendedPubkey(`m/${purpose}/${coin}/${account}`);
10
- const desc = { "44'": 'pkh', "84'": 'wpkh', "86'": 'tr' }[purpose];
11
- return new DefaultWalletPolicy(`${desc}(@0/**)`, `[${fpr}/${purpose}/${coin}/${account}]${xpub}`);
11
+ const desc = {
12
+ "44'": 'pkh(@0/**)',
13
+ "49'": 'sh(wpkh(@0/**))',
14
+ "84'": 'wpkh(@0/**)',
15
+ "86'": 'tr(@0/**)',
16
+ }[purpose];
17
+ return new DefaultWalletPolicy(desc, `[${fpr}/${purpose}/${coin}/${account}]${xpub}`);
18
+ };
19
+ const getAddress = async (params) => {
20
+ const [purpose, coin, account, change, address] = splitBip44DerivationPath(params.derivationPath);
21
+ const walletPolicy = await getWalletPolicy(purpose, coin, account);
22
+ return app.getWalletAddress(walletPolicy, null, Number(change), Number(address), false);
23
+ };
24
+ const getXPub = async (params) => {
25
+ return app.getExtendedPubkey(params.derivationPath);
26
+ };
27
+ const getPublicKey = async (params) => {
28
+ return bs58.decode(await app.getExtendedPubkey(params.derivationPath)).slice(45, 45 + 33);
29
+ };
30
+ const signTransaction = async (params) => {
31
+ assert(Buffer.isBuffer(params.signableTransaction), 'signableTransaction must be PSBT buffer');
32
+ const psbtBuffer = params.signableTransaction;
33
+ const psbt = new PsbtV2();
34
+ psbt.deserialize(psbtBuffer);
35
+ const [purpose, coin, account] = splitBip44DerivationPath(params.derivationPaths[0]);
36
+ const fpr = await app.getMasterFingerprint();
37
+ const walletPolicy = await getWalletPolicy(purpose, coin, account);
38
+ const derivationPathToPublicKeyMap = Object.create(null);
39
+ for (const derivationPath of params.derivationPaths) {
40
+ derivationPathToPublicKeyMap[derivationPath] = await getPublicKey({
41
+ assetName: 'bitcoin',
42
+ derivationPath,
43
+ });
44
+ }
45
+ tagPsbtWithDerivationPaths(psbt, fpr, derivationPathToPublicKeyMap);
46
+ assertPsbtOnlyHasAllowedDerivationPaths(psbt, fpr, params.derivationPaths);
47
+ const signatures = await app.signPsbt(psbt.serialize(), walletPolicy, null);
48
+ const result = signatures.map(([inputIndex, partialSignature]) => ({
49
+ inputIndex,
50
+ publicKey: partialSignature.pubkey,
51
+ signature: partialSignature.signature,
52
+ tapleafHash: partialSignature.tapleafHash,
53
+ }));
54
+ return result;
55
+ };
56
+ const signMessage = async (params) => {
57
+ assert(Buffer.isBuffer(params.message.rawMessage), 'rawMessage must be buffer');
58
+ const signedMessageHex = await app.signMessage(params.message.rawMessage, params.derivationPath);
59
+ return Buffer.from(signedMessageHex, 'base64');
12
60
  };
13
61
  return {
14
- getAddress: async (params) => {
15
- const [purpose, coin, account, change, address] = splitBip44DerivationPath(params.derivationPath);
16
- const walletPolicy = await getWalletPolicy(purpose, coin, account);
17
- return app.getWalletAddress(walletPolicy, null, Number(change), Number(address), false);
18
- },
19
- getXPub: async (params) => {
20
- return app.getExtendedPubkey(params.derivationPath);
21
- },
22
- getPublicKey: async (params) => {
23
- return bs58.decode(await app.getExtendedPubkey(params.derivationPath)).slice(45, 45 + 33);
24
- },
25
- signTransaction: async (params) => {
26
- assert(Buffer.isBuffer(params.unsignedTx.txData), 'unsignedTx.txData must be PSBT buffer');
27
- const psbtBuffer = params.unsignedTx.txData;
28
- const psbt = new PsbtV2();
29
- psbt.deserialize(psbtBuffer);
30
- const [purpose, coin, account] = splitBip44DerivationPath(params.derivationPaths[0]);
31
- const walletPolicy = await getWalletPolicy(purpose, coin, account);
32
- const signatures = await app.signPsbt(psbtBuffer, walletPolicy, null);
33
- for (const [inputIndex, partialSignature] of signatures) {
34
- psbt.setInputPartialSig(inputIndex, partialSignature.pubkey, partialSignature.signature);
35
- }
36
- return psbt.serialize();
37
- },
38
- signMessage: async (params) => {
39
- assert(Buffer.isBuffer(params.message.rawMessage), 'rawMessage must be buffer');
40
- const signedMessageHex = await app.signMessage(params.message.rawMessage, params.derivationPath);
41
- return Buffer.from(signedMessageHex, 'base64');
42
- },
62
+ getAddress,
63
+ getPublicKey,
64
+ getXPub,
65
+ signTransaction,
66
+ signMessage,
43
67
  };
44
68
  }
45
69
  const metadata = {
@@ -44,10 +44,8 @@ async function createHandler(transport) {
44
44
  },
45
45
  signTransaction: async (params) => {
46
46
  assert(Array.isArray(params.derivationPaths) && params.derivationPaths.length === 1, 'derivationsPath array must be one element long');
47
- assert(params.unsignedTx.txData, 'txData must be defined');
48
- const deserializedTransaction = Buffer.isBuffer(params.unsignedTx.txData)
49
- ? ethers.parse(params.unsignedTx.txData)
50
- : params.unsignedTx.txData;
47
+ assert(Buffer.isBuffer(params.signableTransaction), 'signableTransaction must be Buffer');
48
+ const deserializedTransaction = ethers.parse(params.signableTransaction);
51
49
  delete deserializedTransaction.r;
52
50
  delete deserializedTransaction.s;
53
51
  delete deserializedTransaction.v;
@@ -80,13 +78,16 @@ async function createHandler(transport) {
80
78
  throw err;
81
79
  }
82
80
  }
83
- const { r, s, v } = response;
84
- const serializedSignedTransaction = ethers.serialize(deserializedTransaction, {
85
- r: '0x' + r,
86
- s: '0x' + s,
87
- v: Number.parseInt(v, 16),
88
- });
89
- return Buffer.from(serializedSignedTransaction.slice(2), 'hex');
81
+ const { v, r, s } = response;
82
+ return [
83
+ {
84
+ signature: Buffer.concat([
85
+ Buffer.from(v, 'hex'),
86
+ Buffer.from(r, 'hex'),
87
+ Buffer.from(s, 'hex'),
88
+ ]),
89
+ },
90
+ ];
90
91
  },
91
92
  signMessage: async (params) => {
92
93
  const message = params.message;
@@ -1,11 +1,11 @@
1
1
  import bitcoin from './bitcoin';
2
2
  import ethereum from './ethereum';
3
- import polygon from './polygon';
3
+ import matic from './matic';
4
4
  import solana from './solana';
5
5
  export const assetApplications = {
6
6
  bitcoin,
7
7
  ethereum,
8
- polygon,
8
+ matic,
9
9
  solana,
10
10
  };
11
11
  export const supportedBaseAssetNames = Object.keys(assetApplications);
@@ -4,28 +4,52 @@ import bs58 from 'bs58';
4
4
  import { XPubUnsupportedError } from '@exodus/hw-common';
5
5
  async function createHandler(transport) {
6
6
  const app = new AppSolana(transport);
7
+ const getAddress = async (params) => {
8
+ const { address } = await app.getAddress(params.derivationPath.replace('m/', ''));
9
+ return bs58.encode(address);
10
+ };
11
+ const getXPub = async (params) => {
12
+ throw new XPubUnsupportedError();
13
+ };
14
+ const getPublicKey = async (params) => {
15
+ const { address } = await app.getAddress(params.derivationPath.replace('m/', ''));
16
+ return address;
17
+ };
18
+ const signTransaction = async (params) => {
19
+ assert(Array.isArray(params.derivationPaths) && params.derivationPaths.length === 1, 'derivationsPath array must be one element long');
20
+ let serializedTransaction;
21
+ if (Buffer.isBuffer(params.signableTransaction)) {
22
+ serializedTransaction = params.signableTransaction;
23
+ }
24
+ else if (params.signableTransaction instanceof Uint8Array) {
25
+ serializedTransaction = Buffer.from(params.signableTransaction);
26
+ }
27
+ else if (params.signableTransaction.serialize) {
28
+ serializedTransaction = Buffer.from(params.signableTransaction.serialize());
29
+ }
30
+ if (!Buffer.isBuffer(serializedTransaction)) {
31
+ throw new TypeError(`signableTransaction must be Buffer or serializable to a Buffer.`);
32
+ }
33
+ const { signature } = await app.signTransaction(params.derivationPaths[0].replace('m/', ''), serializedTransaction);
34
+ const { address: publicKey } = await app.getAddress(params.derivationPaths[0].replace('m/', ''));
35
+ return [
36
+ {
37
+ publicKey,
38
+ signature,
39
+ },
40
+ ];
41
+ };
42
+ const signMessage = async (params) => {
43
+ assert(Buffer.isBuffer(params.message.rawMessage), 'rawMessage must be a buffer');
44
+ const { signature } = await app.signOffchainMessage(params.derivationPath.replace('m/', ''), params.message.rawMessage);
45
+ return signature;
46
+ };
7
47
  return {
8
- getAddress: async (params) => {
9
- const { address } = await app.getAddress(params.derivationPath.replace('m/', ''));
10
- return bs58.encode(address);
11
- },
12
- getXPub: async (params) => {
13
- throw new XPubUnsupportedError();
14
- },
15
- getPublicKey: async (params) => {
16
- const { address } = await app.getAddress(params.derivationPath.replace('m/', ''));
17
- return address;
18
- },
19
- signTransaction: async (params) => {
20
- assert(Array.isArray(params.derivationPaths) && params.derivationPaths.length === 1, 'derivationsPath array must be one element long');
21
- const { signature } = await app.signTransaction(params.derivationPaths[0].replace('m/', ''), params.unsignedTx.txData);
22
- return signature;
23
- },
24
- signMessage: async (params) => {
25
- assert(Buffer.isBuffer(params.message.rawMessage), 'rawMessage must be a buffer');
26
- const { signature } = await app.signOffchainMessage(params.derivationPath.replace('m/', ''), params.message.rawMessage);
27
- return signature;
28
- },
48
+ getAddress,
49
+ getPublicKey,
50
+ getXPub,
51
+ signTransaction,
52
+ signMessage,
29
53
  };
30
54
  }
31
55
  const metadata = {
@@ -0,0 +1,8 @@
1
+ export declare function splitDerivationPath(derivationPath: string): string[];
2
+ export type Bip44PathArray = [string, string, string, string, string];
3
+ export type Bip44PathArrayNumber = [number, number, number, number, number];
4
+ export declare function assertBip44Path(parts: string[]): asserts parts is Bip44PathArray;
5
+ export declare function splitBip44DerivationPath(derivationPath: string): Bip44PathArray;
6
+ export declare function assertBip44PathNumber(parts: readonly number[]): asserts parts is Bip44PathArrayNumber;
7
+ export declare function buildBip44PathFromNumber(parts: Bip44PathArrayNumber): string;
8
+ export declare function splitBip44PathToNumber(derivationPath: string): Bip44PathArrayNumber;
@@ -1,5 +1,4 @@
1
1
  import assert from 'minimalistic-assert';
2
- import bs58check from 'bs58check';
3
2
  export function splitDerivationPath(derivationPath) {
4
3
  assert(typeof derivationPath === 'string', 'derivationPath must be string');
5
4
  const parts = derivationPath.split('/');
@@ -7,7 +6,7 @@ export function splitDerivationPath(derivationPath) {
7
6
  assert(parts[0] === 'm', 'invalid derivationPath: must start with m');
8
7
  return parts.slice(1);
9
8
  }
10
- function assertBip44Path(parts) {
9
+ export function assertBip44Path(parts) {
11
10
  assert(parts.length === 5, 'invalid bip44 derivation path: length must be 5');
12
11
  assert(parts.every((part) => typeof part === 'string'));
13
12
  }
@@ -20,18 +19,23 @@ export function splitBip44DerivationPath(derivationPath) {
20
19
  assert(account.endsWith("'"), 'invalid derivationPath: account must be hardened');
21
20
  return [purpose, coin, account, change, address];
22
21
  }
23
- export function buildFauxXPUB(publicKey, chainCode) {
24
- const LEN = 78;
25
- const XPUB_PREFIX = 76067358;
26
- const buffer = Buffer.allocUnsafe(LEN);
27
- const fingerprint = 0;
28
- const depth = 0;
29
- const index = 0;
30
- buffer.writeUInt32BE(XPUB_PREFIX, 0);
31
- buffer.writeUInt8(depth, 4);
32
- buffer.writeUInt32BE(fingerprint, 5);
33
- buffer.writeUInt32BE(index, 9);
34
- chainCode.copy(buffer, 13);
35
- publicKey.copy(buffer, 45);
36
- return bs58check.encode(buffer);
22
+ export function assertBip44PathNumber(parts) {
23
+ assert(parts.length === 5, 'invalid bip44 derivation path: length must be 5');
24
+ assert(parts.every((part) => typeof part === 'number'));
25
+ }
26
+ export function buildBip44PathFromNumber(parts) {
27
+ const [purpose, coin, account, chainIndex, addressIndex] = parts;
28
+ const HARDENED_OFFSET = 2147483648;
29
+ return `m/${purpose - HARDENED_OFFSET}'/${coin - HARDENED_OFFSET}'/${account - HARDENED_OFFSET}'/${chainIndex}/${addressIndex}`;
30
+ }
31
+ export function splitBip44PathToNumber(derivationPath) {
32
+ const [purpose, coin, account, change, address] = splitBip44DerivationPath(derivationPath);
33
+ const HARDENED_OFFSET = 2147483648;
34
+ return [
35
+ Number(purpose.replaceAll(`'`, '')) + HARDENED_OFFSET,
36
+ Number(coin.replaceAll(`'`, '')) + HARDENED_OFFSET,
37
+ Number(account.replaceAll(`'`, '')) + HARDENED_OFFSET,
38
+ Number(change),
39
+ Number(address),
40
+ ];
37
41
  }
@@ -0,0 +1,3 @@
1
+ export * from './derivation-paths';
2
+ export * from './xpub';
3
+ export * from './psbt';
@@ -0,0 +1,3 @@
1
+ export * from './derivation-paths';
2
+ export * from './xpub';
3
+ export * from './psbt';
@@ -0,0 +1,4 @@
1
+ /// <reference types="node" />
2
+ import { PsbtV2 } from '@exodus/ledger-bitcoin';
3
+ export declare function assertPsbtOnlyHasAllowedDerivationPaths(psbt: PsbtV2, masterFingerprint: string, derivationPaths: string[]): void;
4
+ export declare function tagPsbtWithDerivationPaths(psbt: PsbtV2, masterFingerprint: string, derivationPathToPublicKeyMap: Record<string, Buffer>): void;
@@ -0,0 +1,96 @@
1
+ import assert from 'minimalistic-assert';
2
+ import { payments } from '@exodus/bitcoinjs-lib';
3
+ import { buildBip44PathFromNumber, assertBip44PathNumber, splitBip44PathToNumber, } from './derivation-paths';
4
+ export function assertPsbtOnlyHasAllowedDerivationPaths(psbt, masterFingerprint, derivationPaths) {
5
+ for (let i = 0; i < psbt.getGlobalInputCount(); i++) {
6
+ const BIP32_DERIVATION = 6;
7
+ const TAP_BIP32_DERIVATION = 22;
8
+ const publicKeysNormal = psbt.getInputKeyDatas(i, BIP32_DERIVATION);
9
+ const publicKeysTap = psbt.getInputKeyDatas(i, TAP_BIP32_DERIVATION);
10
+ for (const publicKey of publicKeysNormal) {
11
+ const bip32Derivation = psbt.getInputBip32Derivation(i, publicKey);
12
+ if (bip32Derivation) {
13
+ assertAllowedDerivation(bip32Derivation, masterFingerprint, derivationPaths);
14
+ }
15
+ }
16
+ for (const publicKey of publicKeysTap) {
17
+ const tapBip32Derivation = psbt.getInputTapBip32Derivation(i, publicKey);
18
+ if (tapBip32Derivation) {
19
+ assertAllowedDerivation(tapBip32Derivation, masterFingerprint, derivationPaths);
20
+ }
21
+ }
22
+ }
23
+ }
24
+ function assertAllowedDerivation(bip32Derivation, masterFingerprint, derivationPaths) {
25
+ const masterFingerprintToCheck = bip32Derivation.masterFingerprint.toString('hex');
26
+ const isOurMasterFingerprint = masterFingerprintToCheck === masterFingerprint;
27
+ if (isOurMasterFingerprint) {
28
+ assertBip44PathNumber(bip32Derivation.path);
29
+ const derivationPath = buildBip44PathFromNumber(bip32Derivation.path);
30
+ assert(derivationPaths.includes(derivationPath), `found unsupported derivation path: ${derivationPath}`);
31
+ }
32
+ }
33
+ export function tagPsbtWithDerivationPaths(psbt, masterFingerprint, derivationPathToPublicKeyMap) {
34
+ const masterFingerprintBuffer = Buffer.from(masterFingerprint, 'hex');
35
+ const scriptPubKeyToDerivationPathMap = computeScriptPubKeyToDerivationPathMap(derivationPathToPublicKeyMap);
36
+ for (let inputIndex = 0; inputIndex < psbt.getGlobalInputCount(); inputIndex++) {
37
+ const witnessUtxo = psbt.getInputWitnessUtxo(inputIndex);
38
+ assert(witnessUtxo, `witnessUtxo is currently the only supported type`);
39
+ const scriptPubKey = witnessUtxo.scriptPubKey.toString('hex');
40
+ const derivationPath = scriptPubKeyToDerivationPathMap[scriptPubKey];
41
+ if (derivationPath) {
42
+ const publicKey = derivationPathToPublicKeyMap[derivationPath];
43
+ assert(Buffer.isBuffer(publicKey), `no public key found for derivation path`);
44
+ const derivationPathNumerical = splitBip44PathToNumber(derivationPath);
45
+ if (isTaproot(witnessUtxo.scriptPubKey)) {
46
+ psbt.setInputTapBip32Derivation(inputIndex, publicKey.slice(1), [], masterFingerprintBuffer, derivationPathNumerical);
47
+ }
48
+ else {
49
+ psbt.setInputBip32Derivation(inputIndex, publicKey, masterFingerprintBuffer, derivationPathNumerical);
50
+ }
51
+ }
52
+ }
53
+ for (let outputIndex = 0; outputIndex < psbt.getGlobalOutputCount(); outputIndex++) {
54
+ const scriptPubKey = psbt.getOutputScript(outputIndex);
55
+ const derivationPath = scriptPubKeyToDerivationPathMap[scriptPubKey.toString('hex')];
56
+ if (derivationPath) {
57
+ const publicKey = derivationPathToPublicKeyMap[derivationPath];
58
+ assert(Buffer.isBuffer(publicKey), `no public key found for derivation path`);
59
+ const derivationPathNumerical = splitBip44PathToNumber(derivationPath);
60
+ if (isTaproot(scriptPubKey)) {
61
+ psbt.setOutputTapBip32Derivation(outputIndex, publicKey.slice(1), [], masterFingerprintBuffer, derivationPathNumerical);
62
+ }
63
+ else {
64
+ psbt.setOutputBip32Derivation(outputIndex, publicKey, masterFingerprintBuffer, derivationPathNumerical);
65
+ }
66
+ }
67
+ }
68
+ }
69
+ function computeScriptPubKeyToDerivationPathMap(derivationPathToPublicKeyMap) {
70
+ return Object.entries(derivationPathToPublicKeyMap).reduce((acc, [derivationPath, publicKey]) => {
71
+ if (derivationPath.startsWith('m/84')) {
72
+ const segwitV0 = payments.p2wpkh({
73
+ pubkey: publicKey,
74
+ }).output;
75
+ if (segwitV0) {
76
+ Object.assign(acc, {
77
+ [segwitV0.toString('hex')]: derivationPath,
78
+ });
79
+ }
80
+ }
81
+ else if (derivationPath.startsWith('m/86')) {
82
+ const taproot = payments.p2tr({
83
+ internalPubkey: publicKey.slice(1),
84
+ }).output;
85
+ if (taproot) {
86
+ Object.assign(acc, {
87
+ [taproot.toString('hex')]: derivationPath,
88
+ });
89
+ }
90
+ }
91
+ return acc;
92
+ }, Object.create(null));
93
+ }
94
+ function isTaproot(scriptPubKey) {
95
+ return scriptPubKey.length === 34 && scriptPubKey.at(0) === 0x51 && scriptPubKey.at(1) === 0x20;
96
+ }
@@ -0,0 +1,2 @@
1
+ /// <reference types="node" />
2
+ export declare function buildFauxXPUB(publicKey: Buffer, chainCode: Buffer): string;
@@ -0,0 +1,16 @@
1
+ import bs58check from 'bs58check';
2
+ export function buildFauxXPUB(publicKey, chainCode) {
3
+ const LEN = 78;
4
+ const XPUB_PREFIX = 76067358;
5
+ const buffer = Buffer.allocUnsafe(LEN);
6
+ const fingerprint = 0;
7
+ const depth = 0;
8
+ const index = 0;
9
+ buffer.writeUInt32BE(XPUB_PREFIX, 0);
10
+ buffer.writeUInt8(depth, 4);
11
+ buffer.writeUInt32BE(fingerprint, 5);
12
+ buffer.writeUInt32BE(index, 9);
13
+ chainCode.copy(buffer, 13);
14
+ publicKey.copy(buffer, 45);
15
+ return bs58check.encode(buffer);
16
+ }
@@ -14,6 +14,6 @@ export declare class LedgerDevice implements HardwareWalletDevice {
14
14
  getAddress: (params: GetAddressParams) => Promise<string>;
15
15
  getXPub: (params: GetXPubParams) => Promise<string>;
16
16
  getPublicKey: (params: GetPublicKeyParams) => Promise<Readonly<Uint8Array>>;
17
- signTransaction: (params: SignTransactionParams) => Promise<Readonly<Uint8Array>>;
17
+ signTransaction: (params: SignTransactionParams) => Promise<import("@exodus/hw-common").Signatures>;
18
18
  signMessage: (params: SignMessageParams) => Promise<Readonly<Uint8Array>>;
19
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/hw-ledger",
3
- "version": "1.3.2",
3
+ "version": "2.0.0",
4
4
  "description": "The ledger module",
5
5
  "author": "Exodus Movement Inc.",
6
6
  "repository": {
@@ -26,14 +26,16 @@
26
26
  "zemu:install": "docker pull zondax/builder-zemu:speculos-745fe46f81cfc18114b94a37beaefb58a09c0fcd",
27
27
  "test:integration": "yarn zemu:install && jest --runInBand",
28
28
  "lint": "eslint . --ignore-path ../../.gitignore",
29
- "lint:fix": "yarn lint --fix"
29
+ "lint:fix": "yarn lint --fix",
30
+ "prepublishOnly": "yarn run -T build --scope @exodus/hw-ledger"
30
31
  },
31
32
  "dependencies": {
33
+ "@exodus/bitcoinjs-lib": "^6.1.5-exodus.0",
32
34
  "@exodus/ethersproject-abi": "^5.4.2-exodus.2",
33
35
  "@exodus/ethersproject-transactions": "^5.4.2-exodus.0",
34
- "@exodus/hw-common": "^1.2.1",
36
+ "@exodus/hw-common": "^2.0.0",
35
37
  "@exodus/ledger-bitcoin": "^0.2.3-exodus.0",
36
- "@exodus/ledgerhq-hw-app-eth": "^6.33.7-exodus.0",
38
+ "@exodus/ledgerhq-hw-app-eth": "^6.33.7-exodus.3",
37
39
  "@exodus/secp256k1": "^4.0.2-exodus.0",
38
40
  "@ledgerhq/hw-app-solana": "^7.0.10",
39
41
  "@ledgerhq/hw-transport": "^6.28.5",
@@ -45,11 +47,13 @@
45
47
  },
46
48
  "devDependencies": {
47
49
  "@exodus/bip32": "^2.0.0",
50
+ "@exodus/ledgerhq-devices": "^8.1.0-exodus.0",
48
51
  "@exodus/module": "^1.2.2",
49
52
  "@ledgerhq/hw-transport-node-hid": "^6.27.17",
50
53
  "@ledgerhq/hw-transport-node-speculos": "^6.27.16",
51
54
  "@types/bs58": "^4.0.1",
52
55
  "@types/bs58check": "^2.1.0",
56
+ "@types/create-hash": "^1.2.6",
53
57
  "@types/jest-when": "^3.5.2",
54
58
  "@types/minimalistic-assert": "^1.0.1",
55
59
  "@types/secp256k1": "^4.0.3",
@@ -59,8 +63,9 @@
59
63
  "istanbul-combine": "^0.3.0",
60
64
  "jest": "^29.1.2",
61
65
  "jest-when": "^3.5.2",
66
+ "tiny-secp256k1": "^2.2.3",
62
67
  "ts-jest": "^29.0.3",
63
68
  "typescript": "^4.7.3"
64
69
  },
65
- "gitHead": "d8f26958c3e7cf55de5bc2cbe4f4ae2a075e5051"
70
+ "gitHead": "bd3c7a6093ef581c8c0d694761cde1114a486c75"
66
71
  }
@@ -1,6 +0,0 @@
1
- /// <reference types="node" />
2
- export declare function splitDerivationPath(derivationPath: string): string[];
3
- type Bip44PathArray = [string, string, string, string, string];
4
- export declare function splitBip44DerivationPath(derivationPath: string): Bip44PathArray;
5
- export declare function buildFauxXPUB(publicKey: Buffer, chainCode: Buffer): string;
6
- export {};
File without changes
File without changes