ecash-lib 4.10.0 → 4.12.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/README.md +2 -0
- package/dist/consts.d.ts +11 -0
- package/dist/consts.d.ts.map +1 -1
- package/dist/consts.js +12 -1
- package/dist/consts.js.map +1 -1
- package/dist/ffi/ecash_lib_wasm_bg_browser.js +20489 -20489
- package/dist/ffi/ecash_lib_wasm_bg_browser.wasm +0 -0
- package/dist/ffi/ecash_lib_wasm_bg_nodejs.wasm +0 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/psbt.d.ts +115 -0
- package/dist/psbt.d.ts.map +1 -0
- package/dist/psbt.js +743 -0
- package/dist/psbt.js.map +1 -0
- package/dist/script.d.ts +11 -1
- package/dist/script.d.ts.map +1 -1
- package/dist/script.js +24 -0
- package/dist/script.js.map +1 -1
- package/dist/signatories.d.ts +44 -0
- package/dist/signatories.d.ts.map +1 -0
- package/dist/signatories.js +206 -0
- package/dist/signatories.js.map +1 -0
- package/dist/tx.d.ts +60 -0
- package/dist/tx.d.ts.map +1 -1
- package/dist/tx.js +274 -0
- package/dist/tx.js.map +1 -1
- package/dist/txBuilder.d.ts +2 -30
- package/dist/txBuilder.d.ts.map +1 -1
- package/dist/txBuilder.js +1 -45
- package/dist/txBuilder.js.map +1 -1
- package/package.json +1 -1
- package/src/consts.ts +13 -0
- package/src/ffi/ecash_lib_wasm_bg_browser.js +20489 -20489
- package/src/ffi/ecash_lib_wasm_bg_browser.wasm +0 -0
- package/src/ffi/ecash_lib_wasm_bg_nodejs.wasm +0 -0
- package/src/index.ts +2 -0
- package/src/psbt.ts +908 -0
- package/src/script.ts +25 -1
- package/src/signatories.ts +296 -0
- package/src/tx.ts +330 -0
- package/src/txBuilder.ts +2 -74
package/dist/psbt.js
ADDED
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2026 The Bitcoin developers
|
|
3
|
+
// Distributed under the MIT software license, see the accompanying
|
|
4
|
+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Psbt = exports.PSBT_IN_UTXO = exports.PSBT_MAGIC = void 0;
|
|
7
|
+
exports.txToUnsigned = txToUnsigned;
|
|
8
|
+
/**
|
|
9
|
+
* Partially Signed Bitcoin Transaction (PSBT) per **BIP 174**:
|
|
10
|
+
* - Spec: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
|
|
11
|
+
*
|
|
12
|
+
* Decode/encode aligned with Bitcoin ABC (`{@link Psbt.fromBytes}`,
|
|
13
|
+
* `{@link Psbt.toBytes}`), plus multisig workflows: per-input `PSBT_IN_UTXO`
|
|
14
|
+
* (`0x00`), redeem script, partial signatures; unknown pairs preserved (BIP 174).
|
|
15
|
+
*
|
|
16
|
+
* **Input key `0x00` (`PSBT_IN_UTXO`):** BIP 174 also defines type `0x01` (“witness
|
|
17
|
+
* UTXO”) for the same *value* shape. **Bitcoin ABC only implements `0x00`:** value
|
|
18
|
+
* is `CTxOut` or full previous tx (non-witness UTXO). We match the node; `0x01`
|
|
19
|
+
* entries are preserved as unknown keys on round-trip.
|
|
20
|
+
*
|
|
21
|
+
* High-level merge/sign helpers: {@link Tx.addMultisigSignature},
|
|
22
|
+
* {@link Tx.addMultisigSignatureFromKey}; {@link Psbt.fromTx} builds a PSBT from a
|
|
23
|
+
* partially signed {@link Tx}; {@link Psbt.toTx} rebuilds scriptSigs from partial sigs.
|
|
24
|
+
*/
|
|
25
|
+
const bytes_js_1 = require("./io/bytes.js");
|
|
26
|
+
const hex_js_1 = require("./io/hex.js");
|
|
27
|
+
const varsize_js_1 = require("./io/varsize.js");
|
|
28
|
+
const writerbytes_js_1 = require("./io/writerbytes.js");
|
|
29
|
+
const writerlength_js_1 = require("./io/writerlength.js");
|
|
30
|
+
const ecc_js_1 = require("./ecc.js");
|
|
31
|
+
const hash_js_1 = require("./hash.js");
|
|
32
|
+
const script_js_1 = require("./script.js");
|
|
33
|
+
const sigHashType_js_1 = require("./sigHashType.js");
|
|
34
|
+
const tx_js_1 = require("./tx.js");
|
|
35
|
+
const unsignedTx_js_1 = require("./unsignedTx.js");
|
|
36
|
+
/**
|
|
37
|
+
* BIP 174 **magic bytes** for PSBT version 0: ASCII `psbt` + `0xff`.
|
|
38
|
+
* Defined in BIP 174 “Specification > Version 0” (must be first bytes of a `.psbt`).
|
|
39
|
+
* - https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#specification
|
|
40
|
+
* - https://bips.dev/174/#specification
|
|
41
|
+
*/
|
|
42
|
+
exports.PSBT_MAGIC = new Uint8Array([0x70, 0x73, 0x62, 0x74, 0xff]);
|
|
43
|
+
const PSBT_GLOBAL_UNSIGNED_TX = 0x00;
|
|
44
|
+
/**
|
|
45
|
+
* Bitcoin ABC `PSBT_IN_UTXO` (BIP 174 input type `0x00`): value is either the full
|
|
46
|
+
* previous transaction (BIP “non-witness UTXO”) or a serialized `CTxOut` (amount +
|
|
47
|
+
* scriptPubKey). This is the **only** key we use for spent-output data; BIP type
|
|
48
|
+
* `0x01` is not handled like the node (see module TSDoc). Resolved in
|
|
49
|
+
* {@link resolveWitnessFromKey00}.
|
|
50
|
+
*/
|
|
51
|
+
exports.PSBT_IN_UTXO = 0x00;
|
|
52
|
+
const PSBT_IN_PARTIAL_SIG = 0x02;
|
|
53
|
+
const PSBT_IN_SIGHASH = 0x03;
|
|
54
|
+
const PSBT_IN_REDEEM_SCRIPT = 0x04;
|
|
55
|
+
/** BIP 174 / Bitcoin ABC — input HD keypaths */
|
|
56
|
+
const PSBT_IN_BIP32_DERIVATION = 0x06;
|
|
57
|
+
const PSBT_IN_SCRIPTSIG = 0x07;
|
|
58
|
+
/** Bitcoin ABC — output redeem script (same first byte as global unsigned tx key type). */
|
|
59
|
+
const PSBT_OUT_REDEEMSCRIPT = 0x00;
|
|
60
|
+
/** Bitcoin ABC — output HD keypaths */
|
|
61
|
+
const PSBT_OUT_BIP32_DERIVATION = 0x02;
|
|
62
|
+
function compareLex(a, b) {
|
|
63
|
+
const n = Math.min(a.length, b.length);
|
|
64
|
+
for (let i = 0; i < n; i++) {
|
|
65
|
+
if (a[i] !== b[i])
|
|
66
|
+
return a[i] - b[i];
|
|
67
|
+
}
|
|
68
|
+
return a.length - b.length;
|
|
69
|
+
}
|
|
70
|
+
function sortPairs(pairs) {
|
|
71
|
+
pairs.sort((x, y) => compareLex(x.key, y.key));
|
|
72
|
+
}
|
|
73
|
+
function writePsbtKeyValue(writer, key, value) {
|
|
74
|
+
(0, varsize_js_1.writeVarSize)(key.length, writer);
|
|
75
|
+
writer.putBytes(key);
|
|
76
|
+
(0, varsize_js_1.writeVarSize)(value.length, writer);
|
|
77
|
+
writer.putBytes(value);
|
|
78
|
+
}
|
|
79
|
+
function serializeMap(pairs) {
|
|
80
|
+
sortPairs(pairs);
|
|
81
|
+
const wl = new writerlength_js_1.WriterLength();
|
|
82
|
+
for (const p of pairs) {
|
|
83
|
+
writePsbtKeyValue(wl, p.key, p.value);
|
|
84
|
+
}
|
|
85
|
+
(0, varsize_js_1.writeVarSize)(0, wl);
|
|
86
|
+
const out = new writerbytes_js_1.WriterBytes(wl.length);
|
|
87
|
+
for (const p of pairs) {
|
|
88
|
+
writePsbtKeyValue(out, p.key, p.value);
|
|
89
|
+
}
|
|
90
|
+
(0, varsize_js_1.writeVarSize)(0, out);
|
|
91
|
+
return out.data;
|
|
92
|
+
}
|
|
93
|
+
function parseMap(bytes) {
|
|
94
|
+
const pairs = [];
|
|
95
|
+
while (true) {
|
|
96
|
+
const keyLen = (0, varsize_js_1.readVarSize)(bytes);
|
|
97
|
+
if (keyLen === 0)
|
|
98
|
+
break;
|
|
99
|
+
const key = bytes.readBytes(Number(keyLen));
|
|
100
|
+
const valLen = (0, varsize_js_1.readVarSize)(bytes);
|
|
101
|
+
const value = bytes.readBytes(Number(valLen));
|
|
102
|
+
pairs.push({ key, value });
|
|
103
|
+
}
|
|
104
|
+
return pairs;
|
|
105
|
+
}
|
|
106
|
+
/** BIP 174 / Bitcoin ABC: duplicate keys in a map are forbidden (see `PSBTInput::Unserialize`). */
|
|
107
|
+
function assertUniquePsbtKeys(pairs, mapLabel) {
|
|
108
|
+
const seen = new Set();
|
|
109
|
+
for (const { key } of pairs) {
|
|
110
|
+
if (key.length === 0)
|
|
111
|
+
continue;
|
|
112
|
+
const id = (0, hex_js_1.toHex)(key);
|
|
113
|
+
if (seen.has(id)) {
|
|
114
|
+
throw new Error(`PSBT: duplicate key in ${mapLabel} map`);
|
|
115
|
+
}
|
|
116
|
+
seen.add(id);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** Global unsigned transaction must have empty scriptSigs (Bitcoin ABC `PartiallySignedTransaction::Unserialize`). */
|
|
120
|
+
function assertUnsignedTxEmptyScriptSigs(tx) {
|
|
121
|
+
for (let i = 0; i < tx.inputs.length; i++) {
|
|
122
|
+
const sc = tx.inputs[i]?.script;
|
|
123
|
+
const len = sc?.bytecode.length ?? 0;
|
|
124
|
+
if (len > 0) {
|
|
125
|
+
throw new Error('PSBT: unsigned tx must have empty scriptSigs');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Partial signature key: 1 byte type + 33 (compressed) or 65 (uncompressed) byte pubkey. */
|
|
130
|
+
function isValidPartialSigKeyLength(keyLen) {
|
|
131
|
+
return keyLen === 34 || keyLen === 66;
|
|
132
|
+
}
|
|
133
|
+
/** CPubKey-style prefix check (matches `DeserializeHDKeypaths` length rules; not full curve validation). */
|
|
134
|
+
function pubkeyBytesLookPlausible(pk) {
|
|
135
|
+
if (pk.length === 33) {
|
|
136
|
+
return pk[0] === 0x02 || pk[0] === 0x03;
|
|
137
|
+
}
|
|
138
|
+
if (pk.length === 65) {
|
|
139
|
+
return pk[0] === 0x04;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* `DeserializeHDKeypaths` in `src/script/sign.h` (Bitcoin ABC) reads a leading
|
|
145
|
+
* compact size `value_len`, then exactly `value_len` bytes (fingerprint + path).
|
|
146
|
+
*
|
|
147
|
+
* Some `rpc_psbt.json` valid vectors use the same logical payload **without** that
|
|
148
|
+
* length prefix (fingerprint + uint32 path only), matching BIP 174’s description
|
|
149
|
+
* of the value bytes; we accept both shapes so `decodepsbt`-valid PSBTs parse.
|
|
150
|
+
*/
|
|
151
|
+
function validateBip32DerivationKeyValue(key, value) {
|
|
152
|
+
if (key.length !== 34 && key.length !== 66) {
|
|
153
|
+
throw new Error('PSBT: size of key was not the expected size for the type BIP32 keypath');
|
|
154
|
+
}
|
|
155
|
+
const pk = key.slice(1);
|
|
156
|
+
if (!pubkeyBytesLookPlausible(pk)) {
|
|
157
|
+
throw new Error('PSBT: invalid pubkey in BIP32 derivation key');
|
|
158
|
+
}
|
|
159
|
+
const tryPrefixedLength = () => {
|
|
160
|
+
const b = new bytes_js_1.Bytes(value);
|
|
161
|
+
let valueLen;
|
|
162
|
+
try {
|
|
163
|
+
valueLen = Number((0, varsize_js_1.readVarSize)(b));
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
if (valueLen === 0 || valueLen % 4 !== 0) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
if (value.length - b.idx < valueLen) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
b.readBytes(4);
|
|
175
|
+
for (let i = 4; i < valueLen; i += 4) {
|
|
176
|
+
b.readU32();
|
|
177
|
+
}
|
|
178
|
+
return b.idx === value.length;
|
|
179
|
+
};
|
|
180
|
+
if (tryPrefixedLength()) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (value.length < 4 || value.length % 4 !== 0) {
|
|
184
|
+
throw new Error('PSBT: invalid length for HD key path');
|
|
185
|
+
}
|
|
186
|
+
const b = new bytes_js_1.Bytes(value);
|
|
187
|
+
b.readBytes(4);
|
|
188
|
+
for (let i = 4; i < value.length; i += 4) {
|
|
189
|
+
b.readU32();
|
|
190
|
+
}
|
|
191
|
+
if (b.idx !== value.length) {
|
|
192
|
+
throw new Error('PSBT: invalid BIP32 derivation value');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/** Strip input scripts for PSBT global unsigned transaction (BIP 174). */
|
|
196
|
+
function txToUnsigned(tx) {
|
|
197
|
+
return new tx_js_1.Tx({
|
|
198
|
+
version: tx.version,
|
|
199
|
+
inputs: tx.inputs.map(inp => ({
|
|
200
|
+
prevOut: inp.prevOut,
|
|
201
|
+
script: new script_js_1.Script(),
|
|
202
|
+
sequence: inp.sequence,
|
|
203
|
+
})),
|
|
204
|
+
outputs: tx.outputs.map(o => (0, tx_js_1.copyTxOutput)(o)),
|
|
205
|
+
locktime: tx.locktime,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/** `CTxOut` bytes for a {@link PSBT_IN_UTXO} map value (same layout BIP 174 labels “witness UTXO”). */
|
|
209
|
+
function encodeWitnessUtxo(sats, scriptPubKey) {
|
|
210
|
+
const wl = new writerlength_js_1.WriterLength();
|
|
211
|
+
wl.putU64(sats);
|
|
212
|
+
(0, varsize_js_1.writeVarSize)(scriptPubKey.length, wl);
|
|
213
|
+
wl.putBytes(scriptPubKey);
|
|
214
|
+
const w = new writerbytes_js_1.WriterBytes(wl.length);
|
|
215
|
+
w.putU64(sats);
|
|
216
|
+
(0, varsize_js_1.writeVarSize)(scriptPubKey.length, w);
|
|
217
|
+
w.putBytes(scriptPubKey);
|
|
218
|
+
return w.data;
|
|
219
|
+
}
|
|
220
|
+
function decodeWitnessUtxo(data) {
|
|
221
|
+
const b = new bytes_js_1.Bytes(data);
|
|
222
|
+
const sats = b.readU64();
|
|
223
|
+
const sl = (0, varsize_js_1.readVarSize)(b);
|
|
224
|
+
const scriptPubKey = b.readBytes(Number(sl));
|
|
225
|
+
return { sats, scriptPubKey };
|
|
226
|
+
}
|
|
227
|
+
function prevOutTxidHex(po) {
|
|
228
|
+
return typeof po.txid === 'string' ? po.txid : (0, hex_js_1.toHexRev)(po.txid);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Value for input key type `0x00` (`PSBT_IN_UTXO` in Bitcoin ABC): either BIP 174 non-witness
|
|
232
|
+
* UTXO (full prev tx) or a `CTxOut`-shaped blob (amount + scriptPubKey), as in
|
|
233
|
+
* Bitcoin ABC.
|
|
234
|
+
*/
|
|
235
|
+
function resolveWitnessFromKey00(value, prevOut) {
|
|
236
|
+
if (value.length >= 50) {
|
|
237
|
+
const tx = tx_js_1.Tx.tryDeserExact(value);
|
|
238
|
+
if (tx !== undefined && tx.inputs.length > 0) {
|
|
239
|
+
if (tx.txid() === prevOutTxidHex(prevOut)) {
|
|
240
|
+
const out = tx.outputs[prevOut.outIdx];
|
|
241
|
+
if (out !== undefined) {
|
|
242
|
+
return {
|
|
243
|
+
sats: out.sats,
|
|
244
|
+
scriptPubKey: out.script.bytecode,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
return decodeWitnessUtxo(value);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function pubkeyHex(pk) {
|
|
258
|
+
return (0, hex_js_1.toHex)(pk);
|
|
259
|
+
}
|
|
260
|
+
function scriptPubKeyFromSignData(signData) {
|
|
261
|
+
if (signData.redeemScript !== undefined) {
|
|
262
|
+
const h = (0, hash_js_1.shaRmd160)(signData.redeemScript.bytecode);
|
|
263
|
+
return script_js_1.Script.p2sh(h).bytecode;
|
|
264
|
+
}
|
|
265
|
+
if (signData.outputScript !== undefined) {
|
|
266
|
+
return signData.outputScript.bytecode;
|
|
267
|
+
}
|
|
268
|
+
throw new Error('SignData needs redeemScript or outputScript for PSBT');
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* The multisig **redeem** script used to interpret partial sigs: P2SH `redeemScript`,
|
|
272
|
+
* or bare `outputScript` (the locking script is the multisig template itself).
|
|
273
|
+
*/
|
|
274
|
+
function multisigLockingScript(signData) {
|
|
275
|
+
if (signData.redeemScript !== undefined) {
|
|
276
|
+
return signData.redeemScript;
|
|
277
|
+
}
|
|
278
|
+
if (signData.outputScript !== undefined) {
|
|
279
|
+
return signData.outputScript;
|
|
280
|
+
}
|
|
281
|
+
throw new Error('SignData needs redeemScript or outputScript');
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Build the input `scriptSig` for a multisig spend from the PSBT partial-signature
|
|
285
|
+
* map (pubkey hex → signature with sighash byte). Used by {@link Psbt.toTx} only.
|
|
286
|
+
*
|
|
287
|
+
* Matches pubkey order from {@link multisigLockingScript}'s `parseMultisigRedeemScript`,
|
|
288
|
+
* then dispatches:
|
|
289
|
+
* - **Schnorr** if any partial sig has length 65 (Schnorr + sighash): uses
|
|
290
|
+
* {@link Script.multisigSpend} with `pubkeyIndices` derived from which pubkeys
|
|
291
|
+
* have sigs (bare passes `numPubkeys`; P2SH passes `redeemScript`).
|
|
292
|
+
* - **ECDSA** otherwise: fills `m` slots left-to-right from present sigs (same
|
|
293
|
+
* convention as {@link BareMultisigSignatory} / {@link P2SHMultisigSignatory}).
|
|
294
|
+
*/
|
|
295
|
+
function buildScriptSigFromPartialSigs(signData, partialSigs) {
|
|
296
|
+
const lock = multisigLockingScript(signData);
|
|
297
|
+
const { numSignatures, numPubkeys, pubkeys } = lock.parseMultisigRedeemScript();
|
|
298
|
+
const perPk = pubkeys.map(pk => partialSigs.get(pubkeyHex(pk)));
|
|
299
|
+
const signatures = perPk.filter((s) => s !== undefined);
|
|
300
|
+
const anySchnorr = [...partialSigs.values()].some(s => s.length === 65);
|
|
301
|
+
if (anySchnorr) {
|
|
302
|
+
const pubkeyIndices = new Set(perPk.flatMap((s, i) => (s !== undefined ? [i] : [])));
|
|
303
|
+
if (signData.redeemScript !== undefined) {
|
|
304
|
+
return script_js_1.Script.multisigSpend({
|
|
305
|
+
signatures,
|
|
306
|
+
redeemScript: signData.redeemScript,
|
|
307
|
+
pubkeyIndices,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
return script_js_1.Script.multisigSpend({
|
|
311
|
+
signatures,
|
|
312
|
+
pubkeyIndices,
|
|
313
|
+
numPubkeys,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const sigsForScript = signatures.length >= numSignatures
|
|
317
|
+
? signatures.slice(0, numSignatures)
|
|
318
|
+
: [
|
|
319
|
+
...signatures,
|
|
320
|
+
...Array(numSignatures - signatures.length).fill(undefined),
|
|
321
|
+
];
|
|
322
|
+
return script_js_1.Script.multisigSpend({
|
|
323
|
+
signatures: sigsForScript,
|
|
324
|
+
redeemScript: signData.redeemScript,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Extract pubkey → signature (with sighash byte) from a multisig scriptSig.
|
|
329
|
+
*/
|
|
330
|
+
function extractPartialSigsFromInput(script, signData, unsignedTx, inputIdx, ecc) {
|
|
331
|
+
const map = new Map();
|
|
332
|
+
if (!script.bytecode.length)
|
|
333
|
+
return map;
|
|
334
|
+
const inputs = unsignedTx.inputs.map((inp, i) => ({
|
|
335
|
+
...(0, tx_js_1.copyTxInput)(inp),
|
|
336
|
+
signData: i === inputIdx ? signData : undefined,
|
|
337
|
+
}));
|
|
338
|
+
inputs[inputIdx] = {
|
|
339
|
+
...(0, tx_js_1.copyTxInput)(unsignedTx.inputs[inputIdx]),
|
|
340
|
+
script,
|
|
341
|
+
signData,
|
|
342
|
+
};
|
|
343
|
+
const txForPreimage = new tx_js_1.Tx({
|
|
344
|
+
version: unsignedTx.version,
|
|
345
|
+
inputs,
|
|
346
|
+
outputs: unsignedTx.outputs.map(tx_js_1.copyTxOutput),
|
|
347
|
+
locktime: unsignedTx.locktime,
|
|
348
|
+
});
|
|
349
|
+
const msgHash = (0, hash_js_1.sha256d)(unsignedTx_js_1.UnsignedTx.fromTx(txForPreimage)
|
|
350
|
+
.inputAt(inputIdx)
|
|
351
|
+
.sigHashPreimage(sigHashType_js_1.ALL_BIP143).bytes);
|
|
352
|
+
if (signData.redeemScript !== undefined) {
|
|
353
|
+
const parsed = script.parseP2shMultisigSpend();
|
|
354
|
+
if (parsed.isSchnorr) {
|
|
355
|
+
const sorted = [...(parsed.pubkeyIndices ?? [])].sort((a, b) => a - b);
|
|
356
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
357
|
+
const pkIdx = sorted[i];
|
|
358
|
+
const pk = parsed.pubkeys[pkIdx];
|
|
359
|
+
const sig = parsed.signatures[i];
|
|
360
|
+
if (sig !== undefined)
|
|
361
|
+
map.set(pubkeyHex(pk), sig);
|
|
362
|
+
}
|
|
363
|
+
return map;
|
|
364
|
+
}
|
|
365
|
+
for (const sig of parsed.signatures) {
|
|
366
|
+
if (sig === undefined)
|
|
367
|
+
continue;
|
|
368
|
+
const sigNo = sig.slice(0, -1);
|
|
369
|
+
for (const pk of parsed.pubkeys) {
|
|
370
|
+
try {
|
|
371
|
+
ecc.ecdsaVerify(sigNo, msgHash, pk);
|
|
372
|
+
map.set(pubkeyHex(pk), sig);
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
/* next */
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return map;
|
|
381
|
+
}
|
|
382
|
+
if (signData.outputScript !== undefined) {
|
|
383
|
+
const parsed = script.parseBareMultisigSpend(signData.outputScript);
|
|
384
|
+
if (parsed.isSchnorr) {
|
|
385
|
+
const sorted = [...(parsed.pubkeyIndices ?? [])].sort((a, b) => a - b);
|
|
386
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
387
|
+
const pkIdx = sorted[i];
|
|
388
|
+
const pk = parsed.pubkeys[pkIdx];
|
|
389
|
+
const sig = parsed.signatures[i];
|
|
390
|
+
if (sig !== undefined)
|
|
391
|
+
map.set(pubkeyHex(pk), sig);
|
|
392
|
+
}
|
|
393
|
+
return map;
|
|
394
|
+
}
|
|
395
|
+
for (const sig of parsed.signatures) {
|
|
396
|
+
if (sig === undefined)
|
|
397
|
+
continue;
|
|
398
|
+
const sigNo = sig.slice(0, -1);
|
|
399
|
+
for (const pk of parsed.pubkeys) {
|
|
400
|
+
try {
|
|
401
|
+
ecc.ecdsaVerify(sigNo, msgHash, pk);
|
|
402
|
+
map.set(pubkeyHex(pk), sig);
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
/* next */
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return map;
|
|
412
|
+
}
|
|
413
|
+
function parseInputMapPairs(inPairs, prevOut) {
|
|
414
|
+
const partialSigs = new Map();
|
|
415
|
+
const unknown = [];
|
|
416
|
+
let redeemScript;
|
|
417
|
+
let pair00;
|
|
418
|
+
for (const p of inPairs) {
|
|
419
|
+
if (p.key.length === 0)
|
|
420
|
+
continue;
|
|
421
|
+
const t = p.key[0];
|
|
422
|
+
// Bitcoin ABC PSBTInput::Unserialize: these types require a 1-byte key only.
|
|
423
|
+
if (t === PSBT_IN_SIGHASH) {
|
|
424
|
+
if (p.key.length !== 1) {
|
|
425
|
+
throw new Error('PSBT: sighash type key must be exactly one byte');
|
|
426
|
+
}
|
|
427
|
+
unknown.push(p);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (t === PSBT_IN_SCRIPTSIG) {
|
|
431
|
+
if (p.key.length !== 1) {
|
|
432
|
+
throw new Error('PSBT: final scriptSig key must be exactly one byte');
|
|
433
|
+
}
|
|
434
|
+
unknown.push(p);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
if (t === PSBT_IN_REDEEM_SCRIPT) {
|
|
438
|
+
if (p.key.length !== 1) {
|
|
439
|
+
throw new Error('PSBT: redeemScript key must be exactly one byte');
|
|
440
|
+
}
|
|
441
|
+
redeemScript = new script_js_1.Script(p.value);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
// Bitcoin ABC PSBT_IN_UTXO: key is type byte only (see psbt.h).
|
|
445
|
+
if (t === exports.PSBT_IN_UTXO && p.key.length !== 1) {
|
|
446
|
+
throw new Error('PSBT: input UTXO key must be exactly one byte');
|
|
447
|
+
}
|
|
448
|
+
if (t === exports.PSBT_IN_UTXO && p.key.length === 1) {
|
|
449
|
+
pair00 = p;
|
|
450
|
+
}
|
|
451
|
+
else if (t === PSBT_IN_PARTIAL_SIG) {
|
|
452
|
+
if (!isValidPartialSigKeyLength(p.key.length)) {
|
|
453
|
+
throw new Error('PSBT: invalid partial signature pubkey key size');
|
|
454
|
+
}
|
|
455
|
+
partialSigs.set(pubkeyHex(p.key.slice(1)), p.value);
|
|
456
|
+
}
|
|
457
|
+
else if (t === PSBT_IN_BIP32_DERIVATION) {
|
|
458
|
+
validateBip32DerivationKeyValue(p.key, p.value);
|
|
459
|
+
unknown.push(p);
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
unknown.push(p);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
let witness;
|
|
466
|
+
if (pair00 !== undefined) {
|
|
467
|
+
const w = resolveWitnessFromKey00(pair00.value, prevOut);
|
|
468
|
+
if (w === undefined) {
|
|
469
|
+
throw new Error('PSBT input: invalid PSBT_IN_UTXO (0x00) value');
|
|
470
|
+
}
|
|
471
|
+
witness = w;
|
|
472
|
+
}
|
|
473
|
+
return { witness, redeemScript, partialSigs, unknown };
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* BIP 174 PSBT for eCash multisig and ABC-aligned decode/encode.
|
|
477
|
+
* See https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#serialization
|
|
478
|
+
*
|
|
479
|
+
* Typical flow: {@link Psbt.fromTx} from a partially signed {@link Tx} plus
|
|
480
|
+
* {@link SignData} per input → {@link Psbt.toBytes} → share → {@link Psbt.fromBytes}.
|
|
481
|
+
* Unknown key-value pairs are preserved (BIP 174).
|
|
482
|
+
*/
|
|
483
|
+
class Psbt {
|
|
484
|
+
constructor(params) {
|
|
485
|
+
if (params.signDataPerInput.length !== params.unsignedTx.inputs.length) {
|
|
486
|
+
throw new Error('signDataPerInput length must match inputs');
|
|
487
|
+
}
|
|
488
|
+
if (params.inputPartialSigs.length !== params.unsignedTx.inputs.length) {
|
|
489
|
+
throw new Error('inputPartialSigs length must match inputs');
|
|
490
|
+
}
|
|
491
|
+
this.unsignedTx = params.unsignedTx;
|
|
492
|
+
this.signDataPerInput = params.signDataPerInput;
|
|
493
|
+
this.inputPartialSigs = params.inputPartialSigs;
|
|
494
|
+
this.unknownGlobalPairs = params.unknownGlobalPairs ?? [];
|
|
495
|
+
this.unknownInputPairs =
|
|
496
|
+
params.unknownInputPairs ?? params.unsignedTx.inputs.map(() => []);
|
|
497
|
+
this.unknownOutputPairs =
|
|
498
|
+
params.unknownOutputPairs ??
|
|
499
|
+
params.unsignedTx.outputs.map(() => []);
|
|
500
|
+
if (this.unknownInputPairs.length !== this.unsignedTx.inputs.length) {
|
|
501
|
+
throw new Error('unknownInputPairs length must match inputs');
|
|
502
|
+
}
|
|
503
|
+
if (this.unknownOutputPairs.length !== this.unsignedTx.outputs.length) {
|
|
504
|
+
throw new Error('unknownOutputPairs length must match outputs');
|
|
505
|
+
}
|
|
506
|
+
this.inputWitnessIncomplete =
|
|
507
|
+
params.inputWitnessIncomplete ??
|
|
508
|
+
params.unsignedTx.inputs.map(() => false);
|
|
509
|
+
if (this.inputWitnessIncomplete.length !== this.unsignedTx.inputs.length) {
|
|
510
|
+
throw new Error('inputWitnessIncomplete length must match inputs');
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Build a PSBT from a transaction that may already include partial scriptSigs.
|
|
515
|
+
* Populates `PSBT_IN_UTXO` (`0x00`) + redeem script + partial signatures.
|
|
516
|
+
*/
|
|
517
|
+
static fromTx(tx, signDataPerInput, ecc) {
|
|
518
|
+
const unsignedTx = txToUnsigned(tx);
|
|
519
|
+
const inputPartialSigs = [];
|
|
520
|
+
for (let i = 0; i < tx.inputs.length; i++) {
|
|
521
|
+
const script = tx.inputs[i]?.script ?? new script_js_1.Script();
|
|
522
|
+
const sigs = extractPartialSigsFromInput(script, signDataPerInput[i], unsignedTx, i, ecc);
|
|
523
|
+
inputPartialSigs.push(sigs);
|
|
524
|
+
}
|
|
525
|
+
return new Psbt({
|
|
526
|
+
unsignedTx,
|
|
527
|
+
signDataPerInput,
|
|
528
|
+
inputPartialSigs,
|
|
529
|
+
unknownGlobalPairs: [],
|
|
530
|
+
unknownInputPairs: unsignedTx.inputs.map(() => []),
|
|
531
|
+
unknownOutputPairs: unsignedTx.outputs.map(() => []),
|
|
532
|
+
inputWitnessIncomplete: unsignedTx.inputs.map(() => false),
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
/** Deserialize PSBT bytes (BIP 174). */
|
|
536
|
+
static fromBytes(data) {
|
|
537
|
+
const bytes = new bytes_js_1.Bytes(data);
|
|
538
|
+
for (let i = 0; i < exports.PSBT_MAGIC.length; i++) {
|
|
539
|
+
if (bytes.readU8() !== exports.PSBT_MAGIC[i]) {
|
|
540
|
+
throw new Error('Invalid PSBT: bad magic');
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
const globalPairs = parseMap(bytes);
|
|
544
|
+
assertUniquePsbtKeys(globalPairs, 'global');
|
|
545
|
+
let unsignedRaw;
|
|
546
|
+
const unknownGlobalPairs = [];
|
|
547
|
+
for (const { key, value } of globalPairs) {
|
|
548
|
+
if (key.length === 0)
|
|
549
|
+
continue;
|
|
550
|
+
if (key[0] === PSBT_GLOBAL_UNSIGNED_TX && key.length === 1) {
|
|
551
|
+
unsignedRaw = value;
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
unknownGlobalPairs.push({ key, value });
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (unsignedRaw === undefined) {
|
|
558
|
+
throw new Error('PSBT missing global unsigned transaction');
|
|
559
|
+
}
|
|
560
|
+
const unsignedTx = tx_js_1.Tx.deser(unsignedRaw);
|
|
561
|
+
assertUnsignedTxEmptyScriptSigs(unsignedTx);
|
|
562
|
+
const inputPartialSigs = [];
|
|
563
|
+
const signDataPerInput = [];
|
|
564
|
+
const unknownInputPairs = [];
|
|
565
|
+
const inputWitnessIncomplete = [];
|
|
566
|
+
for (let i = 0; i < unsignedTx.inputs.length; i++) {
|
|
567
|
+
const inPairs = parseMap(bytes);
|
|
568
|
+
assertUniquePsbtKeys(inPairs, `input ${i}`);
|
|
569
|
+
const prevOut = unsignedTx.inputs[i].prevOut;
|
|
570
|
+
const { witness, redeemScript, partialSigs, unknown } = parseInputMapPairs(inPairs, prevOut);
|
|
571
|
+
unknownInputPairs.push(unknown);
|
|
572
|
+
const incomplete = witness === undefined;
|
|
573
|
+
inputWitnessIncomplete.push(incomplete);
|
|
574
|
+
let signData;
|
|
575
|
+
if (incomplete) {
|
|
576
|
+
signData =
|
|
577
|
+
redeemScript !== undefined
|
|
578
|
+
? { sats: 0n, redeemScript }
|
|
579
|
+
: { sats: 0n, outputScript: new script_js_1.Script() };
|
|
580
|
+
}
|
|
581
|
+
else if (redeemScript !== undefined) {
|
|
582
|
+
signData = {
|
|
583
|
+
sats: witness.sats,
|
|
584
|
+
redeemScript,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
signData = {
|
|
589
|
+
sats: witness.sats,
|
|
590
|
+
outputScript: new script_js_1.Script(witness.scriptPubKey),
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
signDataPerInput.push(signData);
|
|
594
|
+
inputPartialSigs.push(partialSigs);
|
|
595
|
+
}
|
|
596
|
+
const unknownOutputPairs = [];
|
|
597
|
+
for (let o = 0; o < unsignedTx.outputs.length; o++) {
|
|
598
|
+
const outPairs = parseMap(bytes);
|
|
599
|
+
assertUniquePsbtKeys(outPairs, `output ${o}`);
|
|
600
|
+
for (const p of outPairs) {
|
|
601
|
+
if (p.key.length === 0)
|
|
602
|
+
continue;
|
|
603
|
+
const ot = p.key[0];
|
|
604
|
+
// PSBTOutput::Unserialize (Bitcoin ABC): redeem script key is type byte only.
|
|
605
|
+
if (ot === PSBT_OUT_REDEEMSCRIPT && p.key.length !== 1) {
|
|
606
|
+
throw new Error('PSBT: output redeemScript key must be exactly one byte');
|
|
607
|
+
}
|
|
608
|
+
if (ot === PSBT_OUT_BIP32_DERIVATION) {
|
|
609
|
+
validateBip32DerivationKeyValue(p.key, p.value);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
unknownOutputPairs.push(outPairs.filter(p => p.key.length > 0));
|
|
613
|
+
}
|
|
614
|
+
if (bytes.idx !== data.length) {
|
|
615
|
+
throw new Error('PSBT: trailing bytes after output maps');
|
|
616
|
+
}
|
|
617
|
+
return new Psbt({
|
|
618
|
+
unsignedTx,
|
|
619
|
+
signDataPerInput,
|
|
620
|
+
inputPartialSigs,
|
|
621
|
+
unknownGlobalPairs,
|
|
622
|
+
unknownInputPairs,
|
|
623
|
+
unknownOutputPairs,
|
|
624
|
+
inputWitnessIncomplete,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
/** Serialize to BIP 174 bytes. */
|
|
628
|
+
toBytes() {
|
|
629
|
+
const unsignedSer = this.unsignedTx.ser();
|
|
630
|
+
const globalPairs = [
|
|
631
|
+
{
|
|
632
|
+
key: new Uint8Array([PSBT_GLOBAL_UNSIGNED_TX]),
|
|
633
|
+
value: unsignedSer,
|
|
634
|
+
},
|
|
635
|
+
...this.unknownGlobalPairs,
|
|
636
|
+
];
|
|
637
|
+
const parts = [exports.PSBT_MAGIC, serializeMap(globalPairs)];
|
|
638
|
+
for (let i = 0; i < this.unsignedTx.inputs.length; i++) {
|
|
639
|
+
const sd = this.signDataPerInput[i];
|
|
640
|
+
const inPairs = [];
|
|
641
|
+
if (!this.inputWitnessIncomplete[i]) {
|
|
642
|
+
const spk = scriptPubKeyFromSignData(sd);
|
|
643
|
+
inPairs.push({
|
|
644
|
+
key: new Uint8Array([exports.PSBT_IN_UTXO]),
|
|
645
|
+
value: encodeWitnessUtxo(sd.sats, spk),
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
if (sd.redeemScript !== undefined) {
|
|
649
|
+
inPairs.push({
|
|
650
|
+
key: new Uint8Array([PSBT_IN_REDEEM_SCRIPT]),
|
|
651
|
+
value: sd.redeemScript.bytecode,
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
const pSig = this.inputPartialSigs[i];
|
|
655
|
+
for (const [pkHex, sig] of pSig) {
|
|
656
|
+
const pk = (0, hex_js_1.fromHex)(pkHex);
|
|
657
|
+
const key = new Uint8Array(1 + pk.length);
|
|
658
|
+
key[0] = PSBT_IN_PARTIAL_SIG;
|
|
659
|
+
key.set(pk, 1);
|
|
660
|
+
inPairs.push({ key, value: sig });
|
|
661
|
+
}
|
|
662
|
+
inPairs.push(...(this.unknownInputPairs[i] ?? []));
|
|
663
|
+
parts.push(serializeMap(inPairs));
|
|
664
|
+
}
|
|
665
|
+
for (let o = 0; o < this.unsignedTx.outputs.length; o++) {
|
|
666
|
+
parts.push(serializeMap(this.unknownOutputPairs[o] ?? []));
|
|
667
|
+
}
|
|
668
|
+
const wl = new writerlength_js_1.WriterLength();
|
|
669
|
+
for (const p of parts) {
|
|
670
|
+
wl.putBytes(p);
|
|
671
|
+
}
|
|
672
|
+
const w = new writerbytes_js_1.WriterBytes(wl.length);
|
|
673
|
+
for (const p of parts) {
|
|
674
|
+
w.putBytes(p);
|
|
675
|
+
}
|
|
676
|
+
return w.data;
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Current transaction with scriptSigs built from partial signatures.
|
|
680
|
+
* Attach each input's `signData` for signing and validation helpers.
|
|
681
|
+
*/
|
|
682
|
+
toTx() {
|
|
683
|
+
return new tx_js_1.Tx({
|
|
684
|
+
version: this.unsignedTx.version,
|
|
685
|
+
inputs: this.unsignedTx.inputs.map((inp, i) => {
|
|
686
|
+
const sd = this.signDataPerInput[i];
|
|
687
|
+
const script = buildScriptSigFromPartialSigs(sd, this.inputPartialSigs[i]);
|
|
688
|
+
return {
|
|
689
|
+
...(0, tx_js_1.copyTxInput)(inp),
|
|
690
|
+
script,
|
|
691
|
+
signData: sd,
|
|
692
|
+
};
|
|
693
|
+
}),
|
|
694
|
+
outputs: this.unsignedTx.outputs.map(tx_js_1.copyTxOutput),
|
|
695
|
+
locktime: this.unsignedTx.locktime,
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Add or merge a multisig signature on an input (same semantics as
|
|
700
|
+
* {@link Tx.addMultisigSignature}).
|
|
701
|
+
*/
|
|
702
|
+
addMultisigSignature(params) {
|
|
703
|
+
const tx = this.toTx();
|
|
704
|
+
const nextTx = tx.addMultisigSignature(params);
|
|
705
|
+
const next = Psbt.fromTx(nextTx, this.signDataPerInput, params.ecc ?? new ecc_js_1.Ecc());
|
|
706
|
+
return new Psbt({
|
|
707
|
+
unsignedTx: next.unsignedTx,
|
|
708
|
+
signDataPerInput: next.signDataPerInput,
|
|
709
|
+
inputPartialSigs: next.inputPartialSigs,
|
|
710
|
+
unknownGlobalPairs: this.unknownGlobalPairs,
|
|
711
|
+
unknownInputPairs: this.unknownInputPairs,
|
|
712
|
+
unknownOutputPairs: this.unknownOutputPairs,
|
|
713
|
+
inputWitnessIncomplete: this.inputWitnessIncomplete,
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Like {@link addMultisigSignature}, but signs with a secret key (see
|
|
718
|
+
* {@link Tx.addMultisigSignatureFromKey}).
|
|
719
|
+
*/
|
|
720
|
+
addMultisigSignatureFromKey(params) {
|
|
721
|
+
const tx = this.toTx();
|
|
722
|
+
const nextTx = tx.addMultisigSignatureFromKey(params);
|
|
723
|
+
const next = Psbt.fromTx(nextTx, this.signDataPerInput, params.ecc ?? new ecc_js_1.Ecc());
|
|
724
|
+
return new Psbt({
|
|
725
|
+
unsignedTx: next.unsignedTx,
|
|
726
|
+
signDataPerInput: next.signDataPerInput,
|
|
727
|
+
inputPartialSigs: next.inputPartialSigs,
|
|
728
|
+
unknownGlobalPairs: this.unknownGlobalPairs,
|
|
729
|
+
unknownInputPairs: this.unknownInputPairs,
|
|
730
|
+
unknownOutputPairs: this.unknownOutputPairs,
|
|
731
|
+
inputWitnessIncomplete: this.inputWitnessIncomplete,
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Same as {@link Tx.isFullySignedMultisig} on {@link toTx} (including
|
|
736
|
+
* vacuous `true` when there are no multisig inputs).
|
|
737
|
+
*/
|
|
738
|
+
isFullySignedMultisig() {
|
|
739
|
+
return this.toTx().isFullySignedMultisig();
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
exports.Psbt = Psbt;
|
|
743
|
+
//# sourceMappingURL=psbt.js.map
|