ecash-lib 4.9.0 → 4.11.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 (43) hide show
  1. package/README.md +2 -0
  2. package/dist/consts.d.ts +2 -0
  3. package/dist/consts.d.ts.map +1 -1
  4. package/dist/consts.js +3 -1
  5. package/dist/consts.js.map +1 -1
  6. package/dist/ffi/ecash_lib_wasm_bg_browser.js +20495 -20495
  7. package/dist/ffi/ecash_lib_wasm_bg_browser.wasm +0 -0
  8. package/dist/ffi/ecash_lib_wasm_bg_nodejs.wasm +0 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +2 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/psbt.d.ts +65 -0
  14. package/dist/psbt.d.ts.map +1 -0
  15. package/dist/psbt.js +494 -0
  16. package/dist/psbt.js.map +1 -0
  17. package/dist/script.d.ts +60 -0
  18. package/dist/script.d.ts.map +1 -1
  19. package/dist/script.js +217 -0
  20. package/dist/script.js.map +1 -1
  21. package/dist/signatories.d.ts +36 -0
  22. package/dist/signatories.d.ts.map +1 -0
  23. package/dist/signatories.js +51 -0
  24. package/dist/signatories.js.map +1 -0
  25. package/dist/tx.d.ts +20 -0
  26. package/dist/tx.d.ts.map +1 -1
  27. package/dist/tx.js +51 -0
  28. package/dist/tx.js.map +1 -1
  29. package/dist/txBuilder.d.ts +2 -30
  30. package/dist/txBuilder.d.ts.map +1 -1
  31. package/dist/txBuilder.js +1 -45
  32. package/dist/txBuilder.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/consts.ts +3 -0
  35. package/src/ffi/ecash_lib_wasm_bg_browser.js +20495 -20495
  36. package/src/ffi/ecash_lib_wasm_bg_browser.wasm +0 -0
  37. package/src/ffi/ecash_lib_wasm_bg_nodejs.wasm +0 -0
  38. package/src/index.ts +2 -0
  39. package/src/psbt.ts +590 -0
  40. package/src/script.ts +312 -1
  41. package/src/signatories.ts +85 -0
  42. package/src/tx.ts +51 -0
  43. package/src/txBuilder.ts +2 -74
package/src/index.ts CHANGED
@@ -14,6 +14,8 @@ export * from './hdwallet.js';
14
14
  export * from './address/address.js';
15
15
  export * from './sigHashType.js';
16
16
  export * from './tx.js';
17
+ export * from './psbt.js';
18
+ export * from './signatories.js';
17
19
  export * from './txBuilder.js';
18
20
  export * from './unsignedTx.js';
19
21
  export * from './io/bytes.js';
package/src/psbt.ts ADDED
@@ -0,0 +1,590 @@
1
+ // Copyright (c) 2026 The Bitcoin developers
2
+ // Distributed under the MIT software license, see the accompanying
3
+ // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
+
5
+ /**
6
+ * Partially Signed Bitcoin Transaction (PSBT) per **BIP 174**:
7
+ * - Spec: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
8
+ *
9
+ * This module implements **decode/encode** (`{@link Psbt.fromBytes}`,
10
+ * `{@link Psbt.toBytes}`) aligned with Bitcoin ABC: global unsigned tx, per-input
11
+ * `PSBT_IN_UTXO` (`0x00`) / redeem script / partial sigs, output maps, and
12
+ * preservation of unknown pairs (BIP 174).
13
+ *
14
+ * **Input key `0x00` (`PSBT_IN_UTXO`):** BIP 174 also defines type `0x01` (“witness
15
+ * UTXO”) for the same *value* shape. **Bitcoin ABC only implements `0x00`:** value
16
+ * is `CTxOut` or full previous tx (non-witness UTXO). We match the node; `0x01`
17
+ * entries are preserved as unknown keys on round-trip.
18
+ */
19
+
20
+ import { Bytes } from './io/bytes.js';
21
+ import { fromHex, toHex, toHexRev } from './io/hex.js';
22
+ import { readVarSize, writeVarSize } from './io/varsize.js';
23
+ import { WriterBytes } from './io/writerbytes.js';
24
+ import { WriterLength } from './io/writerlength.js';
25
+ import { Writer } from './io/writer.js';
26
+ import { shaRmd160 } from './hash.js';
27
+ import { Script } from './script.js';
28
+ import { OutPoint, SignData, Tx } from './tx.js';
29
+
30
+ /**
31
+ * BIP 174 **magic bytes** for PSBT version 0: ASCII `psbt` + `0xff`.
32
+ * Defined in BIP 174 “Specification > Version 0” (must be first bytes of a `.psbt`).
33
+ * - https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#specification
34
+ * - https://bips.dev/174/#specification
35
+ */
36
+ export const PSBT_MAGIC = new Uint8Array([0x70, 0x73, 0x62, 0x74, 0xff]);
37
+
38
+ const PSBT_GLOBAL_UNSIGNED_TX = 0x00;
39
+ /**
40
+ * Bitcoin ABC `PSBT_IN_UTXO` (BIP 174 input type `0x00`): value is either the full
41
+ * previous transaction (BIP “non-witness UTXO”) or a serialized `CTxOut` (amount +
42
+ * scriptPubKey). This is the **only** key we use for spent-output data; BIP type
43
+ * `0x01` is not handled like the node (see module TSDoc). Resolved in
44
+ * {@link resolveWitnessFromKey00}.
45
+ */
46
+ export const PSBT_IN_UTXO = 0x00;
47
+ const PSBT_IN_PARTIAL_SIG = 0x02;
48
+ const PSBT_IN_SIGHASH = 0x03;
49
+ const PSBT_IN_REDEEM_SCRIPT = 0x04;
50
+ /** BIP 174 / Bitcoin ABC — input HD keypaths */
51
+ const PSBT_IN_BIP32_DERIVATION = 0x06;
52
+ const PSBT_IN_SCRIPTSIG = 0x07;
53
+ /** Bitcoin ABC — output redeem script (same first byte as global unsigned tx key type). */
54
+ const PSBT_OUT_REDEEMSCRIPT = 0x00;
55
+ /** Bitcoin ABC — output HD keypaths */
56
+ const PSBT_OUT_BIP32_DERIVATION = 0x02;
57
+
58
+ function compareLex(a: Uint8Array, b: Uint8Array): number {
59
+ const n = Math.min(a.length, b.length);
60
+ for (let i = 0; i < n; i++) {
61
+ if (a[i] !== b[i]) return a[i]! - b[i]!;
62
+ }
63
+ return a.length - b.length;
64
+ }
65
+
66
+ function sortPairs(pairs: { key: Uint8Array; value: Uint8Array }[]): void {
67
+ pairs.sort((x, y) => compareLex(x.key, y.key));
68
+ }
69
+
70
+ function writePsbtKeyValue(writer: Writer, key: Uint8Array, value: Uint8Array) {
71
+ writeVarSize(key.length, writer);
72
+ writer.putBytes(key);
73
+ writeVarSize(value.length, writer);
74
+ writer.putBytes(value);
75
+ }
76
+
77
+ function serializeMap(
78
+ pairs: { key: Uint8Array; value: Uint8Array }[],
79
+ ): Uint8Array {
80
+ sortPairs(pairs);
81
+ const wl = new WriterLength();
82
+ for (const p of pairs) {
83
+ writePsbtKeyValue(wl, p.key, p.value);
84
+ }
85
+ writeVarSize(0, wl);
86
+ const out = new WriterBytes(wl.length);
87
+ for (const p of pairs) {
88
+ writePsbtKeyValue(out, p.key, p.value);
89
+ }
90
+ writeVarSize(0, out);
91
+ return out.data;
92
+ }
93
+
94
+ function parseMap(bytes: Bytes): { key: Uint8Array; value: Uint8Array }[] {
95
+ const pairs: { key: Uint8Array; value: Uint8Array }[] = [];
96
+ while (true) {
97
+ const keyLen = readVarSize(bytes);
98
+ if (keyLen === 0) break;
99
+ const key = bytes.readBytes(Number(keyLen));
100
+ const valLen = readVarSize(bytes);
101
+ const value = bytes.readBytes(Number(valLen));
102
+ pairs.push({ key, value });
103
+ }
104
+ return pairs;
105
+ }
106
+
107
+ /** BIP 174 / Bitcoin ABC: duplicate keys in a map are forbidden (see `PSBTInput::Unserialize`). */
108
+ function assertUniquePsbtKeys(
109
+ pairs: { key: Uint8Array; value: Uint8Array }[],
110
+ mapLabel: string,
111
+ ): void {
112
+ const seen = new Set<string>();
113
+ for (const { key } of pairs) {
114
+ if (key.length === 0) continue;
115
+ const id = toHex(key);
116
+ if (seen.has(id)) {
117
+ throw new Error(`PSBT: duplicate key in ${mapLabel} map`);
118
+ }
119
+ seen.add(id);
120
+ }
121
+ }
122
+
123
+ /** Global unsigned transaction must have empty scriptSigs (Bitcoin ABC `PartiallySignedTransaction::Unserialize`). */
124
+ function assertUnsignedTxEmptyScriptSigs(tx: Tx): void {
125
+ for (let i = 0; i < tx.inputs.length; i++) {
126
+ const sc = tx.inputs[i]?.script;
127
+ const len = sc?.bytecode.length ?? 0;
128
+ if (len > 0) {
129
+ throw new Error('PSBT: unsigned tx must have empty scriptSigs');
130
+ }
131
+ }
132
+ }
133
+
134
+ /** Partial signature key: 1 byte type + 33 (compressed) or 65 (uncompressed) byte pubkey. */
135
+ function isValidPartialSigKeyLength(keyLen: number): boolean {
136
+ return keyLen === 34 || keyLen === 66;
137
+ }
138
+
139
+ /** CPubKey-style prefix check (matches `DeserializeHDKeypaths` length rules; not full curve validation). */
140
+ function pubkeyBytesLookPlausible(pk: Uint8Array): boolean {
141
+ if (pk.length === 33) {
142
+ return pk[0] === 0x02 || pk[0] === 0x03;
143
+ }
144
+ if (pk.length === 65) {
145
+ return pk[0] === 0x04;
146
+ }
147
+ return false;
148
+ }
149
+
150
+ /**
151
+ * `DeserializeHDKeypaths` in `src/script/sign.h` (Bitcoin ABC) reads a leading
152
+ * compact size `value_len`, then exactly `value_len` bytes (fingerprint + path).
153
+ *
154
+ * Some `rpc_psbt.json` valid vectors use the same logical payload **without** that
155
+ * length prefix (fingerprint + uint32 path only), matching BIP 174’s description
156
+ * of the value bytes; we accept both shapes so `decodepsbt`-valid PSBTs parse.
157
+ */
158
+ function validateBip32DerivationKeyValue(
159
+ key: Uint8Array,
160
+ value: Uint8Array,
161
+ ): void {
162
+ if (key.length !== 34 && key.length !== 66) {
163
+ throw new Error(
164
+ 'PSBT: size of key was not the expected size for the type BIP32 keypath',
165
+ );
166
+ }
167
+ const pk = key.slice(1);
168
+ if (!pubkeyBytesLookPlausible(pk)) {
169
+ throw new Error('PSBT: invalid pubkey in BIP32 derivation key');
170
+ }
171
+ const tryPrefixedLength = (): boolean => {
172
+ const b = new Bytes(value);
173
+ let valueLen: number;
174
+ try {
175
+ valueLen = Number(readVarSize(b));
176
+ } catch {
177
+ return false;
178
+ }
179
+ if (valueLen === 0 || valueLen % 4 !== 0) {
180
+ return false;
181
+ }
182
+ if (value.length - b.idx < valueLen) {
183
+ return false;
184
+ }
185
+ b.readBytes(4);
186
+ for (let i = 4; i < valueLen; i += 4) {
187
+ b.readU32();
188
+ }
189
+ return b.idx === value.length;
190
+ };
191
+
192
+ if (tryPrefixedLength()) {
193
+ return;
194
+ }
195
+
196
+ if (value.length < 4 || value.length % 4 !== 0) {
197
+ throw new Error('PSBT: invalid length for HD key path');
198
+ }
199
+ const b = new Bytes(value);
200
+ b.readBytes(4);
201
+ for (let i = 4; i < value.length; i += 4) {
202
+ b.readU32();
203
+ }
204
+ if (b.idx !== value.length) {
205
+ throw new Error('PSBT: invalid BIP32 derivation value');
206
+ }
207
+ }
208
+
209
+ /** `CTxOut` bytes for a {@link PSBT_IN_UTXO} map value (same layout BIP 174 labels “witness UTXO”). */
210
+ function encodeWitnessUtxo(sats: bigint, scriptPubKey: Uint8Array): Uint8Array {
211
+ const wl = new WriterLength();
212
+ wl.putU64(sats);
213
+ writeVarSize(scriptPubKey.length, wl);
214
+ wl.putBytes(scriptPubKey);
215
+ const w = new WriterBytes(wl.length);
216
+ w.putU64(sats);
217
+ writeVarSize(scriptPubKey.length, w);
218
+ w.putBytes(scriptPubKey);
219
+ return w.data;
220
+ }
221
+
222
+ function decodeWitnessUtxo(data: Uint8Array): {
223
+ sats: bigint;
224
+ scriptPubKey: Uint8Array;
225
+ } {
226
+ const b = new Bytes(data);
227
+ const sats = b.readU64();
228
+ const sl = readVarSize(b);
229
+ const scriptPubKey = b.readBytes(Number(sl));
230
+ return { sats, scriptPubKey };
231
+ }
232
+
233
+ function prevOutTxidHex(po: OutPoint): string {
234
+ return typeof po.txid === 'string' ? po.txid : toHexRev(po.txid);
235
+ }
236
+
237
+ /**
238
+ * Value for input key type `0x00` (`PSBT_IN_UTXO` in Bitcoin ABC): either BIP 174 non-witness
239
+ * UTXO (full prev tx) or a `CTxOut`-shaped blob (amount + scriptPubKey), as in
240
+ * Bitcoin ABC.
241
+ */
242
+ function resolveWitnessFromKey00(
243
+ value: Uint8Array,
244
+ prevOut: OutPoint,
245
+ ): { sats: bigint; scriptPubKey: Uint8Array } | undefined {
246
+ if (value.length >= 50) {
247
+ const tx = Tx.tryDeserExact(value);
248
+ if (tx !== undefined && tx.inputs.length > 0) {
249
+ if (tx.txid() === prevOutTxidHex(prevOut)) {
250
+ const out = tx.outputs[prevOut.outIdx];
251
+ if (out !== undefined) {
252
+ return {
253
+ sats: out.sats,
254
+ scriptPubKey: out.script.bytecode,
255
+ };
256
+ }
257
+ }
258
+ }
259
+ }
260
+ try {
261
+ return decodeWitnessUtxo(value);
262
+ } catch {
263
+ return undefined;
264
+ }
265
+ }
266
+
267
+ function pubkeyHex(pk: Uint8Array): string {
268
+ return toHex(pk);
269
+ }
270
+
271
+ function scriptPubKeyFromSignData(signData: SignData): Uint8Array {
272
+ if (signData.redeemScript !== undefined) {
273
+ const h = shaRmd160(signData.redeemScript.bytecode);
274
+ return Script.p2sh(h).bytecode;
275
+ }
276
+ if (signData.outputScript !== undefined) {
277
+ return signData.outputScript.bytecode;
278
+ }
279
+ throw new Error('SignData needs redeemScript or outputScript for PSBT');
280
+ }
281
+
282
+ /** One PSBT key-value pair (BIP 174). */
283
+ export type PsbtKeyValue = { key: Uint8Array; value: Uint8Array };
284
+
285
+ function parseInputMapPairs(
286
+ inPairs: PsbtKeyValue[],
287
+ prevOut: OutPoint,
288
+ ): {
289
+ witness: { sats: bigint; scriptPubKey: Uint8Array } | undefined;
290
+ redeemScript?: Script;
291
+ partialSigs: Map<string, Uint8Array>;
292
+ unknown: PsbtKeyValue[];
293
+ } {
294
+ const partialSigs = new Map<string, Uint8Array>();
295
+ const unknown: PsbtKeyValue[] = [];
296
+ let redeemScript: Script | undefined;
297
+ let pair00: PsbtKeyValue | undefined;
298
+
299
+ for (const p of inPairs) {
300
+ if (p.key.length === 0) continue;
301
+ const t = p.key[0]!;
302
+ // Bitcoin ABC PSBTInput::Unserialize: these types require a 1-byte key only.
303
+ if (t === PSBT_IN_SIGHASH) {
304
+ if (p.key.length !== 1) {
305
+ throw new Error(
306
+ 'PSBT: sighash type key must be exactly one byte',
307
+ );
308
+ }
309
+ unknown.push(p);
310
+ continue;
311
+ }
312
+ if (t === PSBT_IN_SCRIPTSIG) {
313
+ if (p.key.length !== 1) {
314
+ throw new Error(
315
+ 'PSBT: final scriptSig key must be exactly one byte',
316
+ );
317
+ }
318
+ unknown.push(p);
319
+ continue;
320
+ }
321
+ if (t === PSBT_IN_REDEEM_SCRIPT) {
322
+ if (p.key.length !== 1) {
323
+ throw new Error(
324
+ 'PSBT: redeemScript key must be exactly one byte',
325
+ );
326
+ }
327
+ redeemScript = new Script(p.value);
328
+ continue;
329
+ }
330
+ // Bitcoin ABC PSBT_IN_UTXO: key is type byte only (see psbt.h).
331
+ if (t === PSBT_IN_UTXO && p.key.length !== 1) {
332
+ throw new Error('PSBT: input UTXO key must be exactly one byte');
333
+ }
334
+ if (t === PSBT_IN_UTXO && p.key.length === 1) {
335
+ pair00 = p;
336
+ } else if (t === PSBT_IN_PARTIAL_SIG) {
337
+ if (!isValidPartialSigKeyLength(p.key.length)) {
338
+ throw new Error(
339
+ 'PSBT: invalid partial signature pubkey key size',
340
+ );
341
+ }
342
+ partialSigs.set(pubkeyHex(p.key.slice(1)), p.value);
343
+ } else if (t === PSBT_IN_BIP32_DERIVATION) {
344
+ validateBip32DerivationKeyValue(p.key, p.value);
345
+ unknown.push(p);
346
+ } else {
347
+ unknown.push(p);
348
+ }
349
+ }
350
+
351
+ let witness: { sats: bigint; scriptPubKey: Uint8Array } | undefined;
352
+ if (pair00 !== undefined) {
353
+ const w = resolveWitnessFromKey00(pair00.value, prevOut);
354
+ if (w === undefined) {
355
+ throw new Error('PSBT input: invalid PSBT_IN_UTXO (0x00) value');
356
+ }
357
+ witness = w;
358
+ }
359
+
360
+ return { witness, redeemScript, partialSigs, unknown };
361
+ }
362
+
363
+ /**
364
+ * BIP 174 PSBT decode/encode for eCash, aligned with Bitcoin ABC’s PSBT maps.
365
+ * See https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#serialization
366
+ *
367
+ * Use {@link Psbt.fromBytes} / {@link Psbt.toBytes} for round-trip; unknown
368
+ * key-value pairs are preserved (BIP 174).
369
+ */
370
+ export class Psbt {
371
+ /** Unsigned transaction (empty scriptSigs). */
372
+ public readonly unsignedTx: Tx;
373
+ /** Per-input data derived from maps (amount, scripts, partial sigs). */
374
+ public readonly signDataPerInput: SignData[];
375
+ /** Per-input partial signatures: hex(pubkey) → signature incl. sighash byte. */
376
+ public readonly inputPartialSigs: Map<string, Uint8Array>[];
377
+ /**
378
+ * Unknown global key-value pairs (BIP 174: implementations must preserve
379
+ * unknown keys on round-trip).
380
+ */
381
+ public readonly unknownGlobalPairs: PsbtKeyValue[];
382
+ /** Unknown per-input pairs (excluding consumed `PSBT_IN_UTXO` / `0x00` entries). */
383
+ public readonly unknownInputPairs: PsbtKeyValue[][];
384
+ /** Unknown per-output pairs. */
385
+ public readonly unknownOutputPairs: PsbtKeyValue[][];
386
+ /**
387
+ * When true, this input had no `PSBT_IN_UTXO` (`0x00`) field in the PSBT (e.g.
388
+ * creator-only PSBT or finalized script fields only). {@link toBytes} will
389
+ * not emit that entry for the input.
390
+ */
391
+ public readonly inputWitnessIncomplete: boolean[];
392
+
393
+ public constructor(params: {
394
+ unsignedTx: Tx;
395
+ signDataPerInput: SignData[];
396
+ inputPartialSigs: Map<string, Uint8Array>[];
397
+ unknownGlobalPairs?: PsbtKeyValue[];
398
+ unknownInputPairs?: PsbtKeyValue[][];
399
+ unknownOutputPairs?: PsbtKeyValue[][];
400
+ inputWitnessIncomplete?: boolean[];
401
+ }) {
402
+ if (
403
+ params.signDataPerInput.length !== params.unsignedTx.inputs.length
404
+ ) {
405
+ throw new Error('signDataPerInput length must match inputs');
406
+ }
407
+ if (
408
+ params.inputPartialSigs.length !== params.unsignedTx.inputs.length
409
+ ) {
410
+ throw new Error('inputPartialSigs length must match inputs');
411
+ }
412
+ this.unsignedTx = params.unsignedTx;
413
+ this.signDataPerInput = params.signDataPerInput;
414
+ this.inputPartialSigs = params.inputPartialSigs;
415
+ this.unknownGlobalPairs = params.unknownGlobalPairs ?? [];
416
+ this.unknownInputPairs =
417
+ params.unknownInputPairs ?? params.unsignedTx.inputs.map(() => []);
418
+ this.unknownOutputPairs =
419
+ params.unknownOutputPairs ??
420
+ params.unsignedTx.outputs.map(() => []);
421
+ if (this.unknownInputPairs.length !== this.unsignedTx.inputs.length) {
422
+ throw new Error('unknownInputPairs length must match inputs');
423
+ }
424
+ if (this.unknownOutputPairs.length !== this.unsignedTx.outputs.length) {
425
+ throw new Error('unknownOutputPairs length must match outputs');
426
+ }
427
+ this.inputWitnessIncomplete =
428
+ params.inputWitnessIncomplete ??
429
+ params.unsignedTx.inputs.map(() => false);
430
+ if (
431
+ this.inputWitnessIncomplete.length !== this.unsignedTx.inputs.length
432
+ ) {
433
+ throw new Error('inputWitnessIncomplete length must match inputs');
434
+ }
435
+ }
436
+
437
+ /** Deserialize PSBT bytes (BIP 174). */
438
+ public static fromBytes(data: Uint8Array): Psbt {
439
+ const bytes = new Bytes(data);
440
+ for (let i = 0; i < PSBT_MAGIC.length; i++) {
441
+ if (bytes.readU8() !== PSBT_MAGIC[i]) {
442
+ throw new Error('Invalid PSBT: bad magic');
443
+ }
444
+ }
445
+
446
+ const globalPairs = parseMap(bytes);
447
+ assertUniquePsbtKeys(globalPairs, 'global');
448
+ let unsignedRaw: Uint8Array | undefined;
449
+ const unknownGlobalPairs: PsbtKeyValue[] = [];
450
+ for (const { key, value } of globalPairs) {
451
+ if (key.length === 0) continue;
452
+ if (key[0] === PSBT_GLOBAL_UNSIGNED_TX && key.length === 1) {
453
+ unsignedRaw = value;
454
+ } else {
455
+ unknownGlobalPairs.push({ key, value });
456
+ }
457
+ }
458
+ if (unsignedRaw === undefined) {
459
+ throw new Error('PSBT missing global unsigned transaction');
460
+ }
461
+
462
+ const unsignedTx = Tx.deser(unsignedRaw);
463
+ assertUnsignedTxEmptyScriptSigs(unsignedTx);
464
+ const inputPartialSigs: Map<string, Uint8Array>[] = [];
465
+ const signDataPerInput: SignData[] = [];
466
+ const unknownInputPairs: PsbtKeyValue[][] = [];
467
+ const inputWitnessIncomplete: boolean[] = [];
468
+
469
+ for (let i = 0; i < unsignedTx.inputs.length; i++) {
470
+ const inPairs = parseMap(bytes);
471
+ assertUniquePsbtKeys(inPairs, `input ${i}`);
472
+ const prevOut = unsignedTx.inputs[i]!.prevOut;
473
+ const { witness, redeemScript, partialSigs, unknown } =
474
+ parseInputMapPairs(inPairs, prevOut);
475
+ unknownInputPairs.push(unknown);
476
+ const incomplete = witness === undefined;
477
+ inputWitnessIncomplete.push(incomplete);
478
+
479
+ let signData: SignData;
480
+ if (incomplete) {
481
+ signData =
482
+ redeemScript !== undefined
483
+ ? { sats: 0n, redeemScript }
484
+ : { sats: 0n, outputScript: new Script() };
485
+ } else if (redeemScript !== undefined) {
486
+ signData = {
487
+ sats: witness.sats,
488
+ redeemScript,
489
+ };
490
+ } else {
491
+ signData = {
492
+ sats: witness.sats,
493
+ outputScript: new Script(witness.scriptPubKey),
494
+ };
495
+ }
496
+ signDataPerInput.push(signData);
497
+ inputPartialSigs.push(partialSigs);
498
+ }
499
+
500
+ const unknownOutputPairs: PsbtKeyValue[][] = [];
501
+ for (let o = 0; o < unsignedTx.outputs.length; o++) {
502
+ const outPairs = parseMap(bytes);
503
+ assertUniquePsbtKeys(outPairs, `output ${o}`);
504
+ for (const p of outPairs) {
505
+ if (p.key.length === 0) continue;
506
+ const ot = p.key[0]!;
507
+ // PSBTOutput::Unserialize (Bitcoin ABC): redeem script key is type byte only.
508
+ if (ot === PSBT_OUT_REDEEMSCRIPT && p.key.length !== 1) {
509
+ throw new Error(
510
+ 'PSBT: output redeemScript key must be exactly one byte',
511
+ );
512
+ }
513
+ if (ot === PSBT_OUT_BIP32_DERIVATION) {
514
+ validateBip32DerivationKeyValue(p.key, p.value);
515
+ }
516
+ }
517
+ unknownOutputPairs.push(outPairs.filter(p => p.key.length > 0));
518
+ }
519
+
520
+ if (bytes.idx !== data.length) {
521
+ throw new Error('PSBT: trailing bytes after output maps');
522
+ }
523
+
524
+ return new Psbt({
525
+ unsignedTx,
526
+ signDataPerInput,
527
+ inputPartialSigs,
528
+ unknownGlobalPairs,
529
+ unknownInputPairs,
530
+ unknownOutputPairs,
531
+ inputWitnessIncomplete,
532
+ });
533
+ }
534
+
535
+ /** Serialize to BIP 174 bytes. */
536
+ public toBytes(): Uint8Array {
537
+ const unsignedSer = this.unsignedTx.ser();
538
+ const globalPairs: PsbtKeyValue[] = [
539
+ {
540
+ key: new Uint8Array([PSBT_GLOBAL_UNSIGNED_TX]),
541
+ value: unsignedSer,
542
+ },
543
+ ...this.unknownGlobalPairs,
544
+ ];
545
+
546
+ const parts: Uint8Array[] = [PSBT_MAGIC, serializeMap(globalPairs)];
547
+
548
+ for (let i = 0; i < this.unsignedTx.inputs.length; i++) {
549
+ const sd = this.signDataPerInput[i]!;
550
+ const inPairs: PsbtKeyValue[] = [];
551
+ if (!this.inputWitnessIncomplete[i]) {
552
+ const spk = scriptPubKeyFromSignData(sd);
553
+ inPairs.push({
554
+ key: new Uint8Array([PSBT_IN_UTXO]),
555
+ value: encodeWitnessUtxo(sd.sats, spk),
556
+ });
557
+ }
558
+ if (sd.redeemScript !== undefined) {
559
+ inPairs.push({
560
+ key: new Uint8Array([PSBT_IN_REDEEM_SCRIPT]),
561
+ value: sd.redeemScript.bytecode,
562
+ });
563
+ }
564
+ const pSig = this.inputPartialSigs[i]!;
565
+ for (const [pkHex, sig] of pSig) {
566
+ const pk = fromHex(pkHex);
567
+ const key = new Uint8Array(1 + pk.length);
568
+ key[0] = PSBT_IN_PARTIAL_SIG;
569
+ key.set(pk, 1);
570
+ inPairs.push({ key, value: sig });
571
+ }
572
+ inPairs.push(...(this.unknownInputPairs[i] ?? []));
573
+ parts.push(serializeMap(inPairs));
574
+ }
575
+
576
+ for (let o = 0; o < this.unsignedTx.outputs.length; o++) {
577
+ parts.push(serializeMap(this.unknownOutputPairs[o] ?? []));
578
+ }
579
+
580
+ const wl = new WriterLength();
581
+ for (const p of parts) {
582
+ wl.putBytes(p);
583
+ }
584
+ const w = new WriterBytes(wl.length);
585
+ for (const p of parts) {
586
+ w.putBytes(p);
587
+ }
588
+ return w.data;
589
+ }
590
+ }