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/README.md +1 -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/psbt.d.ts +54 -4
- package/dist/psbt.d.ts.map +1 -1
- package/dist/psbt.js +256 -7
- package/dist/psbt.js.map +1 -1
- 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 +10 -2
- package/dist/signatories.d.ts.map +1 -1
- package/dist/signatories.js +156 -1
- package/dist/signatories.js.map +1 -1
- package/dist/tx.d.ts +40 -0
- package/dist/tx.d.ts.map +1 -1
- package/dist/tx.js +223 -0
- package/dist/tx.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/psbt.ts +328 -10
- package/src/script.ts +25 -1
- package/src/signatories.ts +214 -3
- package/src/tx.ts +279 -0
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
|
|
@@ -226,6 +230,281 @@ export class Tx {
|
|
|
226
230
|
return undefined;
|
|
227
231
|
}
|
|
228
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
|
+
}
|
|
229
508
|
}
|
|
230
509
|
|
|
231
510
|
export function readTxOutput(bytes: Bytes): TxOutput {
|