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/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
|
-
|
|
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[];
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// Copyright (c) 2024-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
|
+
* Signatories and signing helpers used with `TxBuilder` (`TxBuilderInput.signatory`).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Ecc, EccDummy } from './ecc.js';
|
|
10
|
+
import {
|
|
11
|
+
ECDSA_SIG_ESTIMATE_BYTES,
|
|
12
|
+
SCHNORR_SIG_ESTIMATE_BYTES,
|
|
13
|
+
} from './consts.js';
|
|
14
|
+
import { sha256d } from './hash.js';
|
|
15
|
+
import { WriterBytes } from './io/writerbytes.js';
|
|
16
|
+
import { pushBytesOp } from './op.js';
|
|
17
|
+
import { Script } from './script.js';
|
|
18
|
+
import { SigHashType, SigHashTypeVariant } from './sigHashType.js';
|
|
19
|
+
import { UnsignedTxInput } from './unsignedTx.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Function that contains all the required data to sign a given `input` and
|
|
23
|
+
* return the scriptSig.
|
|
24
|
+
*
|
|
25
|
+
* Use it by attaching a `Signatory` to a TxBuilderInput, e.g. like this for a
|
|
26
|
+
* P2PKH input:
|
|
27
|
+
* ```ts
|
|
28
|
+
* new TxBuilder({
|
|
29
|
+
* inputs: [{
|
|
30
|
+
* input: { prevOut: ... },
|
|
31
|
+
* signatory: P2PKHSignatory(sk, pk, ALL_BIP143),
|
|
32
|
+
* }],
|
|
33
|
+
* ...
|
|
34
|
+
* })
|
|
35
|
+
* ```
|
|
36
|
+
**/
|
|
37
|
+
export type Signatory = (ecc: Ecc, input: UnsignedTxInput) => Script;
|
|
38
|
+
|
|
39
|
+
/** Append the sighash flags to the signature */
|
|
40
|
+
export function flagSignature(
|
|
41
|
+
sig: Uint8Array,
|
|
42
|
+
sigHashFlags: SigHashType,
|
|
43
|
+
): Uint8Array {
|
|
44
|
+
const writer = new WriterBytes(sig.length + 1);
|
|
45
|
+
writer.putBytes(sig);
|
|
46
|
+
writer.putU8(sigHashFlags.toInt() & 0xff);
|
|
47
|
+
return writer.data;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Sign the sighash using Schnorr for BIP143 signatures and ECDSA for Legacy
|
|
52
|
+
* signatures, and then flags the signature correctly
|
|
53
|
+
**/
|
|
54
|
+
export function signWithSigHash(
|
|
55
|
+
ecc: Ecc,
|
|
56
|
+
sk: Uint8Array,
|
|
57
|
+
sigHash: Uint8Array,
|
|
58
|
+
sigHashType: SigHashType,
|
|
59
|
+
): Uint8Array {
|
|
60
|
+
const sig =
|
|
61
|
+
sigHashType.variant == SigHashTypeVariant.LEGACY
|
|
62
|
+
? ecc.ecdsaSign(sk, sigHash)
|
|
63
|
+
: ecc.schnorrSign(sk, sigHash);
|
|
64
|
+
return flagSignature(sig, sigHashType);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Signatory for a P2PKH input. Always uses Schnorr signatures */
|
|
68
|
+
export const P2PKHSignatory = (
|
|
69
|
+
sk: Uint8Array,
|
|
70
|
+
pk: Uint8Array,
|
|
71
|
+
sigHashType: SigHashType,
|
|
72
|
+
): Signatory => {
|
|
73
|
+
return (ecc: Ecc, input: UnsignedTxInput): Script => {
|
|
74
|
+
const preimage = input.sigHashPreimage(sigHashType);
|
|
75
|
+
const sighash = sha256d(preimage.bytes);
|
|
76
|
+
const sigFlagged = signWithSigHash(ecc, sk, sighash, sigHashType);
|
|
77
|
+
return Script.p2pkhSpend(pk, sigFlagged);
|
|
78
|
+
};
|
|
79
|
+
};
|
|
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
|
+
|
|
285
|
+
/** Signatory for a P2PK input. Always uses Schnorr signatures */
|
|
286
|
+
export const P2PKSignatory = (
|
|
287
|
+
sk: Uint8Array,
|
|
288
|
+
sigHashType: SigHashType,
|
|
289
|
+
): Signatory => {
|
|
290
|
+
return (ecc: Ecc, input: UnsignedTxInput): Script => {
|
|
291
|
+
const preimage = input.sigHashPreimage(sigHashType);
|
|
292
|
+
const sighash = sha256d(preimage.bytes);
|
|
293
|
+
const sigFlagged = signWithSigHash(ecc, sk, sighash, sigHashType);
|
|
294
|
+
return Script.fromOps([pushBytesOp(sigFlagged)]);
|
|
295
|
+
};
|
|
296
|
+
};
|
package/src/tx.ts
CHANGED
|
@@ -8,8 +8,12 @@ import { writeVarSize, readVarSize } from './io/varsize.js';
|
|
|
8
8
|
import { Writer } from './io/writer.js';
|
|
9
9
|
import { WriterBytes } from './io/writerbytes.js';
|
|
10
10
|
import { WriterLength } from './io/writerlength.js';
|
|
11
|
+
import { Ecc } from './ecc.js';
|
|
11
12
|
import { Script } from './script.js';
|
|
12
13
|
import { sha256d } from './hash.js';
|
|
14
|
+
import { flagSignature } from './signatories.js';
|
|
15
|
+
import { ALL_BIP143, SigHashType } from './sigHashType.js';
|
|
16
|
+
import { UnsignedTx } from './unsignedTx.js';
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
19
|
* Default value for nSequence of inputs if left undefined; this opts out of
|
|
@@ -175,6 +179,332 @@ export class Tx {
|
|
|
175
179
|
public txid(): string {
|
|
176
180
|
return toHexRev(sha256d(this.ser()));
|
|
177
181
|
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Attempt to parse a **non-SegWit** serialized transaction from `data` — the same
|
|
185
|
+
* encoding as {@link Tx.deser} / `Tx.ser()` (version, inputs, outputs, locktime;
|
|
186
|
+
* no witness marker or witness stacks). eCash does not use SegWit transactions,
|
|
187
|
+
* so this is the full-transaction wire format on-chain here. Returns a `Tx` only
|
|
188
|
+
* when `data` is **exactly** one such transaction: the parse must consume the
|
|
189
|
+
* **entire** buffer (no trailing bytes). Returns `undefined` on malformed input
|
|
190
|
+
* or if any bytes remain after `locktime`.
|
|
191
|
+
*
|
|
192
|
+
* **PSBT-only motivation:** Bitcoin ABC’s PSBT input key `0x00` (`PSBT_IN_UTXO`)
|
|
193
|
+
* stores **either** a full previous transaction (BIP 174 “non-witness UTXO”) **or**
|
|
194
|
+
* a compact `CTxOut` (amount + `scriptPubKey`). Callers must disambiguate. Plain
|
|
195
|
+
* {@link Tx.deser} reads a tx from the start of `data` but **does not** require
|
|
196
|
+
* `data.length` to match the serialized length — leftover bytes are ignored, so
|
|
197
|
+
* you cannot use it to prove “this blob is solely a full tx.” This helper is
|
|
198
|
+
* used from PSBT (`resolveWitnessFromKey00` in `psbt.ts`): if `tryDeserExact`
|
|
199
|
+
* succeeds (and the txid matches), treat as non-witness UTXO; otherwise decode as
|
|
200
|
+
* `CTxOut`-shaped bytes.
|
|
201
|
+
*/
|
|
202
|
+
public static tryDeserExact(data: Uint8Array): Tx | undefined {
|
|
203
|
+
try {
|
|
204
|
+
const bytes = new Bytes(data);
|
|
205
|
+
const version = bytes.readU32();
|
|
206
|
+
const numInputs = readVarSize(bytes);
|
|
207
|
+
const inputs: TxInput[] = [];
|
|
208
|
+
for (let i = 0; i < numInputs; ++i) {
|
|
209
|
+
const txid = bytes.readBytes(32);
|
|
210
|
+
const outIdx = bytes.readU32();
|
|
211
|
+
const script = Script.readWithSize(bytes);
|
|
212
|
+
const sequence = bytes.readU32();
|
|
213
|
+
inputs.push({
|
|
214
|
+
prevOut: { txid, outIdx },
|
|
215
|
+
script,
|
|
216
|
+
sequence,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
const numOutputs = readVarSize(bytes);
|
|
220
|
+
const outputs: TxOutput[] = [];
|
|
221
|
+
for (let i = 0; i < numOutputs; ++i) {
|
|
222
|
+
outputs.push(readTxOutput(bytes));
|
|
223
|
+
}
|
|
224
|
+
const locktime = bytes.readU32();
|
|
225
|
+
if (bytes.idx !== data.length) {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
return new Tx({ version, inputs, outputs, locktime });
|
|
229
|
+
} catch {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Add a signature to a partially-signed multisig input.
|
|
236
|
+
* Verifies the signature against the sighash for each pubkey in the
|
|
237
|
+
* redeem/output script and merges with existing signatures (which pubkey
|
|
238
|
+
* signed is inferred from verification).
|
|
239
|
+
*/
|
|
240
|
+
public addMultisigSignature(params: {
|
|
241
|
+
inputIdx: number;
|
|
242
|
+
signature: Uint8Array;
|
|
243
|
+
signData: SignData;
|
|
244
|
+
ecc?: Ecc;
|
|
245
|
+
}): Tx {
|
|
246
|
+
const { inputIdx, signature, signData } = params;
|
|
247
|
+
const ecc = params.ecc ?? new Ecc();
|
|
248
|
+
const input = this.inputs[inputIdx];
|
|
249
|
+
if (!input.script || input.script.bytecode.length === 0) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
`Input ${inputIdx} has no scriptSig to add signature to`,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
const isBare =
|
|
255
|
+
signData.outputScript !== undefined &&
|
|
256
|
+
signData.redeemScript === undefined;
|
|
257
|
+
const parsed = isBare
|
|
258
|
+
? input.script.parseBareMultisigSpend(signData.outputScript!)
|
|
259
|
+
: input.script.parseP2shMultisigSpend();
|
|
260
|
+
const txWithSignData = new Tx({
|
|
261
|
+
version: this.version,
|
|
262
|
+
inputs: this.inputs.map((inp, i) =>
|
|
263
|
+
i === inputIdx
|
|
264
|
+
? { ...copyTxInput(inp), signData }
|
|
265
|
+
: copyTxInput(inp),
|
|
266
|
+
),
|
|
267
|
+
outputs: this.outputs,
|
|
268
|
+
locktime: this.locktime,
|
|
269
|
+
});
|
|
270
|
+
const unsignedTx = UnsignedTx.fromTx(txWithSignData);
|
|
271
|
+
const inputAt = unsignedTx.inputAt(inputIdx);
|
|
272
|
+
const sigHashType =
|
|
273
|
+
SigHashType.fromInt(
|
|
274
|
+
(signature[signature.length - 1] ?? 0) & 0xff,
|
|
275
|
+
) ?? ALL_BIP143;
|
|
276
|
+
const preimage = inputAt.sigHashPreimage(sigHashType);
|
|
277
|
+
const sighash = sha256d(preimage.bytes);
|
|
278
|
+
const sigWithoutFlag = signature.slice(0, -1);
|
|
279
|
+
|
|
280
|
+
let pubkeyIndex = -1;
|
|
281
|
+
if (parsed.isSchnorr) {
|
|
282
|
+
for (let i = 0; i < parsed.pubkeys.length; i++) {
|
|
283
|
+
try {
|
|
284
|
+
ecc.schnorrVerify(
|
|
285
|
+
sigWithoutFlag,
|
|
286
|
+
sighash,
|
|
287
|
+
parsed.pubkeys[i]!,
|
|
288
|
+
);
|
|
289
|
+
pubkeyIndex = i;
|
|
290
|
+
break;
|
|
291
|
+
} catch {
|
|
292
|
+
/* try next pubkey */
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (pubkeyIndex < 0) {
|
|
296
|
+
throw new Error(
|
|
297
|
+
'Schnorr signature does not verify for any pubkey in the multisig script',
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
for (let i = 0; i < parsed.pubkeys.length; i++) {
|
|
302
|
+
try {
|
|
303
|
+
ecc.ecdsaVerify(
|
|
304
|
+
sigWithoutFlag,
|
|
305
|
+
sighash,
|
|
306
|
+
parsed.pubkeys[i]!,
|
|
307
|
+
);
|
|
308
|
+
pubkeyIndex = i;
|
|
309
|
+
break;
|
|
310
|
+
} catch {
|
|
311
|
+
/* try next pubkey */
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (pubkeyIndex < 0) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
'ECDSA signature does not verify for any pubkey in the multisig script',
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const sigsByPubkey: (Uint8Array | undefined)[] = Array(
|
|
322
|
+
parsed.pubkeys.length,
|
|
323
|
+
).fill(undefined);
|
|
324
|
+
|
|
325
|
+
if (parsed.isSchnorr) {
|
|
326
|
+
const indices = parsed.pubkeyIndices!;
|
|
327
|
+
const sortedIndices = [...indices].sort((a, b) => a - b);
|
|
328
|
+
for (let i = 0; i < parsed.signatures.length; i++) {
|
|
329
|
+
const sig = parsed.signatures[i];
|
|
330
|
+
if (sig !== undefined && i < sortedIndices.length) {
|
|
331
|
+
sigsByPubkey[sortedIndices[i]!] = sig;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
for (const sig of parsed.signatures) {
|
|
336
|
+
if (sig === undefined) continue;
|
|
337
|
+
const sigNoFlag = sig.slice(0, -1);
|
|
338
|
+
for (let i = 0; i < parsed.pubkeys.length; i++) {
|
|
339
|
+
try {
|
|
340
|
+
ecc.ecdsaVerify(sigNoFlag, sighash, parsed.pubkeys[i]!);
|
|
341
|
+
sigsByPubkey[i] = sig;
|
|
342
|
+
break;
|
|
343
|
+
} catch {
|
|
344
|
+
/* try next pubkey */
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
sigsByPubkey[pubkeyIndex] = signature;
|
|
350
|
+
|
|
351
|
+
const nonNullSigs = sigsByPubkey.filter(
|
|
352
|
+
(s): s is Uint8Array => s !== undefined,
|
|
353
|
+
);
|
|
354
|
+
const sigsForScript =
|
|
355
|
+
nonNullSigs.length >= parsed.numSignatures
|
|
356
|
+
? nonNullSigs.slice(0, parsed.numSignatures)
|
|
357
|
+
: [
|
|
358
|
+
...nonNullSigs,
|
|
359
|
+
...Array(parsed.numSignatures - nonNullSigs.length).fill(
|
|
360
|
+
undefined,
|
|
361
|
+
),
|
|
362
|
+
];
|
|
363
|
+
|
|
364
|
+
const redeemScript: Script | undefined =
|
|
365
|
+
!isBare && 'redeemScript' in parsed
|
|
366
|
+
? (parsed as { redeemScript: Script }).redeemScript
|
|
367
|
+
: undefined;
|
|
368
|
+
const newScriptSig = parsed.isSchnorr
|
|
369
|
+
? (() => {
|
|
370
|
+
const signerIndices = new Set<number>();
|
|
371
|
+
for (
|
|
372
|
+
let i = 0;
|
|
373
|
+
i < parsed.pubkeys.length &&
|
|
374
|
+
signerIndices.size < parsed.numSignatures;
|
|
375
|
+
i++
|
|
376
|
+
) {
|
|
377
|
+
if (sigsByPubkey[i] !== undefined) signerIndices.add(i);
|
|
378
|
+
}
|
|
379
|
+
return isBare
|
|
380
|
+
? Script.multisigSpend({
|
|
381
|
+
signatures: sigsForScript,
|
|
382
|
+
pubkeyIndices: signerIndices,
|
|
383
|
+
numPubkeys: parsed.numPubkeys,
|
|
384
|
+
})
|
|
385
|
+
: Script.multisigSpend({
|
|
386
|
+
signatures: sigsForScript,
|
|
387
|
+
redeemScript,
|
|
388
|
+
pubkeyIndices: signerIndices,
|
|
389
|
+
});
|
|
390
|
+
})()
|
|
391
|
+
: Script.multisigSpend({
|
|
392
|
+
signatures: sigsForScript,
|
|
393
|
+
redeemScript,
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const newInputs = this.inputs.map((inp, i) =>
|
|
397
|
+
i === inputIdx
|
|
398
|
+
? { ...copyTxInput(inp), script: newScriptSig }
|
|
399
|
+
: copyTxInput(inp),
|
|
400
|
+
);
|
|
401
|
+
return new Tx({
|
|
402
|
+
version: this.version,
|
|
403
|
+
inputs: newInputs,
|
|
404
|
+
outputs: this.outputs,
|
|
405
|
+
locktime: this.locktime,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Like {@link addMultisigSignature}, but computes the signature from a
|
|
411
|
+
* secret key: BIP143 preimage (or legacy if `sigHashType` is legacy),
|
|
412
|
+
* Schnorr for Schnorr-format multisig spends and ECDSA otherwise.
|
|
413
|
+
*/
|
|
414
|
+
public addMultisigSignatureFromKey(params: {
|
|
415
|
+
inputIdx: number;
|
|
416
|
+
sk: Uint8Array;
|
|
417
|
+
signData: SignData;
|
|
418
|
+
/** Defaults to {@link ALL_BIP143}. */
|
|
419
|
+
sigHashType?: SigHashType;
|
|
420
|
+
ecc?: Ecc;
|
|
421
|
+
}): Tx {
|
|
422
|
+
const sigHashType = params.sigHashType ?? ALL_BIP143;
|
|
423
|
+
const ecc = params.ecc ?? new Ecc();
|
|
424
|
+
const { inputIdx, sk, signData } = params;
|
|
425
|
+
const input = this.inputs[inputIdx];
|
|
426
|
+
if (!input.script || input.script.bytecode.length === 0) {
|
|
427
|
+
throw new Error(
|
|
428
|
+
`Input ${inputIdx} has no scriptSig to add signature to`,
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
const isBare =
|
|
432
|
+
signData.outputScript !== undefined &&
|
|
433
|
+
signData.redeemScript === undefined;
|
|
434
|
+
const parsed = isBare
|
|
435
|
+
? input.script.parseBareMultisigSpend(signData.outputScript!)
|
|
436
|
+
: input.script.parseP2shMultisigSpend();
|
|
437
|
+
const txWithSignData = new Tx({
|
|
438
|
+
version: this.version,
|
|
439
|
+
inputs: this.inputs.map((inp, i) =>
|
|
440
|
+
i === inputIdx
|
|
441
|
+
? { ...copyTxInput(inp), signData }
|
|
442
|
+
: copyTxInput(inp),
|
|
443
|
+
),
|
|
444
|
+
outputs: this.outputs,
|
|
445
|
+
locktime: this.locktime,
|
|
446
|
+
});
|
|
447
|
+
const unsignedTx = UnsignedTx.fromTx(txWithSignData);
|
|
448
|
+
const preimage = unsignedTx
|
|
449
|
+
.inputAt(inputIdx)
|
|
450
|
+
.sigHashPreimage(sigHashType);
|
|
451
|
+
const sighash = sha256d(preimage.bytes);
|
|
452
|
+
const sig = parsed.isSchnorr
|
|
453
|
+
? ecc.schnorrSign(sk, sighash)
|
|
454
|
+
: ecc.ecdsaSign(sk, sighash);
|
|
455
|
+
const signature = flagSignature(sig, sigHashType);
|
|
456
|
+
return this.addMultisigSignature({
|
|
457
|
+
inputIdx,
|
|
458
|
+
signature,
|
|
459
|
+
signData,
|
|
460
|
+
ecc,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Whether every **multisig** input (identified from `signData`) has enough
|
|
466
|
+
* signatures in its scriptSig. Non-multisig inputs are ignored.
|
|
467
|
+
*
|
|
468
|
+
* If the transaction has **no** multisig inputs, this returns `true` (there
|
|
469
|
+
* is nothing multisig-specific left to satisfy). That can look surprising on
|
|
470
|
+
* a non-multisig or otherwise incomplete tx; this helper is **not** a
|
|
471
|
+
* broadcast-readiness check. Call sites are expected to use it only in
|
|
472
|
+
* multisig / PSBT flows where the question is specifically whether multisig
|
|
473
|
+
* inputs still need more signatures (including mixed txs: non-multisig
|
|
474
|
+
* inputs are finalized elsewhere).
|
|
475
|
+
*/
|
|
476
|
+
public isFullySignedMultisig(): boolean {
|
|
477
|
+
for (let i = 0; i < this.inputs.length; i++) {
|
|
478
|
+
const input = this.inputs[i];
|
|
479
|
+
const multisigScript =
|
|
480
|
+
input.signData?.redeemScript !== undefined
|
|
481
|
+
? input.signData.redeemScript
|
|
482
|
+
: input.signData?.outputScript?.isMultisig()
|
|
483
|
+
? input.signData!.outputScript
|
|
484
|
+
: undefined;
|
|
485
|
+
if (multisigScript === undefined) {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (!input.script || input.script.bytecode.length === 0) {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
try {
|
|
492
|
+
const parsed =
|
|
493
|
+
input.signData?.redeemScript === undefined
|
|
494
|
+
? input.script.parseBareMultisigSpend(multisigScript)
|
|
495
|
+
: input.script.parseP2shMultisigSpend();
|
|
496
|
+
const sigCount = parsed.signatures.filter(
|
|
497
|
+
s => s !== undefined,
|
|
498
|
+
).length;
|
|
499
|
+
if (sigCount < parsed.numSignatures) {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
} catch {
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
178
508
|
}
|
|
179
509
|
|
|
180
510
|
export function readTxOutput(bytes: Bytes): TxOutput {
|