@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
package/lib/psbtv2.js ADDED
@@ -0,0 +1,647 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsbtV2 = exports.NoSuchEntry = exports.psbtOut = exports.psbtIn = exports.psbtGlobal = void 0;
4
+ exports.parseBip32Path = parseBip32Path;
5
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
6
+ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
7
+ const bitcoinjs_lib_1 = require("bitcoinjs-lib");
8
+ const buffertools_1 = require("./buffertools");
9
+ var psbtGlobal;
10
+ (function (psbtGlobal) {
11
+ psbtGlobal[psbtGlobal["TX_VERSION"] = 2] = "TX_VERSION";
12
+ psbtGlobal[psbtGlobal["FALLBACK_LOCKTIME"] = 3] = "FALLBACK_LOCKTIME";
13
+ psbtGlobal[psbtGlobal["INPUT_COUNT"] = 4] = "INPUT_COUNT";
14
+ psbtGlobal[psbtGlobal["OUTPUT_COUNT"] = 5] = "OUTPUT_COUNT";
15
+ psbtGlobal[psbtGlobal["TX_MODIFIABLE"] = 6] = "TX_MODIFIABLE";
16
+ psbtGlobal[psbtGlobal["VERSION"] = 251] = "VERSION";
17
+ })(psbtGlobal || (exports.psbtGlobal = psbtGlobal = {}));
18
+ var psbtIn;
19
+ (function (psbtIn) {
20
+ psbtIn[psbtIn["NON_WITNESS_UTXO"] = 0] = "NON_WITNESS_UTXO";
21
+ psbtIn[psbtIn["WITNESS_UTXO"] = 1] = "WITNESS_UTXO";
22
+ psbtIn[psbtIn["PARTIAL_SIG"] = 2] = "PARTIAL_SIG";
23
+ psbtIn[psbtIn["SIGHASH_TYPE"] = 3] = "SIGHASH_TYPE";
24
+ psbtIn[psbtIn["REDEEM_SCRIPT"] = 4] = "REDEEM_SCRIPT";
25
+ psbtIn[psbtIn["BIP32_DERIVATION"] = 6] = "BIP32_DERIVATION";
26
+ psbtIn[psbtIn["FINAL_SCRIPTSIG"] = 7] = "FINAL_SCRIPTSIG";
27
+ psbtIn[psbtIn["FINAL_SCRIPTWITNESS"] = 8] = "FINAL_SCRIPTWITNESS";
28
+ psbtIn[psbtIn["PREVIOUS_TXID"] = 14] = "PREVIOUS_TXID";
29
+ psbtIn[psbtIn["OUTPUT_INDEX"] = 15] = "OUTPUT_INDEX";
30
+ psbtIn[psbtIn["SEQUENCE"] = 16] = "SEQUENCE";
31
+ psbtIn[psbtIn["TAP_KEY_SIG"] = 19] = "TAP_KEY_SIG";
32
+ psbtIn[psbtIn["TAP_BIP32_DERIVATION"] = 22] = "TAP_BIP32_DERIVATION";
33
+ })(psbtIn || (exports.psbtIn = psbtIn = {}));
34
+ var psbtOut;
35
+ (function (psbtOut) {
36
+ psbtOut[psbtOut["REDEEM_SCRIPT"] = 0] = "REDEEM_SCRIPT";
37
+ psbtOut[psbtOut["BIP_32_DERIVATION"] = 2] = "BIP_32_DERIVATION";
38
+ psbtOut[psbtOut["AMOUNT"] = 3] = "AMOUNT";
39
+ psbtOut[psbtOut["SCRIPT"] = 4] = "SCRIPT";
40
+ psbtOut[psbtOut["TAP_BIP32_DERIVATION"] = 7] = "TAP_BIP32_DERIVATION";
41
+ })(psbtOut || (exports.psbtOut = psbtOut = {}));
42
+ const PSBT_MAGIC_BYTES = Buffer.from([0x70, 0x73, 0x62, 0x74, 0xff]);
43
+ class NoSuchEntry extends Error {
44
+ }
45
+ exports.NoSuchEntry = NoSuchEntry;
46
+ /**
47
+ * Implements Partially Signed Bitcoin Transaction version 2, BIP370, as
48
+ * documented at https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki
49
+ * and https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
50
+ *
51
+ * A psbt is a data structure that can carry all relevant information about a
52
+ * transaction through all stages of the signing process. From constructing an
53
+ * unsigned transaction to extracting the final serialized transaction ready for
54
+ * broadcast.
55
+ *
56
+ * This implementation is limited to what's needed in ledgerjs to carry out its
57
+ * duties, which means that support for features like multisig or taproot script
58
+ * path spending are not implemented. Specifically, it supports p2pkh,
59
+ * p2wpkhWrappedInP2sh, p2wpkh and p2tr key path spending.
60
+ *
61
+ * This class is made purposefully dumb, so it's easy to add support for
62
+ * complemantary fields as needed in the future.
63
+ */
64
+ class PsbtV2 {
65
+ globalMap = new Map();
66
+ inputMaps = [];
67
+ outputMaps = [];
68
+ setGlobalTxVersion(version) {
69
+ this.setGlobal(psbtGlobal.TX_VERSION, uint32LE(version));
70
+ }
71
+ getGlobalTxVersion() {
72
+ return this.getGlobal(psbtGlobal.TX_VERSION).readUInt32LE(0);
73
+ }
74
+ setGlobalFallbackLocktime(locktime) {
75
+ this.setGlobal(psbtGlobal.FALLBACK_LOCKTIME, uint32LE(locktime));
76
+ }
77
+ getGlobalFallbackLocktime() {
78
+ return this.getGlobalOptional(psbtGlobal.FALLBACK_LOCKTIME)?.readUInt32LE(0);
79
+ }
80
+ setGlobalInputCount(inputCount) {
81
+ this.setGlobal(psbtGlobal.INPUT_COUNT, varint(inputCount));
82
+ }
83
+ getGlobalInputCount() {
84
+ return fromVarint(this.getGlobal(psbtGlobal.INPUT_COUNT));
85
+ }
86
+ setGlobalOutputCount(outputCount) {
87
+ this.setGlobal(psbtGlobal.OUTPUT_COUNT, varint(outputCount));
88
+ }
89
+ getGlobalOutputCount() {
90
+ return fromVarint(this.getGlobal(psbtGlobal.OUTPUT_COUNT));
91
+ }
92
+ setGlobalTxModifiable(byte) {
93
+ this.setGlobal(psbtGlobal.TX_MODIFIABLE, byte);
94
+ }
95
+ getGlobalTxModifiable() {
96
+ return this.getGlobalOptional(psbtGlobal.TX_MODIFIABLE);
97
+ }
98
+ setGlobalPsbtVersion(psbtVersion) {
99
+ this.setGlobal(psbtGlobal.VERSION, uint32LE(psbtVersion));
100
+ }
101
+ getGlobalPsbtVersion() {
102
+ return this.getGlobal(psbtGlobal.VERSION).readUInt32LE(0);
103
+ }
104
+ setInputNonWitnessUtxo(inputIndex, transaction) {
105
+ this.setInput(inputIndex, psbtIn.NON_WITNESS_UTXO, b(), transaction);
106
+ }
107
+ getInputNonWitnessUtxo(inputIndex) {
108
+ return this.getInputOptional(inputIndex, psbtIn.NON_WITNESS_UTXO, b());
109
+ }
110
+ setInputWitnessUtxo(inputIndex, amount, scriptPubKey) {
111
+ const buf = new buffertools_1.BufferWriter();
112
+ buf.writeSlice(amount);
113
+ buf.writeVarSlice(scriptPubKey);
114
+ this.setInput(inputIndex, psbtIn.WITNESS_UTXO, b(), buf.buffer());
115
+ }
116
+ getInputWitnessUtxo(inputIndex) {
117
+ const utxo = this.getInputOptional(inputIndex, psbtIn.WITNESS_UTXO, b());
118
+ if (!utxo)
119
+ return undefined;
120
+ const buf = new buffertools_1.BufferReader(utxo);
121
+ return { amount: buf.readSlice(8), scriptPubKey: buf.readVarSlice() };
122
+ }
123
+ setInputPartialSig(inputIndex, pubkey, signature) {
124
+ this.setInput(inputIndex, psbtIn.PARTIAL_SIG, pubkey, signature);
125
+ }
126
+ getInputPartialSig(inputIndex, pubkey) {
127
+ return this.getInputOptional(inputIndex, psbtIn.PARTIAL_SIG, pubkey);
128
+ }
129
+ setInputSighashType(inputIndex, sigHashtype) {
130
+ this.setInput(inputIndex, psbtIn.SIGHASH_TYPE, b(), uint32LE(sigHashtype));
131
+ }
132
+ getInputSighashType(inputIndex) {
133
+ const result = this.getInputOptional(inputIndex, psbtIn.SIGHASH_TYPE, b());
134
+ if (!result)
135
+ return undefined;
136
+ return result.readUInt32LE(0);
137
+ }
138
+ setInputRedeemScript(inputIndex, redeemScript) {
139
+ this.setInput(inputIndex, psbtIn.REDEEM_SCRIPT, b(), redeemScript);
140
+ }
141
+ getInputRedeemScript(inputIndex) {
142
+ return this.getInputOptional(inputIndex, psbtIn.REDEEM_SCRIPT, b());
143
+ }
144
+ setInputBip32Derivation(inputIndex, pubkey, masterFingerprint, path) {
145
+ if (pubkey.length != 33)
146
+ throw new Error("Invalid pubkey length: " + pubkey.length);
147
+ this.setInput(inputIndex, psbtIn.BIP32_DERIVATION, pubkey, this.encodeBip32Derivation(masterFingerprint, path));
148
+ }
149
+ getInputBip32Derivation(inputIndex, pubkey) {
150
+ const buf = this.getInputOptional(inputIndex, psbtIn.BIP32_DERIVATION, pubkey);
151
+ if (!buf)
152
+ return undefined;
153
+ return this.decodeBip32Derivation(buf);
154
+ }
155
+ setInputFinalScriptsig(inputIndex, scriptSig) {
156
+ this.setInput(inputIndex, psbtIn.FINAL_SCRIPTSIG, b(), scriptSig);
157
+ }
158
+ getInputFinalScriptsig(inputIndex) {
159
+ return this.getInputOptional(inputIndex, psbtIn.FINAL_SCRIPTSIG, b());
160
+ }
161
+ setInputFinalScriptwitness(inputIndex, scriptWitness) {
162
+ this.setInput(inputIndex, psbtIn.FINAL_SCRIPTWITNESS, b(), scriptWitness);
163
+ }
164
+ getInputFinalScriptwitness(inputIndex) {
165
+ return this.getInput(inputIndex, psbtIn.FINAL_SCRIPTWITNESS, b());
166
+ }
167
+ setInputPreviousTxId(inputIndex, txid) {
168
+ this.setInput(inputIndex, psbtIn.PREVIOUS_TXID, b(), txid);
169
+ }
170
+ getInputPreviousTxid(inputIndex) {
171
+ return this.getInput(inputIndex, psbtIn.PREVIOUS_TXID, b());
172
+ }
173
+ setInputOutputIndex(inputIndex, outputIndex) {
174
+ this.setInput(inputIndex, psbtIn.OUTPUT_INDEX, b(), uint32LE(outputIndex));
175
+ }
176
+ getInputOutputIndex(inputIndex) {
177
+ return this.getInput(inputIndex, psbtIn.OUTPUT_INDEX, b()).readUInt32LE(0);
178
+ }
179
+ setInputSequence(inputIndex, sequence) {
180
+ this.setInput(inputIndex, psbtIn.SEQUENCE, b(), uint32LE(sequence));
181
+ }
182
+ getInputSequence(inputIndex) {
183
+ return this.getInputOptional(inputIndex, psbtIn.SEQUENCE, b())?.readUInt32LE(0) ?? 0xffffffff;
184
+ }
185
+ setInputTapKeySig(inputIndex, sig) {
186
+ this.setInput(inputIndex, psbtIn.TAP_KEY_SIG, b(), sig);
187
+ }
188
+ getInputTapKeySig(inputIndex) {
189
+ return this.getInputOptional(inputIndex, psbtIn.TAP_KEY_SIG, b());
190
+ }
191
+ setInputTapBip32Derivation(inputIndex, pubkey, hashes, masterFingerprint, path) {
192
+ if (pubkey.length != 32)
193
+ throw new Error("Invalid pubkey length: " + pubkey.length);
194
+ const buf = this.encodeTapBip32Derivation(hashes, masterFingerprint, path);
195
+ this.setInput(inputIndex, psbtIn.TAP_BIP32_DERIVATION, pubkey, buf);
196
+ }
197
+ getInputTapBip32Derivation(inputIndex, pubkey) {
198
+ const buf = this.getInput(inputIndex, psbtIn.TAP_BIP32_DERIVATION, pubkey);
199
+ return this.decodeTapBip32Derivation(buf);
200
+ }
201
+ getInputKeyDatas(inputIndex, keyType) {
202
+ return this.getKeyDatas(this.inputMaps[inputIndex], keyType);
203
+ }
204
+ setOutputRedeemScript(outputIndex, redeemScript) {
205
+ this.setOutput(outputIndex, psbtOut.REDEEM_SCRIPT, b(), redeemScript);
206
+ }
207
+ getOutputRedeemScript(outputIndex) {
208
+ return this.getOutput(outputIndex, psbtOut.REDEEM_SCRIPT, b());
209
+ }
210
+ setOutputBip32Derivation(outputIndex, pubkey, masterFingerprint, path) {
211
+ this.setOutput(outputIndex, psbtOut.BIP_32_DERIVATION, pubkey, this.encodeBip32Derivation(masterFingerprint, path));
212
+ }
213
+ getOutputBip32Derivation(outputIndex, pubkey) {
214
+ const buf = this.getOutput(outputIndex, psbtOut.BIP_32_DERIVATION, pubkey);
215
+ return this.decodeBip32Derivation(buf);
216
+ }
217
+ setOutputAmount(outputIndex, amount) {
218
+ this.setOutput(outputIndex, psbtOut.AMOUNT, b(), uint64LE(amount));
219
+ }
220
+ getOutputAmount(outputIndex) {
221
+ const buf = this.getOutput(outputIndex, psbtOut.AMOUNT, b());
222
+ return (0, buffertools_1.unsafeFrom64bitLE)(buf);
223
+ }
224
+ setOutputScript(outputIndex, scriptPubKey) {
225
+ this.setOutput(outputIndex, psbtOut.SCRIPT, b(), scriptPubKey);
226
+ }
227
+ getOutputScript(outputIndex) {
228
+ return this.getOutput(outputIndex, psbtOut.SCRIPT, b());
229
+ }
230
+ setOutputTapBip32Derivation(outputIndex, pubkey, hashes, fingerprint, path) {
231
+ const buf = this.encodeTapBip32Derivation(hashes, fingerprint, path);
232
+ this.setOutput(outputIndex, psbtOut.TAP_BIP32_DERIVATION, pubkey, buf);
233
+ }
234
+ getOutputTapBip32Derivation(outputIndex, pubkey) {
235
+ const buf = this.getOutput(outputIndex, psbtOut.TAP_BIP32_DERIVATION, pubkey);
236
+ return this.decodeTapBip32Derivation(buf);
237
+ }
238
+ deleteInputEntries(inputIndex, keyTypes) {
239
+ const map = this.inputMaps[inputIndex];
240
+ map.forEach((_v, k, m) => {
241
+ if (this.isKeyType(k, keyTypes)) {
242
+ m.delete(k);
243
+ }
244
+ });
245
+ }
246
+ copy(to) {
247
+ this.copyMap(this.globalMap, to.globalMap);
248
+ this.copyMaps(this.inputMaps, to.inputMaps);
249
+ this.copyMaps(this.outputMaps, to.outputMaps);
250
+ }
251
+ copyMaps(from, to) {
252
+ from.forEach((m, index) => {
253
+ const to_index = new Map();
254
+ this.copyMap(m, to_index);
255
+ to[index] = to_index;
256
+ });
257
+ }
258
+ copyMap(from, to) {
259
+ from.forEach((v, k) => to.set(k, Buffer.from(v)));
260
+ }
261
+ serialize() {
262
+ const buf = new buffertools_1.BufferWriter();
263
+ buf.writeSlice(PSBT_MAGIC_BYTES);
264
+ serializeMap(buf, this.globalMap);
265
+ this.inputMaps.forEach(map => {
266
+ serializeMap(buf, map);
267
+ });
268
+ this.outputMaps.forEach(map => {
269
+ serializeMap(buf, map);
270
+ });
271
+ return buf.buffer();
272
+ }
273
+ deserialize(psbt) {
274
+ const buf = new buffertools_1.BufferReader(psbt);
275
+ if (!buf.readSlice(5).equals(PSBT_MAGIC_BYTES)) {
276
+ throw new Error("Invalid magic bytes");
277
+ }
278
+ while (this.readKeyPair(this.globalMap, buf))
279
+ ;
280
+ for (let i = 0; i < this.getGlobalInputCount(); i++) {
281
+ this.inputMaps[i] = new Map();
282
+ while (this.readKeyPair(this.inputMaps[i], buf))
283
+ ;
284
+ }
285
+ for (let i = 0; i < this.getGlobalOutputCount(); i++) {
286
+ this.outputMaps[i] = new Map();
287
+ while (this.readKeyPair(this.outputMaps[i], buf))
288
+ ;
289
+ }
290
+ }
291
+ /**
292
+ * Attempts to extract the version number as uint32LE from raw psbt regardless
293
+ * of psbt validity.
294
+ *
295
+ * @param psbt - PSBT buffer to extract version from
296
+ * @returns The PSBT version number, or 0 if no version field is found (indicating PSBTv0)
297
+ *
298
+ * @example
299
+ * ```typescript
300
+ * const psbtBuffer = Buffer.from('cHNidP8BAH...', 'base64');
301
+ * const version = PsbtV2.getPsbtVersionNumber(psbtBuffer);
302
+ * if (version === 2) {
303
+ * // Handle PSBTv2
304
+ * } else {
305
+ * // Handle PSBTv0
306
+ * }
307
+ * ```
308
+ */
309
+ static getPsbtVersionNumber(psbt) {
310
+ const map = new Map();
311
+ const buf = new buffertools_1.BufferReader(psbt.subarray(PSBT_MAGIC_BYTES.length));
312
+ // Read global map key-value pairs
313
+ while (buf.available() > 0) {
314
+ const keyLen = buf.readVarInt();
315
+ if (keyLen === 0)
316
+ break; // End of global map
317
+ const keyType = buf.readUInt8();
318
+ const keyData = keyLen > 1 ? buf.readSlice(keyLen - 1) : Buffer.alloc(0);
319
+ const key = Buffer.concat([Buffer.from([keyType]), keyData]).toString("hex");
320
+ const valueLen = buf.readVarInt();
321
+ const value = valueLen > 0 ? buf.readSlice(valueLen) : Buffer.alloc(0);
322
+ map.set(key, value);
323
+ }
324
+ // Look for PSBT version field (0xfb)
325
+ const versionKey = Buffer.from([psbtGlobal.VERSION]).toString("hex");
326
+ const versionValue = map.get(versionKey);
327
+ return versionValue ? versionValue.readUInt32LE(0) : 0;
328
+ }
329
+ /**
330
+ * Converts a PSBTv0 (from bitcoinjs-lib) to PSBTv2.
331
+ *
332
+ * This method deserializes a PSBTv0 buffer and converts it
333
+ * to the PSBTv2 format, preserving all relevant fields including:
334
+ * - Transaction version and locktime
335
+ * - Inputs (UTXOs, derivation paths, sequences, signatures)
336
+ * - Outputs (amounts, scripts, derivation paths)
337
+ * - Finalized scripts (if present)
338
+ *
339
+ * The method follows the PSBT role saga defined in BIP174:
340
+ * 1. Creator Role - Initialize PSBTv2 with version and counts
341
+ * 2. Constructor Role - Add inputs and outputs
342
+ * 3. Signer Role - Transfer partial signatures
343
+ * 4. Input Finalizer - Transfer finalized scripts
344
+ *
345
+ * @param psbt - PSBTv0 as Buffer
346
+ * @param allowTxnVersion1 - Allow transaction version 1 (default: false).
347
+ * Version 2 is recommended per BIP68.
348
+ * @returns A new PsbtV2 instance with converted data
349
+ * @throws Error if PSBT is invalid or contains unsupported features
350
+ *
351
+ * @example
352
+ * ```typescript
353
+ * const psbtV0Buffer = Buffer.from('cHNidP8BAH...', 'base64');
354
+ * const psbtV2 = PsbtV2.fromV0(psbtV0Buffer);
355
+ * ```
356
+ */
357
+ static fromV0(psbt, allowTxnVersion1 = false) {
358
+ const psbtv0 = bitcoinjs_lib_1.Psbt.fromBuffer(psbt);
359
+ // Creator Role - Initialize PSBTv2
360
+ const psbtv2 = new PsbtV2();
361
+ PsbtV2.initializeFromV0(psbtv2, psbtv0, allowTxnVersion1);
362
+ // Constructor Role - Add inputs and outputs
363
+ const txBuffer = psbtv0.data.getTransaction();
364
+ const tx = bitcoinjs_lib_1.Transaction.fromBuffer(txBuffer);
365
+ PsbtV2.addInputsFromV0(psbtv2, psbtv0, tx);
366
+ PsbtV2.addOutputsFromV0(psbtv2, psbtv0, tx);
367
+ // Signer Role - Transfer partial signatures
368
+ PsbtV2.transferPartialSignatures(psbtv2, psbtv0);
369
+ // Input Finalizer - Transfer finalized scripts
370
+ PsbtV2.transferFinalizedScripts(psbtv2, psbtv0);
371
+ return psbtv2;
372
+ }
373
+ static initializeFromV0(psbtv2, psbtv0, allowTxnVersion1) {
374
+ const txVersion = psbtv0.data.getTransaction().readInt32LE(0);
375
+ if (txVersion === 1 && !allowTxnVersion1) {
376
+ throw new Error("Transaction version 1 detected. PSBTv2 recommends version 2 for BIP68 sequence support. " +
377
+ "Pass allowTxnVersion1=true to override.");
378
+ }
379
+ psbtv2.setGlobalTxVersion(txVersion);
380
+ psbtv2.setGlobalFallbackLocktime(psbtv0.locktime);
381
+ psbtv2.setGlobalInputCount(psbtv0.data.inputs.length);
382
+ psbtv2.setGlobalOutputCount(psbtv0.data.outputs.length);
383
+ psbtv2.setGlobalPsbtVersion(2);
384
+ }
385
+ static addInputsFromV0(psbtv2, psbtv0, tx) {
386
+ for (const [index, input] of psbtv0.data.inputs.entries()) {
387
+ // Required fields for PSBTv2 - get from the embedded transaction
388
+ psbtv2.setInputPreviousTxId(index, Buffer.from(tx.ins[index].hash).reverse());
389
+ psbtv2.setInputOutputIndex(index, tx.ins[index].index);
390
+ psbtv2.setInputSequence(index, tx.ins[index].sequence);
391
+ // Optional UTXO information
392
+ if (input.nonWitnessUtxo) {
393
+ psbtv2.setInputNonWitnessUtxo(index, input.nonWitnessUtxo);
394
+ }
395
+ if (input.witnessUtxo) {
396
+ // Convert bitcoinjs-lib format {value, script} to PSBTv2 format {amount, scriptPubKey}
397
+ const amount = (0, buffertools_1.unsafeTo64bitLE)(input.witnessUtxo.value);
398
+ psbtv2.setInputWitnessUtxo(index, amount, input.witnessUtxo.script);
399
+ }
400
+ // Optional scripts and derivation
401
+ if (input.redeemScript) {
402
+ psbtv2.setInputRedeemScript(index, input.redeemScript);
403
+ }
404
+ if (input.sighashType !== undefined) {
405
+ psbtv2.setInputSighashType(index, input.sighashType);
406
+ }
407
+ if (input.bip32Derivation) {
408
+ for (const deriv of input.bip32Derivation) {
409
+ psbtv2.setInputBip32Derivation(index, deriv.pubkey, deriv.masterFingerprint, parseBip32Path(deriv.path));
410
+ }
411
+ }
412
+ }
413
+ }
414
+ static addOutputsFromV0(psbtv2, psbtv0, tx) {
415
+ // Constructor Role - Add outputs
416
+ for (const [index, output] of psbtv0.data.outputs.entries()) {
417
+ // Required fields for PSBTv2 - get from the embedded transaction
418
+ psbtv2.setOutputAmount(index, tx.outs[index].value);
419
+ psbtv2.setOutputScript(index, tx.outs[index].script);
420
+ // Optional fields
421
+ if (output.redeemScript) {
422
+ psbtv2.setOutputRedeemScript(index, output.redeemScript);
423
+ }
424
+ if (output.bip32Derivation) {
425
+ for (const deriv of output.bip32Derivation) {
426
+ psbtv2.setOutputBip32Derivation(index, deriv.pubkey, deriv.masterFingerprint, parseBip32Path(deriv.path));
427
+ }
428
+ }
429
+ }
430
+ }
431
+ static transferPartialSignatures(psbtv2, psbtv0) {
432
+ for (const [index, input] of psbtv0.data.inputs.entries()) {
433
+ if (input.partialSig) {
434
+ for (const sig of input.partialSig) {
435
+ psbtv2.setInputPartialSig(index, sig.pubkey, sig.signature);
436
+ }
437
+ }
438
+ }
439
+ }
440
+ static transferFinalizedScripts(psbtv2, psbtv0) {
441
+ // Input Finalizer - Transfer finalized scripts
442
+ // Note: Per BIP174, the Input Finalizer should remove other fields after
443
+ // finalization, but we preserve them for compatibility with the source PSBTv0
444
+ for (const [index, input] of psbtv0.data.inputs.entries()) {
445
+ if (input.finalScriptSig) {
446
+ psbtv2.setInputFinalScriptsig(index, input.finalScriptSig);
447
+ }
448
+ if (input.finalScriptWitness) {
449
+ psbtv2.setInputFinalScriptwitness(index, input.finalScriptWitness);
450
+ }
451
+ }
452
+ }
453
+ readKeyPair(map, buf) {
454
+ const keyLen = buf.readVarInt();
455
+ if (keyLen == 0) {
456
+ return false;
457
+ }
458
+ const keyType = buf.readUInt8();
459
+ const keyData = buf.readSlice(keyLen - 1);
460
+ const value = buf.readVarSlice();
461
+ set(map, keyType, keyData, value);
462
+ return true;
463
+ }
464
+ getKeyDatas(map, keyType) {
465
+ const result = [];
466
+ map.forEach((_v, k) => {
467
+ if (this.isKeyType(k, [keyType])) {
468
+ result.push(Buffer.from(k.substring(2), "hex"));
469
+ }
470
+ });
471
+ return result;
472
+ }
473
+ isKeyType(hexKey, keyTypes) {
474
+ const keyType = Buffer.from(hexKey.substring(0, 2), "hex").readUInt8(0);
475
+ return keyTypes.some(k => k == keyType);
476
+ }
477
+ setGlobal(keyType, value) {
478
+ const key = new Key(keyType, Buffer.from([]));
479
+ this.globalMap.set(key.toString(), value);
480
+ }
481
+ getGlobal(keyType) {
482
+ return get(this.globalMap, keyType, b(), false);
483
+ }
484
+ getGlobalOptional(keyType) {
485
+ return get(this.globalMap, keyType, b(), true);
486
+ }
487
+ setInput(index, keyType, keyData, value) {
488
+ set(this.getMap(index, this.inputMaps), keyType, keyData, value);
489
+ }
490
+ getInput(index, keyType, keyData) {
491
+ return get(this.inputMaps[index], keyType, keyData, false);
492
+ }
493
+ getInputOptional(index, keyType, keyData) {
494
+ return get(this.inputMaps[index], keyType, keyData, true);
495
+ }
496
+ setOutput(index, keyType, keyData, value) {
497
+ set(this.getMap(index, this.outputMaps), keyType, keyData, value);
498
+ }
499
+ getOutput(index, keyType, keyData) {
500
+ return get(this.outputMaps[index], keyType, keyData, false);
501
+ }
502
+ getMap(index, maps) {
503
+ if (!maps[index]) {
504
+ maps[index] = new Map();
505
+ }
506
+ return maps[index];
507
+ }
508
+ encodeBip32Derivation(masterFingerprint, path) {
509
+ const buf = new buffertools_1.BufferWriter();
510
+ this.writeBip32Derivation(buf, masterFingerprint, path);
511
+ return buf.buffer();
512
+ }
513
+ decodeBip32Derivation(buffer) {
514
+ const buf = new buffertools_1.BufferReader(buffer);
515
+ return this.readBip32Derivation(buf);
516
+ }
517
+ writeBip32Derivation(buf, masterFingerprint, path) {
518
+ buf.writeSlice(masterFingerprint);
519
+ path.forEach(element => {
520
+ buf.writeUInt32(element);
521
+ });
522
+ }
523
+ readBip32Derivation(buf) {
524
+ const masterFingerprint = buf.readSlice(4);
525
+ const path = [];
526
+ while (buf.offset < buf.buffer.length) {
527
+ path.push(buf.readUInt32());
528
+ }
529
+ return { masterFingerprint, path };
530
+ }
531
+ encodeTapBip32Derivation(hashes, masterFingerprint, path) {
532
+ const buf = new buffertools_1.BufferWriter();
533
+ buf.writeVarInt(hashes.length);
534
+ hashes.forEach(h => {
535
+ buf.writeSlice(h);
536
+ });
537
+ this.writeBip32Derivation(buf, masterFingerprint, path);
538
+ return buf.buffer();
539
+ }
540
+ decodeTapBip32Derivation(buffer) {
541
+ const buf = new buffertools_1.BufferReader(buffer);
542
+ const hashCount = buf.readVarInt();
543
+ const hashes = [];
544
+ for (let i = 0; i < hashCount; i++) {
545
+ hashes.push(buf.readSlice(32));
546
+ }
547
+ const deriv = this.readBip32Derivation(buf);
548
+ return { hashes, ...deriv };
549
+ }
550
+ }
551
+ exports.PsbtV2 = PsbtV2;
552
+ function get(map, keyType, keyData, acceptUndefined) {
553
+ if (!map)
554
+ throw new Error("No such map");
555
+ const key = new Key(keyType, keyData);
556
+ const value = map.get(key.toString());
557
+ if (!value) {
558
+ if (acceptUndefined) {
559
+ return undefined;
560
+ }
561
+ throw new NoSuchEntry(key.toString());
562
+ }
563
+ // Make sure to return a copy, to protect the underlying data.
564
+ return Buffer.from(value);
565
+ }
566
+ class Key {
567
+ keyType;
568
+ keyData;
569
+ constructor(keyType, keyData) {
570
+ this.keyType = keyType;
571
+ this.keyData = keyData;
572
+ }
573
+ toString() {
574
+ const buf = new buffertools_1.BufferWriter();
575
+ this.toBuffer(buf);
576
+ return buf.buffer().toString("hex");
577
+ }
578
+ serialize(buf) {
579
+ buf.writeVarInt(1 + this.keyData.length);
580
+ this.toBuffer(buf);
581
+ }
582
+ toBuffer(buf) {
583
+ buf.writeUInt8(this.keyType);
584
+ buf.writeSlice(this.keyData);
585
+ }
586
+ }
587
+ class KeyPair {
588
+ key;
589
+ value;
590
+ constructor(key, value) {
591
+ this.key = key;
592
+ this.value = value;
593
+ }
594
+ serialize(buf) {
595
+ this.key.serialize(buf);
596
+ buf.writeVarSlice(this.value);
597
+ }
598
+ }
599
+ function createKey(buf) {
600
+ return new Key(buf.readUInt8(0), buf.subarray(1));
601
+ }
602
+ function serializeMap(buf, map) {
603
+ for (const k of map.keys()) {
604
+ const value = map.get(k);
605
+ const keyPair = new KeyPair(createKey(Buffer.from(k, "hex")), value);
606
+ keyPair.serialize(buf);
607
+ }
608
+ buf.writeUInt8(0);
609
+ }
610
+ function b() {
611
+ return Buffer.from([]);
612
+ }
613
+ function set(map, keyType, keyData, value) {
614
+ const key = new Key(keyType, keyData);
615
+ map.set(key.toString(), value);
616
+ }
617
+ function uint32LE(n) {
618
+ const b = Buffer.alloc(4);
619
+ b.writeUInt32LE(n, 0);
620
+ return b;
621
+ }
622
+ function uint64LE(n) {
623
+ return (0, buffertools_1.unsafeTo64bitLE)(n);
624
+ }
625
+ function varint(n) {
626
+ const b = new buffertools_1.BufferWriter();
627
+ b.writeVarInt(n);
628
+ return b.buffer();
629
+ }
630
+ function fromVarint(buf) {
631
+ return new buffertools_1.BufferReader(buf).readVarInt();
632
+ }
633
+ function parseBip32Path(path) {
634
+ return path
635
+ .split("/")
636
+ .slice(1)
637
+ .map(s => {
638
+ const hardened = s.endsWith("'") || s.endsWith("h");
639
+ const base = hardened ? s.slice(0, -1) : s;
640
+ const num = Number(base);
641
+ if (Number.isNaN(num)) {
642
+ throw new TypeError(`Invalid BIP32 path segment: ${path}`);
643
+ }
644
+ return hardened ? num + 0x80000000 : num;
645
+ });
646
+ }
647
+ //# sourceMappingURL=psbtv2.js.map