@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
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
|