@ledgerhq/hw-app-btc 10.16.0-nightly.20260115024415 → 10.16.0-nightly.20260116124336

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 (120) hide show
  1. package/CHANGELOG.md +5 -3
  2. package/README.md +106 -56
  3. package/lib/Btc.d.ts +37 -0
  4. package/lib/Btc.d.ts.map +1 -1
  5. package/lib/Btc.js +30 -2
  6. package/lib/Btc.js.map +1 -1
  7. package/lib/BtcNew.d.ts +84 -0
  8. package/lib/BtcNew.d.ts.map +1 -1
  9. package/lib/BtcNew.js +326 -9
  10. package/lib/BtcNew.js.map +1 -1
  11. package/lib/createTransaction.d.ts.map +1 -1
  12. package/lib/createTransaction.js +3 -2
  13. package/lib/createTransaction.js.map +1 -1
  14. package/lib/getTrustedInputBIP143.d.ts +1 -2
  15. package/lib/getTrustedInputBIP143.d.ts.map +1 -1
  16. package/lib/getTrustedInputBIP143.js +1 -1
  17. package/lib/getTrustedInputBIP143.js.map +1 -1
  18. package/lib/newops/accounttype.d.ts +3 -3
  19. package/lib/newops/accounttype.d.ts.map +1 -1
  20. package/lib/newops/accounttype.js +15 -14
  21. package/lib/newops/accounttype.js.map +1 -1
  22. package/lib/newops/appClient.d.ts +1 -1
  23. package/lib/newops/appClient.d.ts.map +1 -1
  24. package/lib/newops/clientCommands.js +2 -2
  25. package/lib/newops/clientCommands.js.map +1 -1
  26. package/lib/newops/merkelizedPsbt.d.ts +1 -1
  27. package/lib/newops/merkelizedPsbt.d.ts.map +1 -1
  28. package/lib/newops/merkelizedPsbt.js +1 -1
  29. package/lib/newops/merkelizedPsbt.js.map +1 -1
  30. package/lib/newops/policy.js +2 -2
  31. package/lib/newops/policy.js.map +1 -1
  32. package/lib/newops/psbtExtractor.d.ts +1 -1
  33. package/lib/newops/psbtExtractor.d.ts.map +1 -1
  34. package/lib/newops/psbtExtractor.js +3 -3
  35. package/lib/newops/psbtExtractor.js.map +1 -1
  36. package/lib/newops/psbtFinalizer.d.ts +1 -1
  37. package/lib/newops/psbtFinalizer.d.ts.map +1 -1
  38. package/lib/newops/psbtFinalizer.js +5 -6
  39. package/lib/newops/psbtFinalizer.js.map +1 -1
  40. package/lib/signP2SHTransaction.d.ts.map +1 -1
  41. package/lib/signP2SHTransaction.js +3 -2
  42. package/lib/signP2SHTransaction.js.map +1 -1
  43. package/lib-es/Btc.d.ts +37 -0
  44. package/lib-es/Btc.d.ts.map +1 -1
  45. package/lib-es/Btc.js +30 -2
  46. package/lib-es/Btc.js.map +1 -1
  47. package/lib-es/BtcNew.d.ts +84 -0
  48. package/lib-es/BtcNew.d.ts.map +1 -1
  49. package/lib-es/BtcNew.js +325 -8
  50. package/lib-es/BtcNew.js.map +1 -1
  51. package/lib-es/createTransaction.d.ts.map +1 -1
  52. package/lib-es/createTransaction.js +3 -2
  53. package/lib-es/createTransaction.js.map +1 -1
  54. package/lib-es/getTrustedInputBIP143.d.ts +1 -2
  55. package/lib-es/getTrustedInputBIP143.d.ts.map +1 -1
  56. package/lib-es/getTrustedInputBIP143.js +1 -1
  57. package/lib-es/getTrustedInputBIP143.js.map +1 -1
  58. package/lib-es/newops/accounttype.d.ts +3 -3
  59. package/lib-es/newops/accounttype.d.ts.map +1 -1
  60. package/lib-es/newops/accounttype.js +11 -10
  61. package/lib-es/newops/accounttype.js.map +1 -1
  62. package/lib-es/newops/appClient.d.ts +1 -1
  63. package/lib-es/newops/appClient.d.ts.map +1 -1
  64. package/lib-es/newops/clientCommands.js +1 -1
  65. package/lib-es/newops/clientCommands.js.map +1 -1
  66. package/lib-es/newops/merkelizedPsbt.d.ts +1 -1
  67. package/lib-es/newops/merkelizedPsbt.d.ts.map +1 -1
  68. package/lib-es/newops/merkelizedPsbt.js +1 -1
  69. package/lib-es/newops/merkelizedPsbt.js.map +1 -1
  70. package/lib-es/newops/policy.js +1 -1
  71. package/lib-es/newops/policy.js.map +1 -1
  72. package/lib-es/newops/psbtExtractor.d.ts +1 -1
  73. package/lib-es/newops/psbtExtractor.d.ts.map +1 -1
  74. package/lib-es/newops/psbtExtractor.js +1 -1
  75. package/lib-es/newops/psbtExtractor.js.map +1 -1
  76. package/lib-es/newops/psbtFinalizer.d.ts +1 -1
  77. package/lib-es/newops/psbtFinalizer.d.ts.map +1 -1
  78. package/lib-es/newops/psbtFinalizer.js +1 -2
  79. package/lib-es/newops/psbtFinalizer.js.map +1 -1
  80. package/lib-es/signP2SHTransaction.d.ts.map +1 -1
  81. package/lib-es/signP2SHTransaction.js +3 -2
  82. package/lib-es/signP2SHTransaction.js.map +1 -1
  83. package/package.json +6 -6
  84. package/src/Btc.ts +41 -2
  85. package/src/BtcNew.ts +483 -9
  86. package/src/createTransaction.ts +4 -3
  87. package/src/getTrustedInputBIP143.ts +0 -2
  88. package/src/newops/accounttype.ts +11 -12
  89. package/src/newops/appClient.ts +1 -1
  90. package/src/newops/clientCommands.ts +1 -1
  91. package/src/newops/merkelizedPsbt.ts +1 -1
  92. package/src/newops/policy.ts +1 -1
  93. package/src/newops/psbtExtractor.ts +1 -2
  94. package/src/newops/psbtFinalizer.ts +1 -2
  95. package/src/signP2SHTransaction.ts +3 -2
  96. package/tests/Btc.test.ts +848 -20
  97. package/tests/newops/BtcNew.signMessage.test.ts +35 -0
  98. package/tests/newops/BtcNew.signPsbtBuffer.test.ts +391 -0
  99. package/tests/newops/BtcNew.test.ts +13 -1
  100. package/tests/newops/integrationtools.ts +1 -1
  101. package/lib/buffertools.d.ts +0 -31
  102. package/lib/buffertools.d.ts.map +0 -1
  103. package/lib/buffertools.js +0 -129
  104. package/lib/buffertools.js.map +0 -1
  105. package/lib/newops/psbtv2.d.ts +0 -150
  106. package/lib/newops/psbtv2.d.ts.map +0 -1
  107. package/lib/newops/psbtv2.js +0 -469
  108. package/lib/newops/psbtv2.js.map +0 -1
  109. package/lib-es/buffertools.d.ts +0 -31
  110. package/lib-es/buffertools.d.ts.map +0 -1
  111. package/lib-es/buffertools.js +0 -119
  112. package/lib-es/buffertools.js.map +0 -1
  113. package/lib-es/newops/psbtv2.d.ts +0 -150
  114. package/lib-es/newops/psbtv2.d.ts.map +0 -1
  115. package/lib-es/newops/psbtv2.js +0 -464
  116. package/lib-es/newops/psbtv2.js.map +0 -1
  117. package/src/buffertools.ts +0 -137
  118. package/src/newops/psbtv2.ts +0 -525
  119. package/tests/buffertools.test.ts +0 -25
  120. package/tests/newops/psbtv2.test.ts +0 -15
@@ -0,0 +1,35 @@
1
+ import BtcNew from "../../src/BtcNew";
2
+ import { createClient } from "./BtcNew.test";
3
+
4
+ // signMessage is a thin wrapper over the AppClient signMessage,
5
+ // decoding the base64 response into (v, r, s). We reuse the
6
+ // existing MockClient from BtcNew.test via createClient and
7
+ // override signMessage to return a controlled value.
8
+
9
+ describe("BtcNew.signMessage", () => {
10
+ test("decodes base64 signature into v, r, s", async () => {
11
+ const [client, transport] = await createClient();
12
+
13
+ const mockSignature = Buffer.concat([
14
+ // First byte: 27 + 4 + v where v = 1 => 32
15
+ Buffer.from([32]),
16
+ // r: 32 bytes of 0x11
17
+ Buffer.alloc(32, 0x11),
18
+ // s: 32 bytes of 0x22
19
+ Buffer.alloc(32, 0x22),
20
+ ]);
21
+
22
+ client.signMessage = jest.fn(async (_message: Buffer, _pathElements: number[]) => {
23
+ return mockSignature.toString("base64");
24
+ });
25
+
26
+ const btcNew = new BtcNew(client);
27
+ const result = await btcNew.signMessage({ path: "m/44'/0'/0'/0/0", messageHex: "deadbeef" });
28
+
29
+ expect(result.v).toBe(1);
30
+ expect(result.r).toBe(Buffer.alloc(32, 0x11).toString("hex"));
31
+ expect(result.s).toBe(Buffer.alloc(32, 0x22).toString("hex"));
32
+
33
+ await transport.close();
34
+ });
35
+ });
@@ -0,0 +1,391 @@
1
+ import BtcNew from "../../src/BtcNew";
2
+ import { openTransportReplayer, RecordStore } from "@ledgerhq/hw-transport-mocker";
3
+ import Transport from "@ledgerhq/hw-transport";
4
+ import { finalize } from "../../src/newops/psbtFinalizer";
5
+ import { extract } from "../../src/newops/psbtExtractor";
6
+ import { TestingClient } from "./integrationtools";
7
+
8
+ // We mock @ledgerhq/psbtv2 to control PSBT behaviour and hit specific
9
+ // branches in signPsbtBuffer without having to build real PSBTs.
10
+
11
+ let mockPsbtVersion = 2;
12
+
13
+ interface MockWitnessUtxo {
14
+ scriptPubKey: Buffer;
15
+ }
16
+
17
+ interface MockPsbtConfig {
18
+ inputCount: number;
19
+ witnessUtxo?: MockWitnessUtxo;
20
+ redeemScript?: Buffer;
21
+ bip32DerivationPath: number[] | null;
22
+ }
23
+
24
+ const mockPsbtConfig: MockPsbtConfig = {
25
+ inputCount: 1,
26
+ witnessUtxo: undefined,
27
+ redeemScript: undefined,
28
+ // Default to a purpose that does not matter much for these tests.
29
+ bip32DerivationPath: [0x80000054, 0x80000000, 0x80000000, 0, 0],
30
+ };
31
+
32
+ jest.mock("@ledgerhq/psbtv2", () => {
33
+ const actual = jest.requireActual("@ledgerhq/psbtv2");
34
+ const localPsbtIn = actual.psbtIn;
35
+
36
+ class MockPsbtV2 {
37
+ static getPsbtVersionNumber(): number {
38
+ return mockPsbtVersion;
39
+ }
40
+
41
+ // For these tests we never exercise PSBT v0, but we still
42
+ // provide a stub to satisfy the API.
43
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
44
+ static fromV0(_buf: Buffer, _upgradeInputs: boolean): MockPsbtV2 {
45
+ return new MockPsbtV2();
46
+ }
47
+ // Methods used by BtcNew.signPsbtBuffer / signPsbt
48
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
49
+ deserialize(_buf: Buffer): void {}
50
+
51
+ getGlobalInputCount(): number {
52
+ return mockPsbtConfig.inputCount;
53
+ }
54
+
55
+ getInputKeyDatas(_index: number, keyType: number): Buffer[] {
56
+ if (keyType === localPsbtIn.BIP32_DERIVATION) {
57
+ return mockPsbtConfig.bip32DerivationPath ? [Buffer.alloc(33, 1)] : [];
58
+ }
59
+ if (keyType === localPsbtIn.TAP_BIP32_DERIVATION) {
60
+ return mockPsbtConfig.bip32DerivationPath ? [] : [Buffer.alloc(32, 2)];
61
+ }
62
+ return [];
63
+ }
64
+
65
+ getInputBip32Derivation(
66
+ _index: number,
67
+ _pubkey: Buffer,
68
+ ): { path: number[]; masterFingerprint: Buffer } | null {
69
+ if (!mockPsbtConfig.bip32DerivationPath) {
70
+ return null;
71
+ }
72
+ return {
73
+ path: mockPsbtConfig.bip32DerivationPath,
74
+ masterFingerprint: Buffer.from([1, 2, 3, 4]),
75
+ };
76
+ }
77
+
78
+ getInputTapBip32Derivation(
79
+ _index: number,
80
+ _pubkey: Buffer,
81
+ ): { path: number[]; masterFingerprint: Buffer } | null {
82
+ if (!mockPsbtConfig.bip32DerivationPath) {
83
+ return null;
84
+ }
85
+ return {
86
+ path: mockPsbtConfig.bip32DerivationPath,
87
+ masterFingerprint: Buffer.from([1, 2, 3, 4]),
88
+ };
89
+ }
90
+
91
+ getInputWitnessUtxo(_index: number): MockWitnessUtxo | undefined {
92
+ return mockPsbtConfig.witnessUtxo;
93
+ }
94
+
95
+ getInputRedeemScript(_index: number): Buffer | undefined {
96
+ return mockPsbtConfig.redeemScript;
97
+ }
98
+
99
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
100
+ setInputTapKeySig(_index: number, _sig: Buffer): void {}
101
+
102
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
103
+ setInputPartialSig(_index: number, _pubkey: Buffer, _sig: Buffer): void {}
104
+
105
+ getInputSighashType(_index: number): number | undefined {
106
+ return undefined;
107
+ }
108
+
109
+ copy(target: MockPsbtV2): void {
110
+ // For our test it is enough that serialize() on the copied
111
+ // instance returns a deterministic buffer.
112
+ // We do not need to actually copy state.
113
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
114
+ const _unused = target;
115
+ }
116
+
117
+ serialize(): Buffer {
118
+ return Buffer.from("010203", "hex");
119
+ }
120
+ }
121
+
122
+ return {
123
+ ...actual,
124
+ PsbtV2: MockPsbtV2,
125
+ psbtIn: localPsbtIn,
126
+ };
127
+ });
128
+ jest.mock("../../src/newops/psbtFinalizer", () => ({
129
+ finalize: jest.fn(),
130
+ }));
131
+
132
+ jest.mock("../../src/newops/psbtExtractor", () => ({
133
+ extract: jest.fn(),
134
+ }));
135
+
136
+ class MockClient extends TestingClient {
137
+ constructor(transport: Transport) {
138
+ super(transport);
139
+ }
140
+ lastWalletPolicy?: any;
141
+
142
+ async getMasterFingerprint(): Promise<Buffer> {
143
+ return Buffer.from([1, 2, 3, 4]);
144
+ }
145
+
146
+ async getExtendedPubkey(_display: boolean, _path: number[]): Promise<string> {
147
+ return "tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT";
148
+ }
149
+
150
+ async signPsbt(
151
+ _psbt: any,
152
+ walletPolicy: any,
153
+ _walletHMAC: Buffer,
154
+ _progressCallback: () => void,
155
+ ): Promise<Map<number, Buffer>> {
156
+ this.lastWalletPolicy = walletPolicy;
157
+ return new Map([[0, Buffer.alloc(64, 0)]]);
158
+ }
159
+ }
160
+
161
+ async function createClient(): Promise<[MockClient, Transport]> {
162
+ const transport = await openTransportReplayer(RecordStore.fromString(""));
163
+ const client = new MockClient(transport);
164
+ return [client, transport];
165
+ }
166
+
167
+ function makePsbtBuffer(): Buffer {
168
+ // We do not care about the actual bytes because PsbtV2 is mocked;
169
+ // only the version number returned by getPsbtVersionNumber is used.
170
+ return Buffer.from("70736274ff", "hex");
171
+ }
172
+
173
+ function makeScriptPubKeyP2wpkh(): Buffer {
174
+ const bytes: number[] = [0x00, 0x14];
175
+ while (bytes.length < 22) {
176
+ bytes.push(1);
177
+ }
178
+ return Buffer.from(bytes);
179
+ }
180
+
181
+ function makeScriptPubKeyP2pkh(): Buffer {
182
+ const bytes: number[] = [0x76, 0xa9, 0x14];
183
+ while (bytes.length < 23) {
184
+ bytes.push(2);
185
+ }
186
+ bytes.push(0x88, 0xac);
187
+ return Buffer.from(bytes);
188
+ }
189
+
190
+ function makeScriptPubKeyP2tr(): Buffer {
191
+ const bytes: number[] = [0x51, 0x20];
192
+ while (bytes.length < 34) {
193
+ bytes.push(3);
194
+ }
195
+ return Buffer.from(bytes);
196
+ }
197
+
198
+ function makeScriptPubKeyP2wpkhWrapped(): Buffer {
199
+ // OP_HASH160 <20-byte-script-hash> OP_EQUAL
200
+ const bytes: number[] = [0xa9, 0x14];
201
+ while (bytes.length < 22) {
202
+ bytes.push(4);
203
+ }
204
+ bytes.push(0x87);
205
+ return Buffer.from(bytes);
206
+ }
207
+
208
+ function makeUnsupportedScriptPubKey(): Buffer {
209
+ // Something that does not match any of the known templates
210
+ return Buffer.from([0x6a, 0x01, 0x00]);
211
+ }
212
+
213
+ describe("BtcNew.signPsbtBuffer", () => {
214
+ beforeEach(() => {
215
+ jest.clearAllMocks();
216
+ mockPsbtConfig.inputCount = 1;
217
+ mockPsbtConfig.witnessUtxo = undefined;
218
+ mockPsbtConfig.redeemScript = undefined;
219
+ mockPsbtConfig.bip32DerivationPath = [0x80000054, 0x80000000, 0x80000000, 0, 0];
220
+ });
221
+
222
+ test("throws on unsupported witness script type", async () => {
223
+ mockPsbtVersion = 2;
224
+ mockPsbtConfig.inputCount = 1;
225
+ mockPsbtConfig.witnessUtxo = {
226
+ scriptPubKey: makeUnsupportedScriptPubKey(),
227
+ };
228
+
229
+ const [client, transport] = await createClient();
230
+ const btcNew = new BtcNew(client);
231
+
232
+ await expect(
233
+ btcNew.signPsbtBuffer(makePsbtBuffer(), { accountPath: "m/84'/0'/0'" }),
234
+ ).rejects.toThrow(/Unsupported script type/);
235
+
236
+ await transport.close();
237
+ });
238
+
239
+ test("infers account type from witness UTXO script", async () => {
240
+ mockPsbtVersion = 2;
241
+ mockPsbtConfig.inputCount = 1;
242
+ mockPsbtConfig.bip32DerivationPath = [0x80000054, 0x80000000, 0x80000000, 0, 0];
243
+ jest.mocked(extract).mockReturnValue(Buffer.from("deadbeef", "hex"));
244
+
245
+ const [client, transport] = await createClient();
246
+ const btcNew = new BtcNew(client);
247
+
248
+ mockPsbtConfig.witnessUtxo = { scriptPubKey: makeScriptPubKeyP2tr() };
249
+ let result = await btcNew.signPsbtBuffer(makePsbtBuffer());
250
+ expect(result.tx).toBe("deadbeef");
251
+
252
+ mockPsbtConfig.witnessUtxo = { scriptPubKey: makeScriptPubKeyP2wpkhWrapped() };
253
+ jest.mocked(extract).mockReturnValue(Buffer.from("cafebabe", "hex"));
254
+ result = await btcNew.signPsbtBuffer(makePsbtBuffer());
255
+ expect(result.tx).toBe("cafebabe");
256
+
257
+ await transport.close();
258
+ });
259
+
260
+ test("infers account type from addressFormat when no witness UTXO or redeemScript", async () => {
261
+ mockPsbtVersion = 2;
262
+ mockPsbtConfig.inputCount = 1;
263
+ mockPsbtConfig.witnessUtxo = undefined;
264
+ mockPsbtConfig.redeemScript = undefined;
265
+ mockPsbtConfig.bip32DerivationPath = [0x80000054, 0x80000000, 0x80000000, 0, 0];
266
+ jest.mocked(extract).mockReturnValue(Buffer.from("cafebabe", "hex"));
267
+
268
+ const [client, transport] = await createClient();
269
+ const btcNew = new BtcNew(client);
270
+
271
+ async function expectFormat(format: any) {
272
+ const result = await btcNew.signPsbtBuffer(makePsbtBuffer(), { addressFormat: format });
273
+ expect(result.tx).toBe("cafebabe");
274
+ }
275
+
276
+ await expectFormat("legacy");
277
+ await expectFormat("p2sh");
278
+ await expectFormat("bech32");
279
+ await expectFormat("bech32m");
280
+
281
+ await transport.close();
282
+ });
283
+
284
+ test("infers account type from BIP32 purpose when no witness UTXO, redeemScript or addressFormat", async () => {
285
+ mockPsbtVersion = 2;
286
+ mockPsbtConfig.inputCount = 1;
287
+ mockPsbtConfig.witnessUtxo = undefined;
288
+ mockPsbtConfig.redeemScript = undefined;
289
+ jest.mocked(extract).mockReturnValue(Buffer.from("baadf00d", "hex"));
290
+
291
+ const [client, transport] = await createClient();
292
+ const btcNew = new BtcNew(client);
293
+
294
+ async function expectPurpose(purpose: number) {
295
+ mockPsbtConfig.bip32DerivationPath = [0x80000000 + purpose, 0x80000000, 0x80000000, 0, 0];
296
+ const result = await btcNew.signPsbtBuffer(makePsbtBuffer());
297
+ expect(result.tx).toBe("baadf00d");
298
+ }
299
+
300
+ await expectPurpose(44);
301
+ await expectPurpose(49);
302
+ await expectPurpose(84);
303
+ await expectPurpose(86);
304
+ // Unknown purpose defaults to native segwit but still signs
305
+ await expectPurpose(45);
306
+
307
+ await transport.close();
308
+ });
309
+
310
+ test("throws if PSBT has no inputs", async () => {
311
+ mockPsbtVersion = 2;
312
+ mockPsbtConfig.inputCount = 0;
313
+
314
+ const [client, transport] = await createClient();
315
+ const btcNew = new BtcNew(client);
316
+
317
+ await expect(btcNew.signPsbtBuffer(makePsbtBuffer())).rejects.toThrow("No inputs in PSBT");
318
+
319
+ await transport.close();
320
+ });
321
+
322
+ test("uses BIP32 derivation from PSBT when available and finalizes by default", async () => {
323
+ mockPsbtVersion = 2;
324
+ mockPsbtConfig.inputCount = 1;
325
+ mockPsbtConfig.witnessUtxo = {
326
+ scriptPubKey: makeScriptPubKeyP2wpkh(),
327
+ };
328
+
329
+ jest.mocked(extract).mockReturnValue(Buffer.from("deadbeef", "hex"));
330
+
331
+ const [client, transport] = await createClient();
332
+ const btcNew = new BtcNew(client);
333
+
334
+ const result = await btcNew.signPsbtBuffer(makePsbtBuffer());
335
+
336
+ // One account xpub request based on BIP32 derivation information
337
+ // is expected; we cannot easily introspect internal calls on the
338
+ // existing MockClient but this is indirectly exercised by the
339
+ // absence of thrown errors.
340
+ expect(finalize).toHaveBeenCalledTimes(1);
341
+ expect(extract).toHaveBeenCalledTimes(1);
342
+ expect(result.tx).toBe("deadbeef");
343
+ expect(result.psbt.toString("hex")).toBe("010203");
344
+
345
+ await transport.close();
346
+ });
347
+
348
+ test("falls back to options.accountPath and does not finalize when finalizePsbt is false", async () => {
349
+ mockPsbtVersion = 2;
350
+ mockPsbtConfig.inputCount = 1;
351
+ mockPsbtConfig.bip32DerivationPath = null;
352
+ mockPsbtConfig.witnessUtxo = {
353
+ scriptPubKey: makeScriptPubKeyP2pkh(),
354
+ };
355
+
356
+ jest.mocked(extract).mockReturnValue(Buffer.from("cafebabe", "hex"));
357
+
358
+ const [client, transport] = await createClient();
359
+ const btcNew = new BtcNew(client);
360
+
361
+ const result = await btcNew.signPsbtBuffer(makePsbtBuffer(), {
362
+ finalizePsbt: false,
363
+ accountPath: "m/44'/0'/0'",
364
+ });
365
+
366
+ expect(finalize).not.toHaveBeenCalled();
367
+ expect(extract).toHaveBeenCalledTimes(1);
368
+ expect(result.tx).toBe("cafebabe");
369
+
370
+ await transport.close();
371
+ });
372
+
373
+ test("throws when neither BIP32 derivation nor options.accountPath is provided", async () => {
374
+ mockPsbtVersion = 2;
375
+ mockPsbtConfig.inputCount = 1;
376
+ mockPsbtConfig.bip32DerivationPath = null;
377
+ mockPsbtConfig.witnessUtxo = {
378
+ scriptPubKey: makeScriptPubKeyP2wpkh(),
379
+ };
380
+
381
+ const [client, transport] = await createClient();
382
+ const btcNew = new BtcNew(client);
383
+
384
+ await expect(btcNew.signPsbtBuffer(makePsbtBuffer())).rejects.toThrow(
385
+ "No internal inputs found in PSBT (no BIP32 derivation matching device fingerprint) " +
386
+ "and no account path provided in options.",
387
+ );
388
+
389
+ await transport.close();
390
+ });
391
+ });
@@ -38,7 +38,7 @@ const ecc = {
38
38
  import { getXpubComponents, pathArrayToString } from "../../src/bip32";
39
39
  import BtcNew from "../../src/BtcNew";
40
40
  import { DefaultDescriptorTemplate, WalletPolicy } from "../../src/newops/policy";
41
- import { PsbtV2 } from "../../src/newops/psbtv2";
41
+ import type { PsbtV2 } from "@ledgerhq/psbtv2";
42
42
  import { splitTransaction } from "../../src/splitTransaction";
43
43
  import {
44
44
  StandardPurpose,
@@ -81,6 +81,18 @@ test("getWalletXpub normal path", async () => {
81
81
  await testGetWalletXpub("m/44'/0'/0'");
82
82
  });
83
83
 
84
+ test("getWalletXpub throws on xpub version mismatch", async () => {
85
+ const [client] = await createClient();
86
+ const path = "m/44'/0'/0'";
87
+ const expectedXpub =
88
+ "tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT";
89
+ client.mockGetPubkeyResponse(path, expectedXpub);
90
+ const btc = new BtcNew(client);
91
+ await expect(btc.getWalletXpub({ path, xpubVersion: 0x043587cf + 1 })).rejects.toThrow(
92
+ /Expected xpub version/,
93
+ );
94
+ });
95
+
84
96
  function testPaths(type: StandardPurpose): { ins: string[]; out?: string } {
85
97
  const basePath = `m/${type}/1'/0'/`;
86
98
  const ins = [
@@ -1,9 +1,9 @@
1
1
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
2
  import Transport from "@ledgerhq/hw-transport";
3
+ import { BufferWriter } from "@ledgerhq/psbtv2";
3
4
  import bs58check from "bs58check";
4
5
  import Btc from "../../src/Btc";
5
6
  import BtcNew from "../../src/BtcNew";
6
- import { BufferWriter } from "../../src/buffertools";
7
7
  import { CreateTransactionArg } from "../../src/createTransaction";
8
8
  import { AddressFormat } from "../../src/getWalletPublicKey";
9
9
  import { AppClient } from "../../src/newops/appClient";
@@ -1,31 +0,0 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- export declare function unsafeTo64bitLE(n: number): Buffer;
4
- export declare function unsafeFrom64bitLE(byteArray: Buffer): number;
5
- export declare class BufferWriter {
6
- private bufs;
7
- write(alloc: number, fn: (b: Buffer) => void): void;
8
- writeUInt8(i: number): void;
9
- writeInt32(i: number): void;
10
- writeUInt32(i: number): void;
11
- writeUInt64(i: number): void;
12
- writeVarInt(i: number): void;
13
- writeSlice(slice: Buffer): void;
14
- writeVarSlice(slice: Buffer): void;
15
- buffer(): Buffer;
16
- }
17
- export declare class BufferReader {
18
- buffer: Buffer;
19
- offset: number;
20
- constructor(buffer: Buffer, offset?: number);
21
- available(): number;
22
- readUInt8(): number;
23
- readInt32(): number;
24
- readUInt32(): number;
25
- readUInt64(): number;
26
- readVarInt(): number;
27
- readSlice(n: number): Buffer;
28
- readVarSlice(): Buffer;
29
- readVector(): Buffer[];
30
- }
31
- //# sourceMappingURL=buffertools.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"buffertools.d.ts","sourceRoot":"","sources":["../src/buffertools.ts"],"names":[],"mappings":";;AAEA,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAYjD;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAe3D;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAgB;IAE5B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAMnD,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAI3B,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAI3B,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAI5B,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAK5B,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAI5B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI/B,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKlC,MAAM,IAAI,MAAM;CAGjB;AAED,qBAAa,YAAY;IAEd,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,MAAM;gBADd,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAU;IAG3B,SAAS,IAAI,MAAM;IAInB,SAAS,IAAI,MAAM;IAMnB,SAAS,IAAI,MAAM;IAMnB,UAAU,IAAI,MAAM;IAMpB,UAAU,IAAI,MAAM;IAMpB,UAAU,IAAI,MAAM;IAMpB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM;IAS5B,YAAY,IAAI,MAAM;IAItB,UAAU,IAAI,MAAM,EAAE;CAMvB"}
@@ -1,129 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.BufferReader = exports.BufferWriter = exports.unsafeFrom64bitLE = exports.unsafeTo64bitLE = void 0;
7
- const varuint_bitcoin_1 = __importDefault(require("varuint-bitcoin"));
8
- function unsafeTo64bitLE(n) {
9
- // we want to represent the input as a 8-bytes array
10
- if (n > Number.MAX_SAFE_INTEGER) {
11
- throw new Error("Can't convert numbers > MAX_SAFE_INT");
12
- }
13
- const byteArray = Buffer.alloc(8, 0);
14
- for (let index = 0; index < byteArray.length; index++) {
15
- const byte = n & 0xff;
16
- byteArray[index] = byte;
17
- n = (n - byte) / 256;
18
- }
19
- return byteArray;
20
- }
21
- exports.unsafeTo64bitLE = unsafeTo64bitLE;
22
- function unsafeFrom64bitLE(byteArray) {
23
- let value = 0;
24
- if (byteArray.length != 8) {
25
- throw new Error("Expected Bufffer of lenght 8");
26
- }
27
- if (byteArray[7] != 0) {
28
- throw new Error("Can't encode numbers > MAX_SAFE_INT");
29
- }
30
- if (byteArray[6] > 0x1f) {
31
- throw new Error("Can't encode numbers > MAX_SAFE_INT");
32
- }
33
- for (let i = byteArray.length - 1; i >= 0; i--) {
34
- value = value * 256 + byteArray[i];
35
- }
36
- return value;
37
- }
38
- exports.unsafeFrom64bitLE = unsafeFrom64bitLE;
39
- class BufferWriter {
40
- bufs = [];
41
- write(alloc, fn) {
42
- const b = Buffer.alloc(alloc);
43
- fn(b);
44
- this.bufs.push(b);
45
- }
46
- writeUInt8(i) {
47
- this.write(1, b => b.writeUInt8(i, 0));
48
- }
49
- writeInt32(i) {
50
- this.write(4, b => b.writeInt32LE(i, 0));
51
- }
52
- writeUInt32(i) {
53
- this.write(4, b => b.writeUInt32LE(i, 0));
54
- }
55
- writeUInt64(i) {
56
- const bytes = unsafeTo64bitLE(i);
57
- this.writeSlice(bytes);
58
- }
59
- writeVarInt(i) {
60
- this.bufs.push(varuint_bitcoin_1.default.encode(i));
61
- }
62
- writeSlice(slice) {
63
- this.bufs.push(Buffer.from(slice));
64
- }
65
- writeVarSlice(slice) {
66
- this.writeVarInt(slice.length);
67
- this.writeSlice(slice);
68
- }
69
- buffer() {
70
- return Buffer.concat(this.bufs);
71
- }
72
- }
73
- exports.BufferWriter = BufferWriter;
74
- class BufferReader {
75
- buffer;
76
- offset;
77
- constructor(buffer, offset = 0) {
78
- this.buffer = buffer;
79
- this.offset = offset;
80
- }
81
- available() {
82
- return this.buffer.length - this.offset;
83
- }
84
- readUInt8() {
85
- const result = this.buffer.readUInt8(this.offset);
86
- this.offset++;
87
- return result;
88
- }
89
- readInt32() {
90
- const result = this.buffer.readInt32LE(this.offset);
91
- this.offset += 4;
92
- return result;
93
- }
94
- readUInt32() {
95
- const result = this.buffer.readUInt32LE(this.offset);
96
- this.offset += 4;
97
- return result;
98
- }
99
- readUInt64() {
100
- const buf = this.readSlice(8);
101
- const n = unsafeFrom64bitLE(buf);
102
- return n;
103
- }
104
- readVarInt() {
105
- const vi = varuint_bitcoin_1.default.decode(this.buffer, this.offset);
106
- this.offset += varuint_bitcoin_1.default.decode.bytes;
107
- return vi;
108
- }
109
- readSlice(n) {
110
- if (this.buffer.length < this.offset + n) {
111
- throw new Error("Cannot read slice out of bounds");
112
- }
113
- const result = this.buffer.slice(this.offset, this.offset + n);
114
- this.offset += n;
115
- return result;
116
- }
117
- readVarSlice() {
118
- return this.readSlice(this.readVarInt());
119
- }
120
- readVector() {
121
- const count = this.readVarInt();
122
- const vector = [];
123
- for (let i = 0; i < count; i++)
124
- vector.push(this.readVarSlice());
125
- return vector;
126
- }
127
- }
128
- exports.BufferReader = BufferReader;
129
- //# sourceMappingURL=buffertools.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"buffertools.js","sourceRoot":"","sources":["../src/buffertools.ts"],"names":[],"mappings":"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"}