@ledgerhq/psbtv2 0.1.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 (46) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/LICENSE.txt +21 -0
  3. package/README.md +91 -0
  4. package/jest.config.js +26 -0
  5. package/lib/buffertools.d.ts +29 -0
  6. package/lib/buffertools.d.ts.map +1 -0
  7. package/lib/buffertools.js +129 -0
  8. package/lib/buffertools.js.map +1 -0
  9. package/lib/index.d.ts +4 -0
  10. package/lib/index.d.ts.map +1 -0
  11. package/lib/index.js +18 -0
  12. package/lib/index.js.map +1 -0
  13. package/lib/psbtParsing.d.ts +15 -0
  14. package/lib/psbtParsing.d.ts.map +1 -0
  15. package/lib/psbtParsing.js +52 -0
  16. package/lib/psbtParsing.js.map +1 -0
  17. package/lib/psbtv2.d.ts +200 -0
  18. package/lib/psbtv2.d.ts.map +1 -0
  19. package/lib/psbtv2.js +647 -0
  20. package/lib/psbtv2.js.map +1 -0
  21. package/lib-es/buffertools.d.ts +29 -0
  22. package/lib-es/buffertools.d.ts.map +1 -0
  23. package/lib-es/buffertools.js +119 -0
  24. package/lib-es/buffertools.js.map +1 -0
  25. package/lib-es/index.d.ts +4 -0
  26. package/lib-es/index.d.ts.map +1 -0
  27. package/lib-es/index.js +4 -0
  28. package/lib-es/index.js.map +1 -0
  29. package/lib-es/psbtParsing.d.ts +15 -0
  30. package/lib-es/psbtParsing.d.ts.map +1 -0
  31. package/lib-es/psbtParsing.js +48 -0
  32. package/lib-es/psbtParsing.js.map +1 -0
  33. package/lib-es/psbtv2.d.ts +200 -0
  34. package/lib-es/psbtv2.d.ts.map +1 -0
  35. package/lib-es/psbtv2.js +641 -0
  36. package/lib-es/psbtv2.js.map +1 -0
  37. package/package.json +78 -0
  38. package/src/buffertools.test.ts +116 -0
  39. package/src/buffertools.ts +137 -0
  40. package/src/fromV0.test.ts +577 -0
  41. package/src/index.ts +3 -0
  42. package/src/psbtParsing.test.ts +86 -0
  43. package/src/psbtParsing.ts +51 -0
  44. package/src/psbtv2.test.ts +441 -0
  45. package/src/psbtv2.ts +740 -0
  46. package/tsconfig.json +9 -0
@@ -0,0 +1,577 @@
1
+ import { Psbt, payments, ECPair, networks } from "bitcoinjs-lib";
2
+ import { PsbtV2 } from "./psbtv2";
3
+
4
+ describe("PsbtV2.fromV0", () => {
5
+ describe("Basic Conversion", () => {
6
+ it("should convert a simple PSBTv0 with one P2WPKH input and output", () => {
7
+ // Create a PSBTv0
8
+ const psbtv0 = new Psbt({ network: networks.testnet });
9
+
10
+ // Add input
11
+ const prevTxId = Buffer.from(
12
+ "0000000000000000000000000000000000000000000000000000000000000001",
13
+ "hex",
14
+ );
15
+ psbtv0.addInput({
16
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
17
+ hash: prevTxId,
18
+ index: 0,
19
+ witnessUtxo: {
20
+ script: Buffer.from("0014" + "00".repeat(20), "hex"),
21
+ value: 100000,
22
+ },
23
+ sequence: 0xfffffffd,
24
+ });
25
+
26
+ // Add output
27
+ psbtv0.addOutput({
28
+ script: Buffer.from("0014" + "11".repeat(20), "hex"),
29
+ value: 90000,
30
+ });
31
+
32
+ // Convert to PSBTv2
33
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
34
+
35
+ // Verify global fields
36
+ expect(psbtv2.getGlobalTxVersion()).toBe(2);
37
+ expect(psbtv2.getGlobalFallbackLocktime()).toBe(0);
38
+ expect(psbtv2.getGlobalInputCount()).toBe(1);
39
+ expect(psbtv2.getGlobalOutputCount()).toBe(1);
40
+ expect(psbtv2.getGlobalPsbtVersion()).toBe(2);
41
+
42
+ // Verify input fields
43
+ expect(psbtv2.getInputPreviousTxid(0)).toEqual(prevTxId.reverse());
44
+ expect(psbtv2.getInputOutputIndex(0)).toBe(0);
45
+ expect(psbtv2.getInputSequence(0)).toBe(0xfffffffd);
46
+
47
+ const witnessUtxo = psbtv2.getInputWitnessUtxo(0);
48
+ expect(witnessUtxo).toBeDefined();
49
+ expect(witnessUtxo!.scriptPubKey.toString("hex")).toBe("0014" + "00".repeat(20));
50
+
51
+ // Verify output fields
52
+ expect(psbtv2.getOutputAmount(0)).toBe(90000);
53
+ expect(psbtv2.getOutputScript(0).toString("hex")).toBe("0014" + "11".repeat(20));
54
+ });
55
+
56
+ it("should convert a PSBTv0 with custom locktime", () => {
57
+ const psbtv0 = new Psbt({ network: networks.testnet });
58
+ psbtv0.setLocktime(500000);
59
+
60
+ psbtv0.addInput({
61
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
62
+ hash: Buffer.alloc(32, 0),
63
+ index: 0,
64
+ witnessUtxo: {
65
+ script: Buffer.from("0014" + "00".repeat(20), "hex"),
66
+ value: 100000,
67
+ },
68
+ });
69
+
70
+ psbtv0.addOutput({
71
+ script: Buffer.from("0014" + "11".repeat(20), "hex"),
72
+ value: 90000,
73
+ });
74
+
75
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
76
+
77
+ expect(psbtv2.getGlobalFallbackLocktime()).toBe(500000);
78
+ });
79
+
80
+ it("should throw error for transaction version 1 when not allowed", () => {
81
+ const psbtv0 = new Psbt({ network: networks.testnet });
82
+ psbtv0.setVersion(1);
83
+
84
+ psbtv0.addInput({
85
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
86
+ hash: Buffer.alloc(32, 0),
87
+ index: 0,
88
+ witnessUtxo: {
89
+ script: Buffer.from("0014" + "00".repeat(20), "hex"),
90
+ value: 100000,
91
+ },
92
+ });
93
+
94
+ psbtv0.addOutput({
95
+ script: Buffer.from("0014" + "11".repeat(20), "hex"),
96
+ value: 90000,
97
+ });
98
+
99
+ expect(() => PsbtV2.fromV0(psbtv0.toBuffer())).toThrow(
100
+ /Transaction version 1 detected.*allowTxnVersion1=true/,
101
+ );
102
+ });
103
+
104
+ it("should accept Buffer input", () => {
105
+ const psbtv0 = new Psbt({ network: networks.testnet });
106
+
107
+ psbtv0.addInput({
108
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
109
+ hash: Buffer.alloc(32, 0),
110
+ index: 0,
111
+ witnessUtxo: {
112
+ script: Buffer.from("0014" + "00".repeat(20), "hex"),
113
+ value: 100000,
114
+ },
115
+ });
116
+
117
+ psbtv0.addOutput({
118
+ script: Buffer.from("0014" + "11".repeat(20), "hex"),
119
+ value: 90000,
120
+ });
121
+
122
+ const buffer = psbtv0.toBuffer();
123
+ const psbtv2 = PsbtV2.fromV0(buffer, true);
124
+
125
+ expect(psbtv2.getGlobalInputCount()).toBe(1);
126
+ expect(psbtv2.getGlobalOutputCount()).toBe(1);
127
+ });
128
+ });
129
+
130
+ describe("Multiple Inputs and Outputs", () => {
131
+ it("should handle multiple inputs", () => {
132
+ const psbtv0 = new Psbt({ network: networks.testnet });
133
+
134
+ // Add 3 inputs
135
+ for (let i = 0; i < 3; i++) {
136
+ psbtv0.addInput({
137
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
138
+ hash: Buffer.alloc(32, i),
139
+ index: i,
140
+ witnessUtxo: {
141
+ script: Buffer.from("0014" + "00".repeat(20), "hex"),
142
+ value: 100000 + i * 10000,
143
+ },
144
+ sequence: 0xffffffff - i,
145
+ });
146
+ }
147
+
148
+ psbtv0.addOutput({
149
+ script: Buffer.from("0014" + "11".repeat(20), "hex"),
150
+ value: 300000,
151
+ });
152
+
153
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
154
+
155
+ expect(psbtv2.getGlobalInputCount()).toBe(3);
156
+
157
+ for (let i = 0; i < 3; i++) {
158
+ expect(psbtv2.getInputOutputIndex(i)).toBe(i);
159
+ expect(psbtv2.getInputSequence(i)).toBe(0xffffffff - i);
160
+ const utxo = psbtv2.getInputWitnessUtxo(i);
161
+ expect(utxo).toBeDefined();
162
+ }
163
+ });
164
+
165
+ it("should handle multiple outputs", () => {
166
+ const psbtv0 = new Psbt({ network: networks.testnet });
167
+
168
+ psbtv0.addInput({
169
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
170
+ hash: Buffer.alloc(32, 0),
171
+ index: 0,
172
+ witnessUtxo: {
173
+ script: Buffer.from("0014" + "00".repeat(20), "hex"),
174
+ value: 500000,
175
+ },
176
+ });
177
+
178
+ // Add 3 outputs
179
+ for (let i = 0; i < 3; i++) {
180
+ psbtv0.addOutput({
181
+ script: Buffer.from("0014" + Buffer.alloc(20, i).toString("hex"), "hex"),
182
+ value: 100000 + i * 10000,
183
+ });
184
+ }
185
+
186
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
187
+
188
+ expect(psbtv2.getGlobalOutputCount()).toBe(3);
189
+
190
+ for (let i = 0; i < 3; i++) {
191
+ expect(psbtv2.getOutputAmount(i)).toBe(100000 + i * 10000);
192
+ }
193
+ });
194
+ });
195
+
196
+ describe("UTXO Types", () => {
197
+ it("should handle witnessUtxo", () => {
198
+ const psbtv0 = new Psbt({ network: networks.testnet });
199
+
200
+ const script = Buffer.from("0014" + "aa".repeat(20), "hex");
201
+ psbtv0.addInput({
202
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
203
+ hash: Buffer.alloc(32, 0),
204
+ index: 0,
205
+ witnessUtxo: {
206
+ script,
207
+ value: 123456,
208
+ },
209
+ });
210
+
211
+ psbtv0.addOutput({
212
+ script: Buffer.from("0014" + "11".repeat(20), "hex"),
213
+ value: 100000,
214
+ });
215
+
216
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
217
+
218
+ const witnessUtxo = psbtv2.getInputWitnessUtxo(0);
219
+ expect(witnessUtxo).toBeDefined();
220
+ expect(witnessUtxo!.scriptPubKey).toEqual(script);
221
+ });
222
+
223
+ it("should handle nonWitnessUtxo", () => {
224
+ const psbtv0 = new Psbt({ network: networks.testnet });
225
+
226
+ // Create a dummy previous transaction
227
+ const prevTx = Buffer.from(
228
+ "0200000001" + // version
229
+ "00".repeat(32) + // prev txid
230
+ "00000000" + // prev vout
231
+ "00" + // scriptSig length
232
+ "ffffffff" + // sequence
233
+ "01" + // output count
234
+ "a086010000000000" + // value (100000)
235
+ "16" + // script length
236
+ "0014" +
237
+ "00".repeat(20) + // script
238
+ "00000000", // locktime
239
+ "hex",
240
+ );
241
+
242
+ psbtv0.addInput({
243
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
244
+ hash: Buffer.alloc(32, 1),
245
+ index: 0,
246
+ nonWitnessUtxo: prevTx,
247
+ });
248
+
249
+ psbtv0.addOutput({
250
+ script: Buffer.from("0014" + "11".repeat(20), "hex"),
251
+ value: 90000,
252
+ });
253
+
254
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
255
+
256
+ const nonWitnessUtxo = psbtv2.getInputNonWitnessUtxo(0);
257
+ expect(nonWitnessUtxo).toBeDefined();
258
+ expect(nonWitnessUtxo).toEqual(prevTx);
259
+ });
260
+ });
261
+
262
+ describe("Optional Fields", () => {
263
+ it("should handle redeemScript for P2SH", () => {
264
+ const psbtv0 = new Psbt({ network: networks.testnet });
265
+
266
+ const redeemScript = Buffer.from("0014" + "00".repeat(20), "hex");
267
+
268
+ psbtv0.addInput({
269
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
270
+ hash: Buffer.alloc(32, 0),
271
+ index: 0,
272
+ witnessUtxo: {
273
+ script: Buffer.from("a914" + "00".repeat(20) + "87", "hex"), // P2SH
274
+ value: 100000,
275
+ },
276
+ redeemScript,
277
+ });
278
+
279
+ psbtv0.addOutput({
280
+ script: Buffer.from("0014" + "11".repeat(20), "hex"),
281
+ value: 90000,
282
+ });
283
+
284
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
285
+
286
+ const convertedRedeemScript = psbtv2.getInputRedeemScript(0);
287
+ expect(convertedRedeemScript).toBeDefined();
288
+ expect(convertedRedeemScript).toEqual(redeemScript);
289
+ });
290
+
291
+ it("should handle sighashType", () => {
292
+ const psbtv0 = new Psbt({ network: networks.testnet });
293
+
294
+ psbtv0.addInput({
295
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
296
+ hash: Buffer.alloc(32, 0),
297
+ index: 0,
298
+ witnessUtxo: {
299
+ script: Buffer.from("0014" + "00".repeat(20), "hex"),
300
+ value: 100000,
301
+ },
302
+ sighashType: 0x03, // SIGHASH_SINGLE
303
+ });
304
+
305
+ psbtv0.addOutput({
306
+ script: Buffer.from("0014" + "11".repeat(20), "hex"),
307
+ value: 90000,
308
+ });
309
+
310
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
311
+
312
+ const sighashType = psbtv2.getInputSighashType(0);
313
+ expect(sighashType).toBe(0x03);
314
+ });
315
+
316
+ it("should handle BIP32 derivation paths on inputs", () => {
317
+ const psbtv0 = new Psbt({ network: networks.testnet });
318
+
319
+ const pubkey = Buffer.from("02" + "00".repeat(32), "hex");
320
+ const masterFingerprint = Buffer.from("12345678", "hex");
321
+
322
+ psbtv0.addInput({
323
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
324
+ hash: Buffer.alloc(32, 0),
325
+ index: 0,
326
+ witnessUtxo: {
327
+ script: Buffer.from("0014" + "00".repeat(20), "hex"),
328
+ value: 100000,
329
+ },
330
+ bip32Derivation: [
331
+ {
332
+ masterFingerprint,
333
+ pubkey,
334
+ path: "m/84'/1'/0'/0/0",
335
+ },
336
+ ],
337
+ });
338
+
339
+ psbtv0.addOutput({
340
+ script: Buffer.from("0014" + "11".repeat(20), "hex"),
341
+ value: 90000,
342
+ });
343
+
344
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
345
+
346
+ const deriv = psbtv2.getInputBip32Derivation(0, pubkey);
347
+ expect(deriv).toBeDefined();
348
+ expect(deriv!.masterFingerprint).toEqual(masterFingerprint);
349
+ expect(deriv!.path).toEqual([
350
+ 0x80000054, // 84'
351
+ 0x80000001, // 1'
352
+ 0x80000000, // 0'
353
+ 0, // 0
354
+ 0, // 0
355
+ ]);
356
+ });
357
+
358
+ it("should handle BIP32 derivation paths on outputs", () => {
359
+ const psbtv0 = new Psbt({ network: networks.testnet });
360
+
361
+ const pubkey = Buffer.from("02" + "00".repeat(32), "hex");
362
+ const masterFingerprint = Buffer.from("87654321", "hex");
363
+
364
+ psbtv0.addInput({
365
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
366
+ hash: Buffer.alloc(32, 0),
367
+ index: 0,
368
+ witnessUtxo: {
369
+ script: Buffer.from("0014" + "00".repeat(20), "hex"),
370
+ value: 100000,
371
+ },
372
+ });
373
+
374
+ psbtv0.addOutput({
375
+ script: Buffer.from("0014" + "11".repeat(20), "hex"),
376
+ value: 90000,
377
+ bip32Derivation: [
378
+ {
379
+ masterFingerprint,
380
+ pubkey,
381
+ path: "m/84'/1'/0'/1/0",
382
+ },
383
+ ],
384
+ });
385
+
386
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
387
+
388
+ const deriv = psbtv2.getOutputBip32Derivation(0, pubkey);
389
+ expect(deriv).toBeDefined();
390
+ expect(deriv.masterFingerprint).toEqual(masterFingerprint);
391
+ expect(deriv.path).toEqual([
392
+ 0x80000054, // 84'
393
+ 0x80000001, // 1'
394
+ 0x80000000, // 0'
395
+ 1, // 1
396
+ 0, // 0
397
+ ]);
398
+ });
399
+
400
+ it("should handle redeemScript on outputs", () => {
401
+ const psbtv0 = new Psbt({ network: networks.testnet });
402
+
403
+ psbtv0.addInput({
404
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
405
+ hash: Buffer.alloc(32, 0),
406
+ index: 0,
407
+ witnessUtxo: {
408
+ script: Buffer.from("0014" + "00".repeat(20), "hex"),
409
+ value: 100000,
410
+ },
411
+ });
412
+
413
+ const redeemScript = Buffer.from("0014" + "22".repeat(20), "hex");
414
+
415
+ psbtv0.addOutput({
416
+ script: Buffer.from("a914" + "11".repeat(20) + "87", "hex"),
417
+ value: 90000,
418
+ redeemScript,
419
+ });
420
+
421
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
422
+
423
+ const outRedeemScript = psbtv2.getOutputRedeemScript(0);
424
+ expect(outRedeemScript).toEqual(redeemScript);
425
+ });
426
+ });
427
+
428
+ describe("Partial Signatures", () => {
429
+ it("should transfer partial signatures", () => {
430
+ const psbtv0 = new Psbt({ network: networks.testnet });
431
+
432
+ // Create a real keypair for signing
433
+ const keyPair = ECPair.makeRandom({ network: networks.testnet });
434
+ const pubkey = keyPair.publicKey;
435
+ const p2wpkh = payments.p2wpkh({ pubkey, network: networks.testnet });
436
+
437
+ psbtv0.addInput({
438
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
439
+ hash: Buffer.alloc(32, 0),
440
+ index: 0,
441
+ witnessUtxo: {
442
+ script: p2wpkh.output!,
443
+ value: 100000,
444
+ },
445
+ });
446
+
447
+ psbtv0.addOutput({
448
+ script: Buffer.from("0014" + "11".repeat(20), "hex"),
449
+ value: 90000,
450
+ });
451
+
452
+ // Actually sign the input
453
+ psbtv0.signInput(0, keyPair);
454
+
455
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
456
+
457
+ // Verify the signature was transferred
458
+ const partialSig = psbtv2.getInputPartialSig(0, pubkey);
459
+ expect(partialSig).toBeDefined();
460
+ expect(partialSig!.length).toBeGreaterThan(0);
461
+ });
462
+ });
463
+
464
+ describe("Finalized Inputs", () => {
465
+ it("should transfer finalScriptSig", () => {
466
+ const psbtv0 = new Psbt({ network: networks.testnet });
467
+
468
+ const finalScriptSig = Buffer.from("47" + "00".repeat(71) + "21" + "00".repeat(33), "hex");
469
+
470
+ psbtv0.addInput({
471
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
472
+ hash: Buffer.alloc(32, 0),
473
+ index: 0,
474
+ nonWitnessUtxo: Buffer.from(
475
+ "0200000001" +
476
+ "00".repeat(32) +
477
+ "00000000" +
478
+ "00" +
479
+ "ffffffff" +
480
+ "01" +
481
+ "a086010000000000" +
482
+ "16" +
483
+ "0014" +
484
+ "00".repeat(20) +
485
+ "00000000",
486
+ "hex",
487
+ ),
488
+ finalScriptSig,
489
+ });
490
+
491
+ psbtv0.addOutput({
492
+ script: Buffer.from("0014" + "11".repeat(20), "hex"),
493
+ value: 90000,
494
+ });
495
+
496
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
497
+
498
+ const scriptSig = psbtv2.getInputFinalScriptsig(0);
499
+ expect(scriptSig).toBeDefined();
500
+ expect(scriptSig).toEqual(finalScriptSig);
501
+ });
502
+
503
+ it("should transfer finalScriptWitness", () => {
504
+ const psbtv0 = new Psbt({ network: networks.testnet });
505
+
506
+ const finalScriptWitness = Buffer.from(
507
+ "02" + // 2 witness items
508
+ "47" +
509
+ "00".repeat(71) + // signature
510
+ "21" +
511
+ "00".repeat(33), // pubkey
512
+ "hex",
513
+ );
514
+
515
+ psbtv0.addInput({
516
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
517
+ hash: Buffer.alloc(32, 0),
518
+ index: 0,
519
+ witnessUtxo: {
520
+ script: Buffer.from("0014" + "00".repeat(20), "hex"),
521
+ value: 100000,
522
+ },
523
+ finalScriptWitness,
524
+ });
525
+
526
+ psbtv0.addOutput({
527
+ script: Buffer.from("0014" + "11".repeat(20), "hex"),
528
+ value: 90000,
529
+ });
530
+
531
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
532
+
533
+ const scriptWitness = psbtv2.getInputFinalScriptwitness(0);
534
+ expect(scriptWitness).toBeDefined();
535
+ expect(scriptWitness).toEqual(finalScriptWitness);
536
+ });
537
+ });
538
+
539
+ describe("Roundtrip Compatibility", () => {
540
+ it("should preserve data through serialize/deserialize after conversion", () => {
541
+ const psbtv0 = new Psbt({ network: networks.testnet });
542
+
543
+ psbtv0.addInput({
544
+ //@ts-expect-error TransactionInput interface is not declared correctly in bip174 lib
545
+ hash: Buffer.from(
546
+ "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
547
+ "hex",
548
+ ),
549
+ index: 7,
550
+ witnessUtxo: {
551
+ script: Buffer.from("0014" + "cc".repeat(20), "hex"),
552
+ value: 250000,
553
+ },
554
+ sequence: 0xfffffffe,
555
+ });
556
+
557
+ psbtv0.addOutput({
558
+ script: Buffer.from("0014" + "dd".repeat(20), "hex"),
559
+ value: 240000,
560
+ });
561
+
562
+ const psbtv2 = PsbtV2.fromV0(psbtv0.toBuffer(), true);
563
+
564
+ // Serialize and deserialize
565
+ const serialized = psbtv2.serialize();
566
+ const psbtv2Copy = new PsbtV2();
567
+ psbtv2Copy.deserialize(serialized);
568
+
569
+ // Verify data is preserved
570
+ expect(psbtv2Copy.getGlobalInputCount()).toBe(1);
571
+ expect(psbtv2Copy.getGlobalOutputCount()).toBe(1);
572
+ expect(psbtv2Copy.getInputOutputIndex(0)).toBe(7);
573
+ expect(psbtv2Copy.getInputSequence(0)).toBe(0xfffffffe);
574
+ expect(psbtv2Copy.getOutputAmount(0)).toBe(240000);
575
+ });
576
+ });
577
+ });
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { PsbtV2, psbtGlobal, psbtIn, psbtOut, NoSuchEntry } from "./psbtv2";
2
+ export { BufferReader, BufferWriter, unsafeTo64bitLE, unsafeFrom64bitLE } from "./buffertools";
3
+ export { normalizeToBuffer, parsePsbt } from "./psbtParsing";
@@ -0,0 +1,86 @@
1
+ import { normalizeToBuffer, parsePsbt } from "./psbtParsing";
2
+
3
+ describe("psbtParsing helpers", () => {
4
+ describe("normalizeToBuffer", () => {
5
+ it("returns null for empty or falsy input", () => {
6
+ // @ts-expect-error testing runtime behaviour with undefined
7
+ expect(normalizeToBuffer(undefined)).toBeNull();
8
+ // @ts-expect-error testing runtime behaviour with null
9
+ expect(normalizeToBuffer(null)).toBeNull();
10
+ const spaceBuf = normalizeToBuffer(" ");
11
+ expect(spaceBuf).not.toBeNull();
12
+ expect(spaceBuf!.length).toBe(0);
13
+ });
14
+
15
+ it("decodes hex strings", () => {
16
+ const buf = normalizeToBuffer("48656c6c6f"); // "Hello"
17
+ expect(buf).not.toBeNull();
18
+ expect(buf!.toString("utf8")).toBe("Hello");
19
+ });
20
+
21
+ it("decodes base64 strings with whitespace and URL-safe alphabet", () => {
22
+ const original = Buffer.from("hello world");
23
+ const urlSafe = original.toString("base64").replace(/\+/g, "-").replace(/\//g, "_");
24
+ const spaced = ` ${urlSafe.slice(0, 4)} \n ${urlSafe.slice(4)} `;
25
+
26
+ const buf = normalizeToBuffer(spaced);
27
+ expect(buf).not.toBeNull();
28
+ expect(buf!.toString("utf8")).toBe("hello world");
29
+ });
30
+
31
+ it("produces a buffer even for non-PSBT garbage", () => {
32
+ const buf = normalizeToBuffer("not base64!!!");
33
+ expect(buf).not.toBeNull();
34
+ expect(Buffer.isBuffer(buf)).toBe(true);
35
+ });
36
+ });
37
+
38
+ describe("parsePsbt", () => {
39
+ it("returns a Buffer for a valid base64 PSBT", () => {
40
+ // Minimal valid PSBTv0-like structure: magic bytes + 0x00 (end of global map)
41
+ // then 0x00 (no inputs) and 0x00 (no outputs).
42
+ const minimalPsbt = Buffer.concat([
43
+ Buffer.from([0x70, 0x73, 0x62, 0x74, 0xff]),
44
+ Buffer.from([0x00, 0x00, 0x00]),
45
+ ]);
46
+
47
+ const b64 = minimalPsbt.toString("base64");
48
+ const parsed = parsePsbt(b64);
49
+
50
+ expect(Buffer.isBuffer(parsed)).toBe(true);
51
+ expect(parsed.equals(minimalPsbt)).toBe(true);
52
+ });
53
+
54
+ it("throws specific error for missing/empty input", () => {
55
+ expect(() => parsePsbt("")).toThrow("Invalid PSBT: not valid base64");
56
+ });
57
+
58
+ it("throws specific error for whitespace-only input (empty buffer)", () => {
59
+ expect(() => parsePsbt(" ")).toThrow("Invalid PSBT: empty buffer");
60
+ });
61
+
62
+ it("propagates base64 decoder failures via normalizeToBuffer", () => {
63
+ const originalFrom = Buffer.from;
64
+
65
+ // Force Buffer.from to throw for a specific base64 case so that
66
+ // normalizeToBuffer's catch branch is exercised.
67
+ const patchedFrom = (value: string | ArrayBufferView, encoding?: BufferEncoding): Buffer => {
68
+ if (encoding === "base64" && typeof value === "string" && value.includes("causeError")) {
69
+ throw new Error("base64 decode failure");
70
+ }
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/consistent-type-assertions
72
+ return originalFrom(value as any, encoding as any);
73
+ };
74
+
75
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/consistent-type-assertions
76
+ (Buffer as any).from = patchedFrom as any;
77
+
78
+ try {
79
+ expect(() => parsePsbt("causeError")).toThrow("Invalid PSBT: not valid base64");
80
+ } finally {
81
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/consistent-type-assertions
82
+ (Buffer as any).from = originalFrom as any;
83
+ }
84
+ });
85
+ });
86
+ });