@ledgerhq/hw-app-btc 6.11.1 → 6.15.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.
Files changed (56) hide show
  1. package/README.md +106 -48
  2. package/lib/Btc.d.ts.map +1 -1
  3. package/lib/Btc.js +5 -3
  4. package/lib/Btc.js.map +1 -1
  5. package/lib/BtcNew.d.ts.map +1 -1
  6. package/lib/BtcNew.js +38 -167
  7. package/lib/BtcNew.js.map +1 -1
  8. package/lib/newops/accounttype.d.ts +110 -0
  9. package/lib/newops/accounttype.d.ts.map +1 -0
  10. package/lib/newops/accounttype.js +236 -0
  11. package/lib/newops/accounttype.js.map +1 -0
  12. package/lib/newops/appClient.js +4 -4
  13. package/lib/newops/appClient.js.map +1 -1
  14. package/lib/newops/clientCommands.d.ts.map +1 -1
  15. package/lib/newops/clientCommands.js +14 -9
  16. package/lib/newops/clientCommands.js.map +1 -1
  17. package/lib/newops/merkle.js +2 -2
  18. package/lib/newops/merkle.js.map +1 -1
  19. package/lib/newops/psbtExtractor.js +2 -2
  20. package/lib/newops/psbtExtractor.js.map +1 -1
  21. package/lib/newops/psbtv2.js +4 -4
  22. package/lib/newops/psbtv2.js.map +1 -1
  23. package/lib-es/Btc.d.ts.map +1 -1
  24. package/lib-es/Btc.js +5 -3
  25. package/lib-es/Btc.js.map +1 -1
  26. package/lib-es/BtcNew.d.ts.map +1 -1
  27. package/lib-es/BtcNew.js +41 -170
  28. package/lib-es/BtcNew.js.map +1 -1
  29. package/lib-es/newops/accounttype.d.ts +110 -0
  30. package/lib-es/newops/accounttype.d.ts.map +1 -0
  31. package/lib-es/newops/accounttype.js +233 -0
  32. package/lib-es/newops/accounttype.js.map +1 -0
  33. package/lib-es/newops/appClient.js +4 -4
  34. package/lib-es/newops/appClient.js.map +1 -1
  35. package/lib-es/newops/clientCommands.d.ts.map +1 -1
  36. package/lib-es/newops/clientCommands.js +14 -9
  37. package/lib-es/newops/clientCommands.js.map +1 -1
  38. package/lib-es/newops/merkle.js +2 -2
  39. package/lib-es/newops/merkle.js.map +1 -1
  40. package/lib-es/newops/psbtExtractor.js +2 -2
  41. package/lib-es/newops/psbtExtractor.js.map +1 -1
  42. package/lib-es/newops/psbtv2.js +4 -4
  43. package/lib-es/newops/psbtv2.js.map +1 -1
  44. package/package.json +3 -3
  45. package/src/Btc.ts +34 -3
  46. package/src/BtcNew.ts +64 -166
  47. package/src/newops/accounttype.ts +373 -0
  48. package/src/newops/appClient.ts +4 -4
  49. package/src/newops/clientCommands.ts +15 -9
  50. package/src/newops/merkle.ts +2 -2
  51. package/src/newops/psbtExtractor.ts +2 -2
  52. package/src/newops/psbtv2.ts +4 -4
  53. package/tests/Btc.test.ts +68 -39
  54. package/tests/newops/BtcNew.test.ts +23 -10
  55. package/tests/newops/integrationtools.ts +71 -41
  56. package/tests/newops/merkle.test.ts +1 -1
@@ -9,7 +9,8 @@ import {
9
9
  WalletPolicy
10
10
  } from "../../src/newops/policy";
11
11
  import { PsbtV2 } from "../../src/newops/psbtv2";
12
- import { AccountType, addressFormatFromDescriptorTemplate, creatDummyXpub, masterFingerprint, runSignTransaction, TestingClient } from "./integrationtools";
12
+ import { splitTransaction } from "../../src/splitTransaction";
13
+ import { StandardPurpose, addressFormatFromDescriptorTemplate, creatDummyXpub, masterFingerprint, runSignTransaction, TestingClient } from "./integrationtools";
13
14
  import { CoreInput, CoreTx, p2pkh, p2tr, p2wpkh, wrappedP2wpkh, wrappedP2wpkhTwoInputs } from "./testtx";
14
15
 
15
16
  test("getWalletPublicKey p2pkh", async () => {
@@ -40,7 +41,7 @@ test("getWalletXpub normal path", async () => {
40
41
  await testGetWalletXpub("m/44'/0'/0'");
41
42
  });
42
43
 
43
- function testPaths(type: AccountType): { ins: string[], out?: string } {
44
+ function testPaths(type: StandardPurpose): { ins: string[], out?: string } {
44
45
  const basePath = `m/${type}/1'/0'/`;
45
46
  const ins = [
46
47
  basePath + "0/0",
@@ -55,21 +56,21 @@ function testPaths(type: AccountType): { ins: string[], out?: string } {
55
56
 
56
57
  test("Sign p2pkh", async () => {
57
58
  const changePubkey = "037ed58c914720772c59f7a1e7e76fba0ef95d7c5667119798586301519b9ad2cf";
58
- await runSignTransactionTest(p2pkh, AccountType.p2pkh, changePubkey);
59
+ await runSignTransactionTest(p2pkh, StandardPurpose.p2pkh, changePubkey);
59
60
  });
60
61
  test("Sign p2wpkh wrapped", async () => {
61
62
  let changePubkey = "03efc6b990c1626d08bd176aab0e545a4f55c627c7ddee878d12bbbc46a126177a";
62
- await runSignTransactionTest(wrappedP2wpkh, AccountType.p2wpkhInP2sh, changePubkey);
63
+ await runSignTransactionTest(wrappedP2wpkh, StandardPurpose.p2wpkhInP2sh, changePubkey);
63
64
  changePubkey = "031175a985c56e310ce3496a819229b427a2172920fd20b5972dda62758c6def09";
64
- await runSignTransactionTest(wrappedP2wpkhTwoInputs, AccountType.p2wpkhInP2sh, changePubkey);
65
+ await runSignTransactionTest(wrappedP2wpkhTwoInputs, StandardPurpose.p2wpkhInP2sh, changePubkey);
65
66
  });
66
67
  test("Sign p2wpkh", async () => {
67
- await runSignTransactionTest(p2wpkh, AccountType.p2wpkh);
68
+ await runSignTransactionTest(p2wpkh, StandardPurpose.p2wpkh);
68
69
  });
69
70
  test("Sign p2tr", async () => {
70
71
  // This tx uses locktime, so this test verifies that locktime is propagated to/from
71
72
  // the psbt correctly.
72
- await runSignTransactionTest(p2tr, AccountType.p2tr);
73
+ await runSignTransactionTest(p2tr, StandardPurpose.p2tr);
73
74
  });
74
75
 
75
76
  test("Sign p2tr with sigHashType", async () => {
@@ -79,16 +80,28 @@ test("Sign p2tr with sigHashType", async () => {
79
80
  const sig = input.txinwitness![0] + "83";
80
81
  input.txinwitness = [sig];
81
82
  })
82
- const tx = await runSignTransactionNoVerification(testTx, AccountType.p2tr);
83
+ const tx = await runSignTransactionNoVerification(testTx, StandardPurpose.p2tr);
83
84
  // The verification of the sighashtype is done in MockClient.signPsbt
84
85
  })
85
86
 
86
- async function runSignTransactionTest(testTx: CoreTx, accountType: AccountType, changePubkey?: string) {
87
+ test("Sign p2tr sequence 0", async() => {
88
+ const testTx = JSON.parse(JSON.stringify(p2tr));
89
+ testTx.vin.forEach((input: CoreInput, index: number) => {
90
+ input.sequence = 0;
91
+ })
92
+ const tx = await runSignTransactionNoVerification(testTx, StandardPurpose.p2tr);
93
+ const txObj = splitTransaction(tx, true);
94
+ txObj.inputs.forEach((input, index) => {
95
+ expect(input.sequence.toString("hex")).toEqual("00000000");
96
+ })
97
+ })
98
+
99
+ async function runSignTransactionTest(testTx: CoreTx, accountType: StandardPurpose, changePubkey?: string) {
87
100
  const tx = await runSignTransactionNoVerification(testTx, accountType, changePubkey);
88
101
  expect(tx).toEqual(testTx.hex);
89
102
  }
90
103
 
91
- async function runSignTransactionNoVerification(testTx: CoreTx, accountType: AccountType, changePubkey?: string): Promise<string> {
104
+ async function runSignTransactionNoVerification(testTx: CoreTx, accountType: StandardPurpose, changePubkey?: string): Promise<string> {
92
105
  const [client, transport] = await createClient();
93
106
  const accountXpub = "tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT";
94
107
  client.mockGetPubkeyResponse(`m/${accountType}/1'/0'`, accountXpub);
@@ -9,26 +9,26 @@ import { AddressFormat } from "../../src/getWalletPublicKey";
9
9
  import { AppClient } from "../../src/newops/appClient";
10
10
  import {
11
11
  DefaultDescriptorTemplate,
12
- WalletPolicy
12
+ WalletPolicy,
13
13
  } from "../../src/newops/policy";
14
14
  import { Transaction } from "../../src/types";
15
15
  import { CoreInput, CoreTx, spentTxs } from "./testtx";
16
16
 
17
-
18
17
  export async function runSignTransaction(
19
18
  testTx: CoreTx,
20
- testPaths: { ins: string[], out?: string },
19
+ testPaths: { ins: string[]; out?: string },
21
20
  client: TestingClient,
22
- transport: Transport): Promise<string> {
21
+ transport: Transport
22
+ ): Promise<string> {
23
23
  const btcNew = new BtcNew(client);
24
24
  // btc is needed to perform some functions like splitTransaction.
25
25
  const btc = new Btc(transport);
26
26
  const accountType = getAccountType(testTx.vin[0], btc);
27
27
  const additionals: string[] = [];
28
- if (accountType == AccountType.p2wpkh) {
28
+ if (accountType == StandardPurpose.p2wpkh) {
29
29
  additionals.push("bech32");
30
30
  }
31
- if (accountType == AccountType.p2tr) {
31
+ if (accountType == StandardPurpose.p2tr) {
32
32
  additionals.push("bech32m");
33
33
  }
34
34
  const associatedKeysets: string[] = [];
@@ -37,30 +37,38 @@ export async function runSignTransaction(
37
37
  const path = testPaths.ins[index];
38
38
  associatedKeysets.push(path);
39
39
  const inputData = createInput(input, btc);
40
- const pubkey = getPubkey(index, accountType, testTx, inputData[0], inputData[1]);
40
+ const pubkey = getPubkey(
41
+ index,
42
+ accountType,
43
+ testTx,
44
+ inputData[0],
45
+ inputData[1]
46
+ );
41
47
  const mockXpub = creatDummyXpub(pubkey);
42
48
  client.mockGetPubkeyResponse(path, mockXpub);
43
49
  yieldSigs.set(index, getSignature(input, accountType));
44
50
  return inputData;
45
51
  });
46
52
  const sig0 = yieldSigs.get(0)!;
47
- let sigHashType: number | undefined = sig0.readUInt8(sig0.length - 1)
53
+ let sigHashType: number | undefined = sig0.readUInt8(sig0.length - 1);
48
54
  if (sigHashType == 0x01) {
49
55
  sigHashType = undefined;
50
56
  }
51
57
  client.mockSignPsbt(yieldSigs);
52
58
  const outputWriter = new BufferWriter();
53
59
  outputWriter.writeVarInt(testTx.vout.length);
54
- testTx.vout.forEach(output => {
55
- outputWriter.writeUInt64(BigInt(Number.parseFloat((output.value * 100000000).toFixed(8))));
60
+ testTx.vout.forEach((output) => {
61
+ outputWriter.writeUInt64(
62
+ BigInt(Number.parseFloat((output.value * 100000000).toFixed(8)))
63
+ );
56
64
  outputWriter.writeVarSlice(Buffer.from(output.scriptPubKey.hex, "hex"));
57
65
  });
58
- const outputScriptHex = outputWriter.buffer().toString("hex");
66
+ const outputScriptHex = outputWriter.buffer().toString("hex");
59
67
  let callbacks = "";
60
68
  function logCallback(message: string) {
61
69
  callbacks += new Date().toISOString() + " " + message + "\n";
62
70
  }
63
- const arg: CreateTransactionArg = {
71
+ const arg: CreateTransactionArg = {
64
72
  inputs,
65
73
  additionals,
66
74
  associatedKeysets,
@@ -68,21 +76,22 @@ export async function runSignTransaction(
68
76
  outputScriptHex,
69
77
  lockTime: testTx.locktime,
70
78
  sigHashType,
71
- segwit: accountType != AccountType.p2pkh,
79
+ segwit: accountType != StandardPurpose.p2pkh,
72
80
  onDeviceSignatureGranted: () => logCallback("CALLBACK: signature granted"),
73
- onDeviceSignatureRequested: () => logCallback("CALLBACK: signature requested"),
74
- onDeviceStreaming: (arg) => logCallback("CALLBACK: " + JSON.stringify(arg))
81
+ onDeviceSignatureRequested: () =>
82
+ logCallback("CALLBACK: signature requested"),
83
+ onDeviceStreaming: (arg) => logCallback("CALLBACK: " + JSON.stringify(arg)),
75
84
  };
76
85
  logCallback("Start createPaymentTransactionNew");
77
86
  const tx = await btcNew.createPaymentTransactionNew(arg);
78
87
  logCallback("Done createPaymentTransactionNew");
79
88
  // console.log(callbacks);
80
89
  return tx;
81
- };
82
-
83
-
90
+ }
84
91
 
85
- export function addressFormatFromDescriptorTemplate(descTemp: DefaultDescriptorTemplate): AddressFormat {
92
+ export function addressFormatFromDescriptorTemplate(
93
+ descTemp: DefaultDescriptorTemplate
94
+ ): AddressFormat {
86
95
  if (descTemp == "tr(@0)") return "bech32m";
87
96
  if (descTemp == "pkh(@0)") return "legacy";
88
97
  if (descTemp == "wpkh(@0)") return "bech32";
@@ -90,42 +99,57 @@ export function addressFormatFromDescriptorTemplate(descTemp: DefaultDescriptorT
90
99
  throw new Error();
91
100
  }
92
101
 
93
- export enum AccountType {
102
+ export enum StandardPurpose {
94
103
  p2tr = "86'",
95
104
  p2wpkh = "84'",
96
105
  p2wpkhInP2sh = "49'",
97
- p2pkh = "44'"
106
+ p2pkh = "44'",
98
107
  }
99
108
 
100
- function getPubkey(inputIndex: number, accountType: AccountType, testTx: CoreTx, spentTx: Transaction, spentOutputIndex: number): Buffer {
109
+ function getPubkey(
110
+ inputIndex: number,
111
+ accountType: StandardPurpose,
112
+ testTx: CoreTx,
113
+ spentTx: Transaction,
114
+ spentOutputIndex: number
115
+ ): Buffer {
101
116
  const scriptSig = Buffer.from(testTx.vin[inputIndex].scriptSig.hex, "hex");
102
- if (accountType == AccountType.p2pkh) {
117
+ if (accountType == StandardPurpose.p2pkh) {
103
118
  return scriptSig.slice(scriptSig.length - 33);
104
119
  }
105
- if (accountType == AccountType.p2tr) {
120
+ if (accountType == StandardPurpose.p2tr) {
106
121
  return spentTx.outputs![spentOutputIndex].script.slice(2, 34); // 32 bytes x-only pubkey
107
122
  }
108
- if (accountType == AccountType.p2wpkh || accountType == AccountType.p2wpkhInP2sh) {
123
+ if (
124
+ accountType == StandardPurpose.p2wpkh ||
125
+ accountType == StandardPurpose.p2wpkhInP2sh
126
+ ) {
109
127
  return Buffer.from(testTx.vin[inputIndex].txinwitness![1], "hex");
110
128
  }
111
129
  throw new Error();
112
130
  }
113
131
 
114
- function getSignature(testTxInput: CoreInput, accountType: AccountType): Buffer {
132
+ function getSignature(
133
+ testTxInput: CoreInput,
134
+ accountType: StandardPurpose
135
+ ): Buffer {
115
136
  const scriptSig = Buffer.from(testTxInput.scriptSig.hex, "hex");
116
- if (accountType == AccountType.p2pkh) {
137
+ if (accountType == StandardPurpose.p2pkh) {
117
138
  return scriptSig.slice(1, scriptSig.length - 34);
118
139
  }
119
- if (accountType == AccountType.p2tr) {
140
+ if (accountType == StandardPurpose.p2tr) {
120
141
  return Buffer.from(testTxInput.txinwitness![0], "hex");
121
142
  }
122
- if (accountType == AccountType.p2wpkh || accountType == AccountType.p2wpkhInP2sh) {
143
+ if (
144
+ accountType == StandardPurpose.p2wpkh ||
145
+ accountType == StandardPurpose.p2wpkhInP2sh
146
+ ) {
123
147
  return Buffer.from(testTxInput.txinwitness![0], "hex");
124
148
  }
125
149
  throw new Error();
126
150
  }
127
151
 
128
- function getAccountType(coreInput: CoreInput, btc: Btc): AccountType {
152
+ function getAccountType(coreInput: CoreInput, btc: Btc): StandardPurpose {
129
153
  const spentTx = spentTxs[coreInput.txid];
130
154
  if (!spentTx) {
131
155
  throw new Error("Spent tx " + coreInput.txid + " unavailable.");
@@ -134,25 +158,31 @@ function getAccountType(coreInput: CoreInput, btc: Btc): AccountType {
134
158
  const spentOutput = splitSpentTx.outputs![coreInput.vout];
135
159
  const script = spentOutput.script;
136
160
  if (script.length == 34 && script[0] == 0x51) {
137
- return AccountType.p2tr;
161
+ return StandardPurpose.p2tr;
138
162
  }
139
163
  if (script.length == 22 && script[0] == 0x00) {
140
- return AccountType.p2wpkh;
164
+ return StandardPurpose.p2wpkh;
141
165
  }
142
166
  if (script.length == 23) {
143
- return AccountType.p2wpkhInP2sh;
167
+ return StandardPurpose.p2wpkhInP2sh;
144
168
  }
145
- return AccountType.p2pkh;
169
+ return StandardPurpose.p2pkh;
146
170
  }
147
171
 
148
172
  export function creatDummyXpub(pubkey: Buffer): string {
149
- const xpubDecoded = bs58check.decode("tpubDHcN44A4UHqdHJZwBxgTbu8Cy87ZrZkN8tQnmJGhcijHqe4rztuvGcD4wo36XSviLmiqL5fUbDnekYaQ7LzAnaqauBb9RsyahsTTFHdeJGd");
150
- const pubkey33 = pubkey.length == 33 ? pubkey : Buffer.concat([Buffer.of(2), pubkey]);
173
+ const xpubDecoded = bs58check.decode(
174
+ "tpubDHcN44A4UHqdHJZwBxgTbu8Cy87ZrZkN8tQnmJGhcijHqe4rztuvGcD4wo36XSviLmiqL5fUbDnekYaQ7LzAnaqauBb9RsyahsTTFHdeJGd"
175
+ );
176
+ const pubkey33 =
177
+ pubkey.length == 33 ? pubkey : Buffer.concat([Buffer.from([2]), pubkey]);
151
178
  xpubDecoded.fill(pubkey33, xpubDecoded.length - 33);
152
179
  return bs58check.encode(xpubDecoded);
153
180
  }
154
181
 
155
- function createInput(coreInput: CoreInput, btc: Btc): [Transaction, number, string | null, number] {
182
+ function createInput(
183
+ coreInput: CoreInput,
184
+ btc: Btc
185
+ ): [Transaction, number, string | null, number] {
156
186
  const spentTx = spentTxs[coreInput.txid];
157
187
  if (!spentTx) {
158
188
  throw new Error("Spent tx " + coreInput.txid + " unavailable.");
@@ -161,14 +191,14 @@ function createInput(coreInput: CoreInput, btc: Btc): [Transaction, number, stri
161
191
  return [splitSpentTx, coreInput.vout, null, coreInput.sequence];
162
192
  }
163
193
 
164
- export const masterFingerprint = Buffer.of(1, 2, 3, 4);
194
+ export const masterFingerprint = Buffer.from([1, 2, 3, 4]);
165
195
  export class TestingClient extends AppClient {
166
- mockGetPubkeyResponse(_pathElements: string, _response: string): void { };
196
+ mockGetPubkeyResponse(_pathElements: string, _response: string): void {}
167
197
  mockGetWalletAddressResponse(
168
198
  _walletPolicy: WalletPolicy,
169
199
  _change: number,
170
200
  _addressIndex: number,
171
201
  _response: string
172
- ): void { };
173
- mockSignPsbt(_yieldSigs: Map<number, Buffer>): void { };
202
+ ): void {}
203
+ mockSignPsbt(_yieldSigs: Map<number, Buffer>): void {}
174
204
  }
@@ -3,7 +3,7 @@ function testHasher(buf: Buffer): Buffer {
3
3
  return Buffer.from(buf);
4
4
  }
5
5
  function leaf(n: number) {
6
- return Buffer.of(0, n);
6
+ return Buffer.from([0, n]);
7
7
  }
8
8
  function merkleOf(count: number): Merkle {
9
9
  const leaves: Buffer[] = [];