@scure/btc-signer 2.2.0 → 2.4.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 +196 -37
- package/index.d.ts +4 -4
- package/index.js +3 -4
- package/musig2.d.ts +28 -9
- package/musig2.js +46 -18
- package/net.d.ts +355 -0
- package/net.js +880 -0
- package/p2p.d.ts +0 -1
- package/p2p.js +23 -7
- package/package.json +18 -21
- package/payment.d.ts +18 -11
- package/payment.js +203 -58
- package/psbt.d.ts +680 -10
- package/psbt.js +204 -43
- package/script.d.ts +1 -2
- package/script.js +71 -59
- package/src/_type_test.ts +83 -0
- package/src/index.ts +4 -2
- package/src/musig2.ts +71 -19
- package/src/net.ts +1111 -0
- package/src/p2p.ts +22 -6
- package/src/payment.ts +233 -70
- package/src/psbt.ts +228 -39
- package/src/script.ts +57 -35
- package/src/transaction.ts +869 -168
- package/src/utils.ts +92 -4
- package/src/utxo.ts +223 -113
- package/transaction.d.ts +40 -7
- package/transaction.js +745 -151
- package/utils.d.ts +45 -2
- package/utils.js +80 -5
- package/utxo.d.ts +192 -2
- package/utxo.js +200 -99
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/musig2.d.ts.map +0 -1
- package/musig2.js.map +0 -1
- package/p2p.d.ts.map +0 -1
- package/p2p.js.map +0 -1
- package/payment.d.ts.map +0 -1
- package/payment.js.map +0 -1
- package/psbt.d.ts.map +0 -1
- package/psbt.js.map +0 -1
- package/script.d.ts.map +0 -1
- package/script.js.map +0 -1
- package/transaction.d.ts.map +0 -1
- package/transaction.js.map +0 -1
- package/utils.d.ts.map +0 -1
- package/utils.js.map +0 -1
- package/utxo.d.ts.map +0 -1
- package/utxo.js.map +0 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { secp256k1 as secp } from '@noble/curves/secp256k1.js';
|
|
2
|
+
import { hex } from '@scure/base';
|
|
3
|
+
import * as btc from './index.ts';
|
|
4
|
+
import type { TxOpts, Unknowns } from './index.ts';
|
|
5
|
+
import type { TArg } from './utils.ts';
|
|
6
|
+
|
|
7
|
+
const unknownPolicy: Unknowns = 'strip';
|
|
8
|
+
const extensionOpts: TxOpts = { unknown: unknownPolicy, proprietary: 'strict' };
|
|
9
|
+
new btc.Transaction(extensionOpts);
|
|
10
|
+
|
|
11
|
+
declare const combineOpts: TArg<TxOpts>;
|
|
12
|
+
btc.PSBTCombine([], combineOpts);
|
|
13
|
+
declare const first: btc.Transaction;
|
|
14
|
+
declare const second: btc.Transaction;
|
|
15
|
+
// Direct combination retains main's receiver-options API; byte-only PSBTCombine owns policy opts.
|
|
16
|
+
// @ts-expect-error Transaction.combine does not accept operation-specific options.
|
|
17
|
+
first.combine(second, combineOpts);
|
|
18
|
+
|
|
19
|
+
const privKey1 = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
20
|
+
const P1 = secp.getPublicKey(privKey1, true);
|
|
21
|
+
|
|
22
|
+
const wpkh = btc.p2wpkh(P1);
|
|
23
|
+
|
|
24
|
+
const tx = new btc.Transaction();
|
|
25
|
+
|
|
26
|
+
// Basic input test
|
|
27
|
+
tx.addInput({
|
|
28
|
+
txid: hex.decode('0af50a00a22f74ece24c12cd667c290d3a35d48124a69f4082700589172a3aa2'),
|
|
29
|
+
index: 0,
|
|
30
|
+
...wpkh,
|
|
31
|
+
finalScriptSig: Uint8Array.of(),
|
|
32
|
+
sequence: 1,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Doesn't force any fields on input addition (only on sign)
|
|
36
|
+
tx.addInput({
|
|
37
|
+
sequence: 1,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
tx.updateInput(0, {
|
|
41
|
+
sequence: 1,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const nonWitnessUtxo =
|
|
45
|
+
'0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000';
|
|
46
|
+
const nonWitnessUtxoB = hex.decode(nonWitnessUtxo);
|
|
47
|
+
|
|
48
|
+
tx.updateInput(0, { nonWitnessUtxo: nonWitnessUtxo });
|
|
49
|
+
tx.updateInput(0, { nonWitnessUtxo: nonWitnessUtxoB });
|
|
50
|
+
tx.addInput({
|
|
51
|
+
txid: hex.decode('0af50a00a22f74ece24c12cd667c290d3a35d48124a69f4082700589172a3aa2'),
|
|
52
|
+
index: 0,
|
|
53
|
+
nonWitnessUtxo: nonWitnessUtxo,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
tx.addInput({
|
|
57
|
+
txid: hex.decode('0af50a00a22f74ece24c12cd667c290d3a35d48124a69f4082700589172a3aa2'),
|
|
58
|
+
index: 0,
|
|
59
|
+
nonWitnessUtxo: nonWitnessUtxoB,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Should fail!
|
|
63
|
+
// tx.updateInput(0, {
|
|
64
|
+
// nonWitnessUtxo: 1,
|
|
65
|
+
// });
|
|
66
|
+
// Outputs
|
|
67
|
+
tx.addOutput({ amount: BigInt(123) });
|
|
68
|
+
// should fail
|
|
69
|
+
// tx.updateOutput(0, { amount: '1' });
|
|
70
|
+
// tx.updateOutput(0, { amount: 1 });
|
|
71
|
+
// should fail
|
|
72
|
+
// tx.addOutput({ amount: '123' });
|
|
73
|
+
// tx.addOutput({ amount: 123 });
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < tx.inputsLength; i++) {
|
|
76
|
+
// @ts-ignore
|
|
77
|
+
console.log('I', tx.getInput(i));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < tx.outputsLength; i++) {
|
|
81
|
+
// @ts-ignore
|
|
82
|
+
console.log('O', tx.getOutput(i));
|
|
83
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -25,9 +25,10 @@ export {
|
|
|
25
25
|
} from './script.ts';
|
|
26
26
|
export type { ScriptType } from './script.ts';
|
|
27
27
|
export { getInputType, Transaction } from './transaction.ts';
|
|
28
|
-
export {
|
|
28
|
+
export type { TxOpts, Unknowns } from './transaction.ts';
|
|
29
|
+
export { NETWORK, TAPROOT_UNSPENDABLE_KEY, TEST_NETWORK, taprootNumsKey } from './utils.ts';
|
|
29
30
|
export type { TArg, TRet } from './utils.ts';
|
|
30
|
-
export { selectUTXO } from './utxo.ts';
|
|
31
|
+
export { filterTaproot, selectUTXO } from './utxo.ts';
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* Small collection of commonly used utility exports.
|
|
@@ -62,6 +63,7 @@ export {
|
|
|
62
63
|
Address,
|
|
63
64
|
combinations,
|
|
64
65
|
getAddress,
|
|
66
|
+
MAX_COMBINATIONS,
|
|
65
67
|
OutScript,
|
|
66
68
|
sortedMultisig,
|
|
67
69
|
taprootListToTree,
|
package/src/musig2.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
|
|
2
|
+
import type { WeierstrassPoint } from '@noble/curves/abstract/weierstrass.js';
|
|
2
3
|
import { aInRange, concatBytes, equalBytes, numberToBytesBE } from '@noble/curves/utils.js';
|
|
3
4
|
import { abytes, anumber, randomBytes } from '@noble/hashes/utils.js';
|
|
4
5
|
import * as P from 'micro-packed';
|
|
5
|
-
import { compareBytes, hasEven, type TArg, type TRet } from './utils.ts';
|
|
6
|
+
import { compareBytes, hasEven, type TArg, type TRet, validateObject } from './utils.ts';
|
|
6
7
|
|
|
7
8
|
/*
|
|
8
9
|
MuSig2. This is not the full protocol: only an implementation of primitives from BIP-327.
|
|
@@ -10,9 +11,16 @@ The implementation can be used to create own protocol,
|
|
|
10
11
|
but you need to implement nonce/partial signatures exchange yourself.
|
|
11
12
|
Someday BIP-373 will be more "implementable" and we can use this from PSBT.
|
|
12
13
|
|
|
14
|
+
SECURITY: A secret nonce MUST be used for exactly one partial signature. Session.sign() zeroes
|
|
15
|
+
only the Uint8Array instance passed to it; copies, serialized values, database records, and process
|
|
16
|
+
snapshots are not erased. Reusing the same nonce scalars in distinct sessions can reveal the
|
|
17
|
+
signer's secret key. Stateful integrations must keep one authoritative nonce record and atomically
|
|
18
|
+
consume it before releasing a partial signature.
|
|
19
|
+
|
|
13
20
|
Links:
|
|
14
21
|
- https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki#user-content-Test_Vectors_and_Reference_Code
|
|
15
|
-
- https://github.com/bitcoin/bips/blob/master/bip-0373.mediawiki (PSBT
|
|
22
|
+
- https://github.com/bitcoin/bips/blob/master/bip-0373.mediawiki (PSBT MuSig2): psbt.ts supports
|
|
23
|
+
its transport fields and vectors, but this module does not orchestrate the signing protocol.
|
|
16
24
|
- https://github.com/bitcoin/bips/blob/master/bip-0327/reference.py
|
|
17
25
|
*/
|
|
18
26
|
// Types
|
|
@@ -20,7 +28,10 @@ Links:
|
|
|
20
28
|
export type Nonces = {
|
|
21
29
|
/** Public nonce that gets shared with the other participants. */
|
|
22
30
|
public: Uint8Array;
|
|
23
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Secret nonce that stays local until partial signing finishes. It MUST be consumed exactly once;
|
|
33
|
+
* never retain a copy that could be loaded for another signing session.
|
|
34
|
+
*/
|
|
24
35
|
secret: Uint8Array;
|
|
25
36
|
};
|
|
26
37
|
/**
|
|
@@ -33,6 +44,15 @@ export type DetNonce = {
|
|
|
33
44
|
/** Partial signature produced after combining all participant data. */
|
|
34
45
|
partialSig: Uint8Array;
|
|
35
46
|
};
|
|
47
|
+
/** MuSig2 key aggregation context used by signing sessions. */
|
|
48
|
+
export type KeyAggregate = {
|
|
49
|
+
/** Aggregate public key before x-only export. */
|
|
50
|
+
aggPublicKey: WeierstrassPoint<bigint>;
|
|
51
|
+
/** Accumulated sign from x-only tweaks. */
|
|
52
|
+
gAcc: bigint;
|
|
53
|
+
/** Accumulated tweak scalar. */
|
|
54
|
+
tweakAcc: bigint;
|
|
55
|
+
};
|
|
36
56
|
/**
|
|
37
57
|
* Represents an error indicating an invalid contribution from a signer.
|
|
38
58
|
* This allows pointing out which participant is malicious and what specifically is wrong.
|
|
@@ -75,6 +95,9 @@ const PUBKEY_LEN = /* @__PURE__ */ (() => secp256k1.lengths.publicKey!)();
|
|
|
75
95
|
// BIP327 uses bytes(33, 0) both as cbytes_ext(Point.ZERO) for infinity and as GetSecondKey's
|
|
76
96
|
// "no second distinct key" sentinel, so this all-zero compressed slot is intentionally out-of-band.
|
|
77
97
|
const ZERO = /* @__PURE__ */ new Uint8Array(PUBKEY_LEN); // Compressed zero point
|
|
98
|
+
// Be friendly to bad ECMAScript parsers by not using bigint literals.
|
|
99
|
+
// prettier-ignore
|
|
100
|
+
const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1);
|
|
78
101
|
|
|
79
102
|
// Encoding
|
|
80
103
|
// TODO: re-use in PSBT?
|
|
@@ -89,7 +112,7 @@ const compressed = /* @__PURE__ */ (() =>
|
|
|
89
112
|
// nonzero scalars in [1, n); tweak scalars use different validation because 0 is allowed there.
|
|
90
113
|
const scalar = /* @__PURE__ */ (() =>
|
|
91
114
|
P.validate(P.U256BE, (n) => {
|
|
92
|
-
aInRange('n', n,
|
|
115
|
+
aInRange('n', n, _1n, Fn.ORDER);
|
|
93
116
|
return n;
|
|
94
117
|
}))();
|
|
95
118
|
// Shared for both per-signer pubnonce bytes and aggregate aggnonce bytes. Because it accepts
|
|
@@ -216,7 +239,7 @@ function keyAggCoeffInternal(
|
|
|
216
239
|
// pk2 is the all-zero sentinel from GetSecondKey, so every real signer key still hashes.
|
|
217
240
|
abytes(publicKey1, PUBKEY_LEN);
|
|
218
241
|
abytes(publicKey2, PUBKEY_LEN);
|
|
219
|
-
if (equalBytes(publicKey1, publicKey2)) return
|
|
242
|
+
if (equalBytes(publicKey1, publicKey2)) return _1n;
|
|
220
243
|
return taggedInt('KeyAgg coefficient', L, publicKey1);
|
|
221
244
|
}
|
|
222
245
|
|
|
@@ -248,7 +271,7 @@ export function keyAggregate(
|
|
|
248
271
|
publicKeys: TArg<Uint8Array[]>,
|
|
249
272
|
tweaks: TArg<Uint8Array[]> = [],
|
|
250
273
|
isXonly: boolean[] = []
|
|
251
|
-
) {
|
|
274
|
+
): KeyAggregate {
|
|
252
275
|
// BIP327 KeyAgg inputs require `0 < u < 2^32`, and ApplyTweak consumes a one-for-one
|
|
253
276
|
// list of boolean tweak modes; callers should enforce that public contract here.
|
|
254
277
|
abytesArray(publicKeys, PUBKEY_LEN);
|
|
@@ -270,6 +293,10 @@ export function keyAggregate(
|
|
|
270
293
|
}
|
|
271
294
|
aggPublicKey = aggPublicKey.add(Pi.multiply(keyAggCoeffInternal(publicKeys[i], pk2, L)));
|
|
272
295
|
}
|
|
296
|
+
// BIP327 KeyAggInternal: "Fail if is_infinite(Q)". Computationally unreachable for
|
|
297
|
+
// hash-derived coefficients, but the spec mandates the explicit check before tweaking.
|
|
298
|
+
if (isZero(aggPublicKey))
|
|
299
|
+
throw new Error('keyAggregate: aggregate public key cannot be infinity');
|
|
273
300
|
let gAcc = Fn.ONE;
|
|
274
301
|
let tweakAcc = Fn.ZERO;
|
|
275
302
|
// Apply tweaks
|
|
@@ -302,6 +329,9 @@ export function keyAggregate(
|
|
|
302
329
|
* ```
|
|
303
330
|
*/
|
|
304
331
|
export function keyAggExport(ctx: ReturnType<typeof keyAggregate>): TRet<Uint8Array> {
|
|
332
|
+
validateObject(ctx as Record<string, any>, {}, {}, 'ctx');
|
|
333
|
+
if (!(ctx.aggPublicKey instanceof Point))
|
|
334
|
+
throw new TypeError('"ctx.aggPublicKey" expected point, got type=' + typeof ctx.aggPublicKey);
|
|
305
335
|
// BIP327 GetXonlyPubkey returns xbytes(Q), so this is the 32-byte x-only aggregate key
|
|
306
336
|
// instead of the 33-byte compressed SEC1 form.
|
|
307
337
|
return pointToBytes(ctx.aggPublicKey) as TRet<Uint8Array>;
|
|
@@ -342,6 +372,12 @@ const nonceHash = (
|
|
|
342
372
|
|
|
343
373
|
/**
|
|
344
374
|
* Generates a nonce pair (public and secret) for MuSig2 signing.
|
|
375
|
+
*
|
|
376
|
+
* SECURITY: The returned secret nonce MUST be used for exactly one partial signature. Keep one
|
|
377
|
+
* authoritative copy and atomically consume it when calling {@link Session.sign}. That method
|
|
378
|
+
* zeroes only the exact `Uint8Array` passed to it; clones, serialized values, database records, and
|
|
379
|
+
* snapshots remain live. Reusing a secret nonce in distinct sessions can reveal the secret key.
|
|
380
|
+
*
|
|
345
381
|
* @param publicKey - individual public key of the signer
|
|
346
382
|
* @param secretKey - optional secret key, mixed in to blind the randomness source
|
|
347
383
|
* @param aggPublicKey - aggregate public key of all signers
|
|
@@ -502,6 +538,7 @@ export class Session {
|
|
|
502
538
|
tweaks: Uint8Array[] = [],
|
|
503
539
|
isXonly: boolean[] = []
|
|
504
540
|
) {
|
|
541
|
+
abytes(aggNonce, 66);
|
|
505
542
|
abytesArray(publicKeys, 33);
|
|
506
543
|
abytesArray(tweaks, 32);
|
|
507
544
|
aXonly(isXonly);
|
|
@@ -520,7 +557,10 @@ export class Session {
|
|
|
520
557
|
this.gAcc = gAcc;
|
|
521
558
|
this.tweakAcc = tweakAcc;
|
|
522
559
|
this.b = taggedInt('MuSig/noncecoef', aggNonce, pointToBytes(aggPublicKey), msg);
|
|
523
|
-
|
|
560
|
+
// b and the nonce points are public session values, so the faster variable-time
|
|
561
|
+
// multiplication is safe here; it also matches the reference point_mul, which
|
|
562
|
+
// maps a (negligible-probability) zero coefficient to infinity instead of failing.
|
|
563
|
+
const R = R1.add(R2.multiplyUnsafe(this.b));
|
|
524
564
|
this.R = isZero(R) ? Point.BASE : R;
|
|
525
565
|
this.e = taggedInt('BIP0340/challenge', pointToBytes(this.R), pointToBytes(aggPublicKey), msg);
|
|
526
566
|
this.tweaks = tweaks.map((t) => Uint8Array.from(t));
|
|
@@ -555,20 +595,28 @@ export class Session {
|
|
|
555
595
|
// BIP327 PartialSigVerifyInternal: `Let s = int(psig); fail if s >= n`, so s=0 must stay
|
|
556
596
|
// in the public verification equation and return false on mismatch instead of throwing.
|
|
557
597
|
const { R1, R2 } = PubNonce.decode(publicNonce);
|
|
558
|
-
|
|
598
|
+
// Verification only handles public data (nonces, pubkeys, hash-derived scalars),
|
|
599
|
+
// so the faster variable-time multiplications are safe here; they also match the
|
|
600
|
+
// reference point_mul, which maps zero scalars to infinity instead of failing.
|
|
601
|
+
const Re_s_ = R1.add(R2.multiplyUnsafe(b));
|
|
559
602
|
const Re_s = hasEven(R.y) ? Re_s_ : Re_s_.negate();
|
|
560
603
|
const P = Point.fromBytes(publicKey);
|
|
561
604
|
const a = this.getSessionKeyAggCoeff(P);
|
|
562
|
-
const g = Fn.mul(evenScalar(Q,
|
|
605
|
+
const g = Fn.mul(evenScalar(Q, _1n), gAcc);
|
|
563
606
|
const left = Point.BASE.multiplyUnsafe(s);
|
|
564
|
-
const right = Re_s.add(P.
|
|
607
|
+
const right = Re_s.add(P.multiplyUnsafe(Fn.mul(e, Fn.mul(a, g))));
|
|
565
608
|
return left.equals(right);
|
|
566
609
|
}
|
|
567
610
|
|
|
568
611
|
/**
|
|
569
612
|
* Generates a partial signature for a given message, secret nonce,
|
|
570
613
|
* secret key, and session context.
|
|
571
|
-
*
|
|
614
|
+
*
|
|
615
|
+
* SECURITY: `secretNonce` MUST be used exactly once. This method zeroes the first 64 bytes of the
|
|
616
|
+
* supplied array, including when later validation fails, but cannot erase copies or persisted
|
|
617
|
+
* representations. Reusing those nonce scalars in a distinct session can reveal the secret key.
|
|
618
|
+
*
|
|
619
|
+
* @param secretNonce - sole authoritative secret-nonce buffer for this signing session
|
|
572
620
|
* @param secret - secret key of the signer
|
|
573
621
|
* @param fastSign - if `true`, skip the self-verification pass
|
|
574
622
|
* @returns The partial signature (Uint8Array).
|
|
@@ -587,7 +635,7 @@ export class Session {
|
|
|
587
635
|
// Modifying input arguments is pretty bad.
|
|
588
636
|
secretNonce.fill(0, 0, 64);
|
|
589
637
|
if (!Fn.isValid(k1_)) throw new Error('wrong k1');
|
|
590
|
-
if (!Fn.isValid(k2_)) throw new Error('wrong
|
|
638
|
+
if (!Fn.isValid(k2_)) throw new Error('wrong k2');
|
|
591
639
|
const k1 = evenScalar(R, k1_);
|
|
592
640
|
const k2 = evenScalar(R, k2_);
|
|
593
641
|
const d_ = Fn.fromBytes(secret);
|
|
@@ -596,7 +644,7 @@ export class Session {
|
|
|
596
644
|
const pk = P.toBytes(true);
|
|
597
645
|
if (!equalBytes(pk, originalPk)) throw new Error('Public key does not match nonceGen argument');
|
|
598
646
|
const a = this.getSessionKeyAggCoeff(P);
|
|
599
|
-
const g = evenScalar(Q,
|
|
647
|
+
const g = evenScalar(Q, _1n);
|
|
600
648
|
const d = Fn.mul(g, Fn.mul(gAcc, d_));
|
|
601
649
|
/// k1 + (b*k2) + (e*a*d)
|
|
602
650
|
const s = Fn.add(k1, Fn.add(Fn.mul(b, k2), Fn.mul(e, Fn.mul(a, d))));
|
|
@@ -643,24 +691,28 @@ export class Session {
|
|
|
643
691
|
}
|
|
644
692
|
/**
|
|
645
693
|
* Aggregates partial signatures from multiple signers into a single final signature.
|
|
646
|
-
* @param partialSigs - partial
|
|
694
|
+
* @param partialSigs - exactly one positional partial signature per session participant
|
|
647
695
|
* @returns The final aggregate signature (Uint8Array).
|
|
648
696
|
* @throws If the input is invalid, such as wrong array sizes or malformed
|
|
649
697
|
* signatures. {@link Error}
|
|
650
698
|
*/
|
|
651
699
|
partialSigAgg(partialSigs: TArg<Uint8Array[]>): TRet<Uint8Array> {
|
|
652
700
|
abytesArray(partialSigs, 32);
|
|
653
|
-
// BIP327 PartialSigAgg
|
|
654
|
-
//
|
|
655
|
-
if (partialSigs.length
|
|
701
|
+
// BIP327 PartialSigAgg consumes psig_1..u for the same u signers in session_ctx. Accepting
|
|
702
|
+
// fewer or more scalars would return a signature-shaped value for a different equation.
|
|
703
|
+
if (partialSigs.length !== this.publicKeys.length)
|
|
704
|
+
throw new RangeError(
|
|
705
|
+
`partialSigs.length=${partialSigs.length} must equal ` +
|
|
706
|
+
`participant count=${this.publicKeys.length}`
|
|
707
|
+
);
|
|
656
708
|
const { Q, tweakAcc, R, e } = this;
|
|
657
|
-
let s =
|
|
709
|
+
let s = _0n;
|
|
658
710
|
for (let i = 0; i < partialSigs.length; i++) {
|
|
659
711
|
const si = Fn.fromBytes(partialSigs[i], true);
|
|
660
712
|
if (!Fn.isValid(si)) throw new InvalidContributionErr(i, 'psig');
|
|
661
713
|
s = Fn.add(s, si);
|
|
662
714
|
}
|
|
663
|
-
const g = evenScalar(Q,
|
|
715
|
+
const g = evenScalar(Q, _1n);
|
|
664
716
|
s = Fn.add(s, Fn.mul(e, Fn.mul(g, tweakAcc))); // s + e * g * tweakAcc
|
|
665
717
|
return concatBytes(pointToBytes(R), Fn.toBytes(s)) as TRet<Uint8Array>;
|
|
666
718
|
}
|