@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.
- package/.turbo/turbo-build.log +4 -0
- package/LICENSE.txt +21 -0
- package/README.md +91 -0
- package/jest.config.js +26 -0
- package/lib/buffertools.d.ts +29 -0
- package/lib/buffertools.d.ts.map +1 -0
- package/lib/buffertools.js +129 -0
- package/lib/buffertools.js.map +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +18 -0
- package/lib/index.js.map +1 -0
- package/lib/psbtParsing.d.ts +15 -0
- package/lib/psbtParsing.d.ts.map +1 -0
- package/lib/psbtParsing.js +52 -0
- package/lib/psbtParsing.js.map +1 -0
- package/lib/psbtv2.d.ts +200 -0
- package/lib/psbtv2.d.ts.map +1 -0
- package/lib/psbtv2.js +647 -0
- package/lib/psbtv2.js.map +1 -0
- package/lib-es/buffertools.d.ts +29 -0
- package/lib-es/buffertools.d.ts.map +1 -0
- package/lib-es/buffertools.js +119 -0
- package/lib-es/buffertools.js.map +1 -0
- package/lib-es/index.d.ts +4 -0
- package/lib-es/index.d.ts.map +1 -0
- package/lib-es/index.js +4 -0
- package/lib-es/index.js.map +1 -0
- package/lib-es/psbtParsing.d.ts +15 -0
- package/lib-es/psbtParsing.d.ts.map +1 -0
- package/lib-es/psbtParsing.js +48 -0
- package/lib-es/psbtParsing.js.map +1 -0
- package/lib-es/psbtv2.d.ts +200 -0
- package/lib-es/psbtv2.d.ts.map +1 -0
- package/lib-es/psbtv2.js +641 -0
- package/lib-es/psbtv2.js.map +1 -0
- package/package.json +78 -0
- package/src/buffertools.test.ts +116 -0
- package/src/buffertools.ts +137 -0
- package/src/fromV0.test.ts +577 -0
- package/src/index.ts +3 -0
- package/src/psbtParsing.test.ts +86 -0
- package/src/psbtParsing.ts +51 -0
- package/src/psbtv2.test.ts +441 -0
- package/src/psbtv2.ts +740 -0
- 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,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
|
+
});
|