ecash-lib 4.11.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/src/psbt.ts CHANGED
@@ -6,15 +6,18 @@
6
6
  * Partially Signed Bitcoin Transaction (PSBT) per **BIP 174**:
7
7
  * - Spec: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
8
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).
9
+ * Decode/encode aligned with Bitcoin ABC (`{@link Psbt.fromBytes}`,
10
+ * `{@link Psbt.toBytes}`), plus multisig workflows: per-input `PSBT_IN_UTXO`
11
+ * (`0x00`), redeem script, partial signatures; unknown pairs preserved (BIP 174).
13
12
  *
14
13
  * **Input key `0x00` (`PSBT_IN_UTXO`):** BIP 174 also defines type `0x01` (“witness
15
14
  * UTXO”) for the same *value* shape. **Bitcoin ABC only implements `0x00`:** value
16
15
  * is `CTxOut` or full previous tx (non-witness UTXO). We match the node; `0x01`
17
16
  * entries are preserved as unknown keys on round-trip.
17
+ *
18
+ * High-level merge/sign helpers: {@link Tx.addMultisigSignature},
19
+ * {@link Tx.addMultisigSignatureFromKey}; {@link Psbt.fromTx} builds a PSBT from a
20
+ * partially signed {@link Tx}; {@link Psbt.toTx} rebuilds scriptSigs from partial sigs.
18
21
  */
19
22
 
20
23
  import { Bytes } from './io/bytes.js';
@@ -23,9 +26,19 @@ import { readVarSize, writeVarSize } from './io/varsize.js';
23
26
  import { WriterBytes } from './io/writerbytes.js';
24
27
  import { WriterLength } from './io/writerlength.js';
25
28
  import { Writer } from './io/writer.js';
26
- import { shaRmd160 } from './hash.js';
29
+ import { Ecc } from './ecc.js';
30
+ import { shaRmd160, sha256d } from './hash.js';
27
31
  import { Script } from './script.js';
28
- import { OutPoint, SignData, Tx } from './tx.js';
32
+ import { ALL_BIP143, type SigHashType } from './sigHashType.js';
33
+ import {
34
+ copyTxInput,
35
+ copyTxOutput,
36
+ OutPoint,
37
+ SignData,
38
+ Tx,
39
+ TxInput,
40
+ } from './tx.js';
41
+ import { UnsignedTx } from './unsignedTx.js';
29
42
 
30
43
  /**
31
44
  * BIP 174 **magic bytes** for PSBT version 0: ASCII `psbt` + `0xff`.
@@ -206,6 +219,20 @@ function validateBip32DerivationKeyValue(
206
219
  }
207
220
  }
208
221
 
222
+ /** Strip input scripts for PSBT global unsigned transaction (BIP 174). */
223
+ export function txToUnsigned(tx: Tx): Tx {
224
+ return new Tx({
225
+ version: tx.version,
226
+ inputs: tx.inputs.map(inp => ({
227
+ prevOut: inp.prevOut,
228
+ script: new Script(),
229
+ sequence: inp.sequence,
230
+ })),
231
+ outputs: tx.outputs.map(o => copyTxOutput(o)),
232
+ locktime: tx.locktime,
233
+ });
234
+ }
235
+
209
236
  /** `CTxOut` bytes for a {@link PSBT_IN_UTXO} map value (same layout BIP 174 labels “witness UTXO”). */
210
237
  function encodeWitnessUtxo(sats: bigint, scriptPubKey: Uint8Array): Uint8Array {
211
238
  const wl = new WriterLength();
@@ -279,6 +306,178 @@ function scriptPubKeyFromSignData(signData: SignData): Uint8Array {
279
306
  throw new Error('SignData needs redeemScript or outputScript for PSBT');
280
307
  }
281
308
 
309
+ /**
310
+ * The multisig **redeem** script used to interpret partial sigs: P2SH `redeemScript`,
311
+ * or bare `outputScript` (the locking script is the multisig template itself).
312
+ */
313
+ function multisigLockingScript(signData: SignData): Script {
314
+ if (signData.redeemScript !== undefined) {
315
+ return signData.redeemScript;
316
+ }
317
+ if (signData.outputScript !== undefined) {
318
+ return signData.outputScript;
319
+ }
320
+ throw new Error('SignData needs redeemScript or outputScript');
321
+ }
322
+
323
+ /**
324
+ * Build the input `scriptSig` for a multisig spend from the PSBT partial-signature
325
+ * map (pubkey hex → signature with sighash byte). Used by {@link Psbt.toTx} only.
326
+ *
327
+ * Matches pubkey order from {@link multisigLockingScript}'s `parseMultisigRedeemScript`,
328
+ * then dispatches:
329
+ * - **Schnorr** if any partial sig has length 65 (Schnorr + sighash): uses
330
+ * {@link Script.multisigSpend} with `pubkeyIndices` derived from which pubkeys
331
+ * have sigs (bare passes `numPubkeys`; P2SH passes `redeemScript`).
332
+ * - **ECDSA** otherwise: fills `m` slots left-to-right from present sigs (same
333
+ * convention as {@link BareMultisigSignatory} / {@link P2SHMultisigSignatory}).
334
+ */
335
+ function buildScriptSigFromPartialSigs(
336
+ signData: SignData,
337
+ partialSigs: Map<string, Uint8Array>,
338
+ ): Script {
339
+ const lock = multisigLockingScript(signData);
340
+ const { numSignatures, numPubkeys, pubkeys } =
341
+ lock.parseMultisigRedeemScript();
342
+ const perPk = pubkeys.map(pk => partialSigs.get(pubkeyHex(pk)));
343
+ const signatures = perPk.filter((s): s is Uint8Array => s !== undefined);
344
+
345
+ const anySchnorr = [...partialSigs.values()].some(s => s.length === 65);
346
+ if (anySchnorr) {
347
+ const pubkeyIndices = new Set(
348
+ perPk.flatMap((s, i) => (s !== undefined ? [i] : [])),
349
+ );
350
+ if (signData.redeemScript !== undefined) {
351
+ return Script.multisigSpend({
352
+ signatures,
353
+ redeemScript: signData.redeemScript,
354
+ pubkeyIndices,
355
+ });
356
+ }
357
+ return Script.multisigSpend({
358
+ signatures,
359
+ pubkeyIndices,
360
+ numPubkeys,
361
+ });
362
+ }
363
+
364
+ const sigsForScript: (Uint8Array | undefined)[] =
365
+ signatures.length >= numSignatures
366
+ ? signatures.slice(0, numSignatures)
367
+ : [
368
+ ...signatures,
369
+ ...Array(numSignatures - signatures.length).fill(undefined),
370
+ ];
371
+
372
+ return Script.multisigSpend({
373
+ signatures: sigsForScript,
374
+ redeemScript: signData.redeemScript,
375
+ });
376
+ }
377
+
378
+ /**
379
+ * Extract pubkey → signature (with sighash byte) from a multisig scriptSig.
380
+ */
381
+ function extractPartialSigsFromInput(
382
+ script: Script,
383
+ signData: SignData,
384
+ unsignedTx: Tx,
385
+ inputIdx: number,
386
+ ecc: Ecc,
387
+ ): Map<string, Uint8Array> {
388
+ const map = new Map<string, Uint8Array>();
389
+ if (!script.bytecode.length) return map;
390
+
391
+ const inputs: TxInput[] = unsignedTx.inputs.map((inp, i) => ({
392
+ ...copyTxInput(inp),
393
+ signData: i === inputIdx ? signData : undefined,
394
+ }));
395
+ inputs[inputIdx] = {
396
+ ...copyTxInput(unsignedTx.inputs[inputIdx]!),
397
+ script,
398
+ signData,
399
+ };
400
+
401
+ const txForPreimage = new Tx({
402
+ version: unsignedTx.version,
403
+ inputs,
404
+ outputs: unsignedTx.outputs.map(copyTxOutput),
405
+ locktime: unsignedTx.locktime,
406
+ });
407
+
408
+ const msgHash = sha256d(
409
+ UnsignedTx.fromTx(txForPreimage)
410
+ .inputAt(inputIdx)
411
+ .sigHashPreimage(ALL_BIP143).bytes,
412
+ );
413
+
414
+ if (signData.redeemScript !== undefined) {
415
+ const parsed = script.parseP2shMultisigSpend();
416
+ if (parsed.isSchnorr) {
417
+ const sorted = [...(parsed.pubkeyIndices ?? [])].sort(
418
+ (a, b) => a - b,
419
+ );
420
+ for (let i = 0; i < sorted.length; i++) {
421
+ const pkIdx = sorted[i]!;
422
+ const pk = parsed.pubkeys[pkIdx]!;
423
+ const sig = parsed.signatures[i];
424
+ if (sig !== undefined) map.set(pubkeyHex(pk), sig);
425
+ }
426
+ return map;
427
+ }
428
+ for (const sig of parsed.signatures) {
429
+ if (sig === undefined) continue;
430
+ const sigNo = sig.slice(0, -1);
431
+ for (const pk of parsed.pubkeys) {
432
+ try {
433
+ ecc.ecdsaVerify(sigNo, msgHash, pk);
434
+ map.set(pubkeyHex(pk), sig);
435
+ break;
436
+ } catch {
437
+ /* next */
438
+ }
439
+ }
440
+ }
441
+ return map;
442
+ }
443
+
444
+ if (signData.outputScript !== undefined) {
445
+ const parsed = script.parseBareMultisigSpend(signData.outputScript);
446
+ if (parsed.isSchnorr) {
447
+ const sorted = [...(parsed.pubkeyIndices ?? [])].sort(
448
+ (a, b) => a - b,
449
+ );
450
+ for (let i = 0; i < sorted.length; i++) {
451
+ const pkIdx = sorted[i]!;
452
+ const pk = parsed.pubkeys[pkIdx]!;
453
+ const sig = parsed.signatures[i];
454
+ if (sig !== undefined) map.set(pubkeyHex(pk), sig);
455
+ }
456
+ return map;
457
+ }
458
+ for (const sig of parsed.signatures) {
459
+ if (sig === undefined) continue;
460
+ const sigNo = sig.slice(0, -1);
461
+ for (const pk of parsed.pubkeys) {
462
+ try {
463
+ ecc.ecdsaVerify(sigNo, msgHash, pk);
464
+ map.set(pubkeyHex(pk), sig);
465
+ break;
466
+ } catch {
467
+ /* next */
468
+ }
469
+ }
470
+ }
471
+ }
472
+ return map;
473
+ }
474
+
475
+ export interface PsbtInputMaps {
476
+ witnessUtxo: { sats: bigint; scriptPubKey: Uint8Array };
477
+ redeemScript?: Script;
478
+ partialSigs: Map<string, Uint8Array>;
479
+ }
480
+
282
481
  /** One PSBT key-value pair (BIP 174). */
283
482
  export type PsbtKeyValue = { key: Uint8Array; value: Uint8Array };
284
483
 
@@ -361,16 +560,17 @@ function parseInputMapPairs(
361
560
  }
362
561
 
363
562
  /**
364
- * BIP 174 PSBT decode/encode for eCash, aligned with Bitcoin ABC’s PSBT maps.
563
+ * BIP 174 PSBT for eCash multisig and ABC-aligned decode/encode.
365
564
  * See https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#serialization
366
565
  *
367
- * Use {@link Psbt.fromBytes} / {@link Psbt.toBytes} for round-trip; unknown
368
- * key-value pairs are preserved (BIP 174).
566
+ * Typical flow: {@link Psbt.fromTx} from a partially signed {@link Tx} plus
567
+ * {@link SignData} per input {@link Psbt.toBytes} → share → {@link Psbt.fromBytes}.
568
+ * Unknown key-value pairs are preserved (BIP 174).
369
569
  */
370
570
  export class Psbt {
371
571
  /** Unsigned transaction (empty scriptSigs). */
372
572
  public readonly unsignedTx: Tx;
373
- /** Per-input data derived from maps (amount, scripts, partial sigs). */
573
+ /** Per-input signing data (amount, scripts, partial sig maps). */
374
574
  public readonly signDataPerInput: SignData[];
375
575
  /** Per-input partial signatures: hex(pubkey) → signature incl. sighash byte. */
376
576
  public readonly inputPartialSigs: Map<string, Uint8Array>[];
@@ -434,6 +634,35 @@ export class Psbt {
434
634
  }
435
635
  }
436
636
 
637
+ /**
638
+ * Build a PSBT from a transaction that may already include partial scriptSigs.
639
+ * Populates `PSBT_IN_UTXO` (`0x00`) + redeem script + partial signatures.
640
+ */
641
+ public static fromTx(tx: Tx, signDataPerInput: SignData[], ecc: Ecc): Psbt {
642
+ const unsignedTx = txToUnsigned(tx);
643
+ const inputPartialSigs: Map<string, Uint8Array>[] = [];
644
+ for (let i = 0; i < tx.inputs.length; i++) {
645
+ const script = tx.inputs[i]?.script ?? new Script();
646
+ const sigs = extractPartialSigsFromInput(
647
+ script,
648
+ signDataPerInput[i]!,
649
+ unsignedTx,
650
+ i,
651
+ ecc,
652
+ );
653
+ inputPartialSigs.push(sigs);
654
+ }
655
+ return new Psbt({
656
+ unsignedTx,
657
+ signDataPerInput,
658
+ inputPartialSigs,
659
+ unknownGlobalPairs: [],
660
+ unknownInputPairs: unsignedTx.inputs.map(() => []),
661
+ unknownOutputPairs: unsignedTx.outputs.map(() => []),
662
+ inputWitnessIncomplete: unsignedTx.inputs.map(() => false),
663
+ });
664
+ }
665
+
437
666
  /** Deserialize PSBT bytes (BIP 174). */
438
667
  public static fromBytes(data: Uint8Array): Psbt {
439
668
  const bytes = new Bytes(data);
@@ -587,4 +816,93 @@ export class Psbt {
587
816
  }
588
817
  return w.data;
589
818
  }
819
+
820
+ /**
821
+ * Current transaction with scriptSigs built from partial signatures.
822
+ * Attach each input's `signData` for signing and validation helpers.
823
+ */
824
+ public toTx(): Tx {
825
+ return new Tx({
826
+ version: this.unsignedTx.version,
827
+ inputs: this.unsignedTx.inputs.map((inp, i) => {
828
+ const sd = this.signDataPerInput[i]!;
829
+ const script = buildScriptSigFromPartialSigs(
830
+ sd,
831
+ this.inputPartialSigs[i]!,
832
+ );
833
+ return {
834
+ ...copyTxInput(inp),
835
+ script,
836
+ signData: sd,
837
+ };
838
+ }),
839
+ outputs: this.unsignedTx.outputs.map(copyTxOutput),
840
+ locktime: this.unsignedTx.locktime,
841
+ });
842
+ }
843
+
844
+ /**
845
+ * Add or merge a multisig signature on an input (same semantics as
846
+ * {@link Tx.addMultisigSignature}).
847
+ */
848
+ public addMultisigSignature(params: {
849
+ inputIdx: number;
850
+ signature: Uint8Array;
851
+ signData: SignData;
852
+ ecc?: Ecc;
853
+ }): Psbt {
854
+ const tx = this.toTx();
855
+ const nextTx = tx.addMultisigSignature(params);
856
+ const next = Psbt.fromTx(
857
+ nextTx,
858
+ this.signDataPerInput,
859
+ params.ecc ?? new Ecc(),
860
+ );
861
+ return new Psbt({
862
+ unsignedTx: next.unsignedTx,
863
+ signDataPerInput: next.signDataPerInput,
864
+ inputPartialSigs: next.inputPartialSigs,
865
+ unknownGlobalPairs: this.unknownGlobalPairs,
866
+ unknownInputPairs: this.unknownInputPairs,
867
+ unknownOutputPairs: this.unknownOutputPairs,
868
+ inputWitnessIncomplete: this.inputWitnessIncomplete,
869
+ });
870
+ }
871
+
872
+ /**
873
+ * Like {@link addMultisigSignature}, but signs with a secret key (see
874
+ * {@link Tx.addMultisigSignatureFromKey}).
875
+ */
876
+ public addMultisigSignatureFromKey(params: {
877
+ inputIdx: number;
878
+ sk: Uint8Array;
879
+ signData: SignData;
880
+ sigHashType?: SigHashType;
881
+ ecc?: Ecc;
882
+ }): Psbt {
883
+ const tx = this.toTx();
884
+ const nextTx = tx.addMultisigSignatureFromKey(params);
885
+ const next = Psbt.fromTx(
886
+ nextTx,
887
+ this.signDataPerInput,
888
+ params.ecc ?? new Ecc(),
889
+ );
890
+ return new Psbt({
891
+ unsignedTx: next.unsignedTx,
892
+ signDataPerInput: next.signDataPerInput,
893
+ inputPartialSigs: next.inputPartialSigs,
894
+ unknownGlobalPairs: this.unknownGlobalPairs,
895
+ unknownInputPairs: this.unknownInputPairs,
896
+ unknownOutputPairs: this.unknownOutputPairs,
897
+ inputWitnessIncomplete: this.inputWitnessIncomplete,
898
+ });
899
+ }
900
+
901
+ /**
902
+ * Same as {@link Tx.isFullySignedMultisig} on {@link toTx} (including
903
+ * vacuous `true` when there are no multisig inputs).
904
+ */
905
+ public isFullySignedMultisig(): boolean {
906
+ return this.toTx().isFullySignedMultisig();
907
+ }
590
908
  }
package/src/script.ts CHANGED
@@ -216,6 +216,29 @@ export class Script {
216
216
  return Script.fromOps(ops);
217
217
  }
218
218
 
219
+ /**
220
+ * Return true iff this script has the shape of a standard multisig output script
221
+ * `OP_m pubkey_1 ... pubkey_N OP_N OP_CHECKMULTISIG` (fixed size: `N + 3` ops).
222
+ */
223
+ public isMultisig(): boolean {
224
+ const ops: Op[] = [];
225
+ const iter = this.ops();
226
+ let op: Op | undefined;
227
+ while ((op = iter.next()) !== undefined) {
228
+ ops.push(op);
229
+ }
230
+ if (ops.length < 4 || ops[ops.length - 1] !== OP_CHECKMULTISIG) {
231
+ return false;
232
+ }
233
+ try {
234
+ parseNumberFromOp(ops[0]!);
235
+ const numPubkeys = Number(parseNumberFromOp(ops[ops.length - 2]!));
236
+ return ops.length === numPubkeys + 3;
237
+ } catch {
238
+ return false;
239
+ }
240
+ }
241
+
219
242
  /**
220
243
  * Build scriptSig for multisig: <dummy> sig1 sig2 ... sig_m [redeemScript].
221
244
  * Omit redeemScript for bare multisig; include for P2SH-wrapped.
@@ -346,7 +369,8 @@ export class Script {
346
369
  return Script.parseMultisigScriptSig(ops, outputScript);
347
370
  }
348
371
 
349
- private parseMultisigRedeemScript(): {
372
+ /** Parse an OP_m ... OP_CHECKMULTISIG redeem script (used by multisig PSBT flows). */
373
+ public parseMultisigRedeemScript(): {
350
374
  numSignatures: number;
351
375
  numPubkeys: number;
352
376
  pubkeys: Uint8Array[];
@@ -6,7 +6,11 @@
6
6
  * Signatories and signing helpers used with `TxBuilder` (`TxBuilderInput.signatory`).
7
7
  */
8
8
 
9
- import { Ecc } from './ecc.js';
9
+ import { Ecc, EccDummy } from './ecc.js';
10
+ import {
11
+ ECDSA_SIG_ESTIMATE_BYTES,
12
+ SCHNORR_SIG_ESTIMATE_BYTES,
13
+ } from './consts.js';
10
14
  import { sha256d } from './hash.js';
11
15
  import { WriterBytes } from './io/writerbytes.js';
12
16
  import { pushBytesOp } from './op.js';
@@ -65,7 +69,7 @@ export const P2PKHSignatory = (
65
69
  sk: Uint8Array,
66
70
  pk: Uint8Array,
67
71
  sigHashType: SigHashType,
68
- ) => {
72
+ ): Signatory => {
69
73
  return (ecc: Ecc, input: UnsignedTxInput): Script => {
70
74
  const preimage = input.sigHashPreimage(sigHashType);
71
75
  const sighash = sha256d(preimage.bytes);
@@ -74,8 +78,215 @@ export const P2PKHSignatory = (
74
78
  };
75
79
  };
76
80
 
81
+ /** Signatory for bare m-of-n multisig (output script is the multisig script itself). */
82
+ export const BareMultisigSignatory = (
83
+ m: number,
84
+ pubkeys: Uint8Array[],
85
+ sk: Uint8Array,
86
+ myPk: Uint8Array,
87
+ sigHashType: SigHashType,
88
+ ): Signatory => {
89
+ return (ecc: Ecc, input: UnsignedTxInput): Script => {
90
+ if (ecc instanceof EccDummy) {
91
+ const dummySig = new Uint8Array(ECDSA_SIG_ESTIMATE_BYTES);
92
+ const signatures = Array(m)
93
+ .fill(undefined)
94
+ .map(() => dummySig);
95
+ return Script.multisigSpend({ signatures });
96
+ }
97
+ const preimage = input.sigHashPreimage(sigHashType);
98
+ const sighash = sha256d(preimage.bytes);
99
+ const sig = ecc.ecdsaSign(sk, sighash);
100
+ const sigFlagged = flagSignature(sig, sigHashType);
101
+ const myIndex = pubkeys.findIndex(
102
+ pk =>
103
+ pk.length === myPk.length && pk.every((b, i) => b === myPk[i]),
104
+ );
105
+ if (myIndex < 0) {
106
+ throw new Error('Signer pubkey not found in multisig pubkeys');
107
+ }
108
+ const n = pubkeys.length;
109
+ const signatures: (Uint8Array | undefined)[] = Array(n).fill(undefined);
110
+ signatures[myIndex] = sigFlagged;
111
+ const nonNull = signatures.filter(
112
+ (s): s is Uint8Array => s !== undefined,
113
+ );
114
+ const sigsForScript: (Uint8Array | undefined)[] = [
115
+ ...nonNull,
116
+ ...Array(m - nonNull.length).fill(undefined),
117
+ ];
118
+ return Script.multisigSpend({ signatures: sigsForScript });
119
+ };
120
+ };
121
+
122
+ /** Signatory for bare m-of-n multisig using Schnorr signatures (BIP143 Schnorr multisig). */
123
+ export const BareMultisigSignatorySchnorr = (
124
+ m: number,
125
+ pubkeys: Uint8Array[],
126
+ sk: Uint8Array,
127
+ myPk: Uint8Array,
128
+ signerIndices: Set<number>,
129
+ sigHashType: SigHashType,
130
+ ): Signatory => {
131
+ return (ecc: Ecc, input: UnsignedTxInput): Script => {
132
+ const n = pubkeys.length;
133
+ if (signerIndices.size !== m) {
134
+ throw new Error(
135
+ `signerIndices must have ${m} elements for ${m}-of-${n} multisig`,
136
+ );
137
+ }
138
+ if (ecc instanceof EccDummy) {
139
+ const dummySig = new Uint8Array(SCHNORR_SIG_ESTIMATE_BYTES);
140
+ const signatures = Array(m)
141
+ .fill(undefined)
142
+ .map(() => dummySig);
143
+ return Script.multisigSpend({
144
+ signatures,
145
+ pubkeyIndices: signerIndices,
146
+ numPubkeys: n,
147
+ });
148
+ }
149
+ const preimage = input.sigHashPreimage(sigHashType);
150
+ const sighash = sha256d(preimage.bytes);
151
+ const sig = ecc.schnorrSign(sk, sighash);
152
+ const sigFlagged = flagSignature(sig, sigHashType);
153
+ const myIndex = pubkeys.findIndex(
154
+ pk =>
155
+ pk.length === myPk.length && pk.every((b, i) => b === myPk[i]),
156
+ );
157
+ if (myIndex < 0) {
158
+ throw new Error('Signer pubkey not found in multisig pubkeys');
159
+ }
160
+ if (!signerIndices.has(myIndex)) {
161
+ throw new Error(
162
+ `Signer index ${myIndex} not in signerIndices ${[
163
+ ...signerIndices,
164
+ ].join(',')}`,
165
+ );
166
+ }
167
+ const sortedIndices = [...signerIndices].sort((a, b) => a - b);
168
+ const signatures: (Uint8Array | undefined)[] = sortedIndices.map(idx =>
169
+ idx === myIndex ? sigFlagged : undefined,
170
+ );
171
+ return Script.multisigSpend({
172
+ signatures,
173
+ pubkeyIndices: signerIndices,
174
+ numPubkeys: n,
175
+ });
176
+ };
177
+ };
178
+
179
+ /** Signatory for P2SH m-of-n multisig. */
180
+ export const P2SHMultisigSignatory = (
181
+ m: number,
182
+ pubkeys: Uint8Array[],
183
+ sk: Uint8Array,
184
+ myPk: Uint8Array,
185
+ sigHashType: SigHashType,
186
+ ): Signatory => {
187
+ return (ecc: Ecc, input: UnsignedTxInput): Script => {
188
+ const redeemScript = Script.multisig(m, pubkeys);
189
+ if (ecc instanceof EccDummy) {
190
+ const dummySig = new Uint8Array(ECDSA_SIG_ESTIMATE_BYTES);
191
+ const signatures = Array(m)
192
+ .fill(undefined)
193
+ .map(() => dummySig);
194
+ return Script.multisigSpend({
195
+ signatures,
196
+ redeemScript,
197
+ });
198
+ }
199
+ const preimage = input.sigHashPreimage(sigHashType);
200
+ const sighash = sha256d(preimage.bytes);
201
+ const sig = ecc.ecdsaSign(sk, sighash);
202
+ const sigFlagged = flagSignature(sig, sigHashType);
203
+ const myIndex = pubkeys.findIndex(
204
+ pk =>
205
+ pk.length === myPk.length && pk.every((b, i) => b === myPk[i]),
206
+ );
207
+ if (myIndex < 0) {
208
+ throw new Error('Signer pubkey not found in multisig pubkeys');
209
+ }
210
+ const n = pubkeys.length;
211
+ const signatures: (Uint8Array | undefined)[] = Array(n).fill(undefined);
212
+ signatures[myIndex] = sigFlagged;
213
+ const nonNull = signatures.filter(
214
+ (s): s is Uint8Array => s !== undefined,
215
+ );
216
+ const sigsForScript: (Uint8Array | undefined)[] = [
217
+ ...nonNull,
218
+ ...Array(m - nonNull.length).fill(undefined),
219
+ ];
220
+ return Script.multisigSpend({
221
+ signatures: sigsForScript,
222
+ redeemScript,
223
+ });
224
+ };
225
+ };
226
+
227
+ /** Signatory for P2SH m-of-n multisig using Schnorr signatures. */
228
+ export const P2SHMultisigSignatorySchnorr = (
229
+ m: number,
230
+ pubkeys: Uint8Array[],
231
+ sk: Uint8Array,
232
+ myPk: Uint8Array,
233
+ signerIndices: Set<number>,
234
+ sigHashType: SigHashType,
235
+ ): Signatory => {
236
+ return (ecc: Ecc, input: UnsignedTxInput): Script => {
237
+ const redeemScript = Script.multisig(m, pubkeys);
238
+ const n = pubkeys.length;
239
+ if (signerIndices.size !== m) {
240
+ throw new Error(
241
+ `signerIndices must have ${m} elements for ${m}-of-${n} multisig`,
242
+ );
243
+ }
244
+ if (ecc instanceof EccDummy) {
245
+ const dummySig = new Uint8Array(SCHNORR_SIG_ESTIMATE_BYTES);
246
+ const signatures = Array(m)
247
+ .fill(undefined)
248
+ .map(() => dummySig);
249
+ return Script.multisigSpend({
250
+ signatures,
251
+ redeemScript,
252
+ pubkeyIndices: signerIndices,
253
+ });
254
+ }
255
+ const preimage = input.sigHashPreimage(sigHashType);
256
+ const sighash = sha256d(preimage.bytes);
257
+ const sig = ecc.schnorrSign(sk, sighash);
258
+ const sigFlagged = flagSignature(sig, sigHashType);
259
+ const myIndex = pubkeys.findIndex(
260
+ pk =>
261
+ pk.length === myPk.length && pk.every((b, i) => b === myPk[i]),
262
+ );
263
+ if (myIndex < 0) {
264
+ throw new Error('Signer pubkey not found in multisig pubkeys');
265
+ }
266
+ if (!signerIndices.has(myIndex)) {
267
+ throw new Error(
268
+ `Signer index ${myIndex} not in signerIndices ${[
269
+ ...signerIndices,
270
+ ].join(',')}`,
271
+ );
272
+ }
273
+ const sortedIndices = [...signerIndices].sort((a, b) => a - b);
274
+ const signatures: (Uint8Array | undefined)[] = sortedIndices.map(idx =>
275
+ idx === myIndex ? sigFlagged : undefined,
276
+ );
277
+ return Script.multisigSpend({
278
+ signatures,
279
+ redeemScript,
280
+ pubkeyIndices: signerIndices,
281
+ });
282
+ };
283
+ };
284
+
77
285
  /** Signatory for a P2PK input. Always uses Schnorr signatures */
78
- export const P2PKSignatory = (sk: Uint8Array, sigHashType: SigHashType) => {
286
+ export const P2PKSignatory = (
287
+ sk: Uint8Array,
288
+ sigHashType: SigHashType,
289
+ ): Signatory => {
79
290
  return (ecc: Ecc, input: UnsignedTxInput): Script => {
80
291
  const preimage = input.sigHashPreimage(sigHashType);
81
292
  const sighash = sha256d(preimage.bytes);