@scure/btc-signer 2.2.0 → 2.3.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 +148 -28
- package/index.d.ts +0 -1
- package/index.js +0 -1
- package/musig2.d.ts +11 -6
- package/musig2.js +28 -12
- package/net.d.ts +355 -0
- package/net.js +875 -0
- package/p2p.d.ts +0 -1
- package/p2p.js +23 -7
- package/package.json +15 -19
- package/payment.d.ts +2 -6
- package/payment.js +85 -31
- package/psbt.d.ts +0 -1
- package/psbt.js +18 -6
- package/script.d.ts +1 -2
- package/script.js +71 -59
- package/src/_type_test.ts +69 -0
- package/src/musig2.ts +39 -12
- package/src/net.ts +1106 -0
- package/src/p2p.ts +22 -6
- package/src/payment.ts +88 -38
- package/src/psbt.ts +19 -5
- package/src/script.ts +57 -35
- package/src/transaction.ts +81 -34
- package/src/utils.ts +68 -2
- package/src/utxo.ts +39 -43
- package/transaction.d.ts +0 -1
- package/transaction.js +81 -31
- package/utils.d.ts +30 -1
- package/utils.js +59 -3
- package/utxo.d.ts +0 -1
- package/utxo.js +36 -32
- 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
package/script.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import * as P from 'micro-packed';
|
|
2
|
-
import { isBytes, reverseObject } from "./utils.js";
|
|
2
|
+
import { aarray, abytes, isBytes, reverseObject, } from "./utils.js";
|
|
3
3
|
/**
|
|
4
4
|
* Maximum byte size allowed for a single pushed script element.
|
|
5
5
|
* BIP 342 keeps this 520-byte stack-element limit even though tapscript removes
|
|
6
6
|
* the old 10,000-byte overall script-size cap.
|
|
7
7
|
*/
|
|
8
8
|
export const MAX_SCRIPT_BYTE_LENGTH = 520;
|
|
9
|
+
// Be friendly to bad ECMAScript parsers by not using bigint literals.
|
|
10
|
+
// prettier-ignore
|
|
11
|
+
const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1), _2n = /* @__PURE__ */ BigInt(2), _8n = /* @__PURE__ */ BigInt(8);
|
|
12
|
+
const U8_MAX = /* @__PURE__ */ BigInt(0xff);
|
|
13
|
+
const COMPACT_DIRECT_MAX = /* @__PURE__ */ BigInt(0xfc);
|
|
9
14
|
// prettier-ignore
|
|
10
15
|
/**
|
|
11
16
|
* Bitcoin Script opcode table.
|
|
@@ -75,13 +80,13 @@ export const OPNames = /* @__PURE__ */ (() => Object.freeze(reverseObject(OP)))(
|
|
|
75
80
|
export function ScriptNum(bytesLimit = 6, forceMinimal = false) {
|
|
76
81
|
return P.wrap({
|
|
77
82
|
encodeStream: (w, value) => {
|
|
78
|
-
if (value ===
|
|
83
|
+
if (value === _0n)
|
|
79
84
|
return;
|
|
80
85
|
const neg = value < 0;
|
|
81
86
|
const val = BigInt(value);
|
|
82
87
|
const nums = [];
|
|
83
|
-
for (let abs = neg ? -val : val; abs; abs >>=
|
|
84
|
-
nums.push(Number(abs &
|
|
88
|
+
for (let abs = neg ? -val : val; abs; abs >>= _8n)
|
|
89
|
+
nums.push(Number(abs & U8_MAX));
|
|
85
90
|
if (nums[nums.length - 1] >= 0x80)
|
|
86
91
|
nums.push(neg ? 0x80 : 0);
|
|
87
92
|
else if (neg)
|
|
@@ -93,24 +98,23 @@ export function ScriptNum(bytesLimit = 6, forceMinimal = false) {
|
|
|
93
98
|
if (len > bytesLimit)
|
|
94
99
|
throw new Error(`ScriptNum: number (${len}) bigger than limit=${bytesLimit}`);
|
|
95
100
|
if (len === 0)
|
|
96
|
-
return
|
|
101
|
+
return _0n;
|
|
102
|
+
// Read the payload once instead of peeking for the minimality check and
|
|
103
|
+
// then re-reading it byte-by-byte through the Reader.
|
|
104
|
+
const data = r.bytes(len);
|
|
97
105
|
if (forceMinimal) {
|
|
98
|
-
const data = r.bytes(len, true);
|
|
99
106
|
// MSB is zero (without sign bit) -> not minimally encoded
|
|
100
|
-
if ((data[
|
|
107
|
+
if ((data[len - 1] & 0x7f) === 0) {
|
|
101
108
|
// exception
|
|
102
|
-
if (len <= 1 || (data[
|
|
109
|
+
if (len <= 1 || (data[len - 2] & 0x80) === 0)
|
|
103
110
|
throw new Error('Non-minimally encoded ScriptNum');
|
|
104
111
|
}
|
|
105
112
|
}
|
|
106
|
-
let
|
|
107
|
-
let
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
res
|
|
111
|
-
}
|
|
112
|
-
if (last >= 0x80) {
|
|
113
|
-
res &= (2n ** BigInt(len * 8) - 1n) >> 1n;
|
|
113
|
+
let res = _0n;
|
|
114
|
+
for (let i = 0; i < len; ++i)
|
|
115
|
+
res |= BigInt(data[i]) << (_8n * BigInt(i));
|
|
116
|
+
if (data[len - 1] >= 0x80) {
|
|
117
|
+
res &= (_2n ** BigInt(len * 8) - _1n) >> _1n;
|
|
114
118
|
res = -res;
|
|
115
119
|
}
|
|
116
120
|
return res;
|
|
@@ -137,7 +141,9 @@ export function OpToNum(op, bytesLimit = 4, forceMinimal = true) {
|
|
|
137
141
|
if (isBytes(op)) {
|
|
138
142
|
try {
|
|
139
143
|
const val = ScriptNum(bytesLimit, forceMinimal).decode(op);
|
|
140
|
-
|
|
144
|
+
// Symmetric safe-integer bound: large negative values would otherwise
|
|
145
|
+
// coerce through Number() with silent precision loss.
|
|
146
|
+
if (val > Number.MAX_SAFE_INTEGER || val < -Number.MAX_SAFE_INTEGER)
|
|
141
147
|
return;
|
|
142
148
|
return Number(val);
|
|
143
149
|
}
|
|
@@ -192,11 +198,15 @@ export const scriptPushLen = (op, read) => {
|
|
|
192
198
|
*/
|
|
193
199
|
export const Script = /* @__PURE__ */ (() => Object.freeze(P.wrap({
|
|
194
200
|
encodeStream: (w, value) => {
|
|
201
|
+
aarray(value, 'value');
|
|
195
202
|
for (let o of value) {
|
|
196
203
|
if (typeof o === 'string') {
|
|
197
|
-
|
|
204
|
+
const op = OP[o];
|
|
205
|
+
// OP is a plain object, so inherited Object.prototype keys ('toString',
|
|
206
|
+
// 'constructor', ...) are not opcodes and must be rejected here too.
|
|
207
|
+
if (typeof op !== 'number')
|
|
198
208
|
throw new Error(`Unknown opcode=${o}`);
|
|
199
|
-
w.byte(
|
|
209
|
+
w.byte(op);
|
|
200
210
|
continue;
|
|
201
211
|
}
|
|
202
212
|
else if (typeof o === 'number') {
|
|
@@ -218,8 +228,7 @@ export const Script = /* @__PURE__ */ (() => Object.freeze(P.wrap({
|
|
|
218
228
|
// Encode big numbers
|
|
219
229
|
if (typeof o === 'number')
|
|
220
230
|
o = ScriptNum().encode(BigInt(o));
|
|
221
|
-
|
|
222
|
-
throw new Error(`Wrong Script OP=${o} (${typeof o})`);
|
|
231
|
+
abytes(o, undefined, 'value');
|
|
223
232
|
// Bytes
|
|
224
233
|
const len = o.length;
|
|
225
234
|
if (len < OP.PUSHDATA1)
|
|
@@ -269,13 +278,6 @@ export const Script = /* @__PURE__ */ (() => Object.freeze(P.wrap({
|
|
|
269
278
|
return out;
|
|
270
279
|
},
|
|
271
280
|
})))();
|
|
272
|
-
// BTC specific variable length integer encoding
|
|
273
|
-
// https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
|
|
274
|
-
const CSLimits = {
|
|
275
|
-
0xfd: [0xfd, 2, 253n, 65535n],
|
|
276
|
-
0xfe: [0xfe, 4, 65536n, 4294967295n],
|
|
277
|
-
0xff: [0xff, 8, 4294967296n, 18446744073709551615n],
|
|
278
|
-
};
|
|
279
281
|
/**
|
|
280
282
|
* Bitcoin CompactSize integer coder.
|
|
281
283
|
* @example
|
|
@@ -284,37 +286,48 @@ const CSLimits = {
|
|
|
284
286
|
* CompactSize.encode(1n);
|
|
285
287
|
* ```
|
|
286
288
|
*/
|
|
287
|
-
export const CompactSize = /* @__PURE__ */ (() =>
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
289
|
+
export const CompactSize = /* @__PURE__ */ (() => {
|
|
290
|
+
// BTC specific variable length integer encoding
|
|
291
|
+
// https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
|
|
292
|
+
const limits = {
|
|
293
|
+
0xfd: [0xfd, 2, BigInt(0xfd), BigInt(0xffff)],
|
|
294
|
+
0xfe: [0xfe, 4, BigInt(0x10000), BigInt(0xffffffff)],
|
|
295
|
+
0xff: [0xff, 8, BigInt(0x100000000), BigInt('0xffffffffffffffff')],
|
|
296
|
+
};
|
|
297
|
+
// Hoisted: Object.values() would otherwise allocate a fresh array on every encode.
|
|
298
|
+
const limitsList = Object.values(limits);
|
|
299
|
+
return Object.freeze(P.wrap({
|
|
300
|
+
encodeStream: (w, value) => {
|
|
301
|
+
if (typeof value === 'number')
|
|
302
|
+
value = BigInt(value);
|
|
303
|
+
if (_0n <= value && value <= COMPACT_DIRECT_MAX)
|
|
304
|
+
return w.byte(Number(value));
|
|
305
|
+
for (const [flag, bytes, start, stop] of limitsList) {
|
|
306
|
+
if (start > value || value > stop)
|
|
307
|
+
continue;
|
|
308
|
+
w.byte(flag);
|
|
309
|
+
for (let i = 0; i < bytes; i++)
|
|
310
|
+
w.byte(Number((value >> (_8n * BigInt(i))) & U8_MAX));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
throw w.err(`VarInt too big: ${value}`);
|
|
314
|
+
},
|
|
315
|
+
decodeStream: (r) => {
|
|
316
|
+
const b0 = r.byte();
|
|
317
|
+
if (b0 <= 0xfc)
|
|
318
|
+
return BigInt(b0);
|
|
319
|
+
const [_, bytes, start] = limits[b0];
|
|
320
|
+
let num = _0n;
|
|
297
321
|
for (let i = 0; i < bytes; i++)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const [_, bytes, start] = CSLimits[b0];
|
|
308
|
-
let num = 0n;
|
|
309
|
-
for (let i = 0; i < bytes; i++)
|
|
310
|
-
num |= BigInt(r.byte()) << (8n * BigInt(i));
|
|
311
|
-
// BIP 152 / BIP 174: CompactSize fields must use the shortest encoding,
|
|
312
|
-
// so wider prefixes for smaller values are rejected here.
|
|
313
|
-
if (num < start)
|
|
314
|
-
throw r.err(`Wrong CompactSize(${8 * bytes})`);
|
|
315
|
-
return num;
|
|
316
|
-
},
|
|
317
|
-
})))();
|
|
322
|
+
num |= BigInt(r.byte()) << (_8n * BigInt(i));
|
|
323
|
+
// BIP 152 / BIP 174: CompactSize fields must use the shortest encoding,
|
|
324
|
+
// so wider prefixes for smaller values are rejected here.
|
|
325
|
+
if (num < start)
|
|
326
|
+
throw r.err(`Wrong CompactSize(${8 * bytes})`);
|
|
327
|
+
return num;
|
|
328
|
+
},
|
|
329
|
+
}));
|
|
330
|
+
})();
|
|
318
331
|
// Same thing, but in number instead of bigint. Checks for safe integer inside
|
|
319
332
|
/**
|
|
320
333
|
* CompactSize coder that decodes into JavaScript numbers.
|
|
@@ -470,4 +483,3 @@ export const RawOldTx = /* @__PURE__ */ (() => Object.freeze(P.struct({
|
|
|
470
483
|
outputs: BTCArray(RawOutput),
|
|
471
484
|
lockTime: P.U32LE,
|
|
472
485
|
})))();
|
|
473
|
-
//# sourceMappingURL=script.js.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { secp256k1 as secp } from '@noble/curves/secp256k1.js';
|
|
2
|
+
import { hex } from '@scure/base';
|
|
3
|
+
import * as btc from './index.ts';
|
|
4
|
+
|
|
5
|
+
const privKey1 = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
6
|
+
const P1 = secp.getPublicKey(privKey1, true);
|
|
7
|
+
|
|
8
|
+
const wpkh = btc.p2wpkh(P1);
|
|
9
|
+
|
|
10
|
+
const tx = new btc.Transaction();
|
|
11
|
+
|
|
12
|
+
// Basic input test
|
|
13
|
+
tx.addInput({
|
|
14
|
+
txid: hex.decode('0af50a00a22f74ece24c12cd667c290d3a35d48124a69f4082700589172a3aa2'),
|
|
15
|
+
index: 0,
|
|
16
|
+
...wpkh,
|
|
17
|
+
finalScriptSig: Uint8Array.of(),
|
|
18
|
+
sequence: 1,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Doesn't force any fields on input addition (only on sign)
|
|
22
|
+
tx.addInput({
|
|
23
|
+
sequence: 1,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
tx.updateInput(0, {
|
|
27
|
+
sequence: 1,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const nonWitnessUtxo =
|
|
31
|
+
'0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000';
|
|
32
|
+
const nonWitnessUtxoB = hex.decode(nonWitnessUtxo);
|
|
33
|
+
|
|
34
|
+
tx.updateInput(0, { nonWitnessUtxo: nonWitnessUtxo });
|
|
35
|
+
tx.updateInput(0, { nonWitnessUtxo: nonWitnessUtxoB });
|
|
36
|
+
tx.addInput({
|
|
37
|
+
txid: hex.decode('0af50a00a22f74ece24c12cd667c290d3a35d48124a69f4082700589172a3aa2'),
|
|
38
|
+
index: 0,
|
|
39
|
+
nonWitnessUtxo: nonWitnessUtxo,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
tx.addInput({
|
|
43
|
+
txid: hex.decode('0af50a00a22f74ece24c12cd667c290d3a35d48124a69f4082700589172a3aa2'),
|
|
44
|
+
index: 0,
|
|
45
|
+
nonWitnessUtxo: nonWitnessUtxoB,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Should fail!
|
|
49
|
+
// tx.updateInput(0, {
|
|
50
|
+
// nonWitnessUtxo: 1,
|
|
51
|
+
// });
|
|
52
|
+
// Outputs
|
|
53
|
+
tx.addOutput({ amount: BigInt(123) });
|
|
54
|
+
// should fail
|
|
55
|
+
// tx.updateOutput(0, { amount: '1' });
|
|
56
|
+
// tx.updateOutput(0, { amount: 1 });
|
|
57
|
+
// should fail
|
|
58
|
+
// tx.addOutput({ amount: '123' });
|
|
59
|
+
// tx.addOutput({ amount: 123 });
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < tx.inputsLength; i++) {
|
|
62
|
+
// @ts-ignore
|
|
63
|
+
console.log('I', tx.getInput(i));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < tx.outputsLength; i++) {
|
|
67
|
+
// @ts-ignore
|
|
68
|
+
console.log('O', tx.getOutput(i));
|
|
69
|
+
}
|
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.
|
|
@@ -33,6 +34,15 @@ export type DetNonce = {
|
|
|
33
34
|
/** Partial signature produced after combining all participant data. */
|
|
34
35
|
partialSig: Uint8Array;
|
|
35
36
|
};
|
|
37
|
+
/** MuSig2 key aggregation context used by signing sessions. */
|
|
38
|
+
export type KeyAggregate = {
|
|
39
|
+
/** Aggregate public key before x-only export. */
|
|
40
|
+
aggPublicKey: WeierstrassPoint<bigint>;
|
|
41
|
+
/** Accumulated sign from x-only tweaks. */
|
|
42
|
+
gAcc: bigint;
|
|
43
|
+
/** Accumulated tweak scalar. */
|
|
44
|
+
tweakAcc: bigint;
|
|
45
|
+
};
|
|
36
46
|
/**
|
|
37
47
|
* Represents an error indicating an invalid contribution from a signer.
|
|
38
48
|
* This allows pointing out which participant is malicious and what specifically is wrong.
|
|
@@ -75,6 +85,9 @@ const PUBKEY_LEN = /* @__PURE__ */ (() => secp256k1.lengths.publicKey!)();
|
|
|
75
85
|
// BIP327 uses bytes(33, 0) both as cbytes_ext(Point.ZERO) for infinity and as GetSecondKey's
|
|
76
86
|
// "no second distinct key" sentinel, so this all-zero compressed slot is intentionally out-of-band.
|
|
77
87
|
const ZERO = /* @__PURE__ */ new Uint8Array(PUBKEY_LEN); // Compressed zero point
|
|
88
|
+
// Be friendly to bad ECMAScript parsers by not using bigint literals.
|
|
89
|
+
// prettier-ignore
|
|
90
|
+
const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1);
|
|
78
91
|
|
|
79
92
|
// Encoding
|
|
80
93
|
// TODO: re-use in PSBT?
|
|
@@ -89,7 +102,7 @@ const compressed = /* @__PURE__ */ (() =>
|
|
|
89
102
|
// nonzero scalars in [1, n); tweak scalars use different validation because 0 is allowed there.
|
|
90
103
|
const scalar = /* @__PURE__ */ (() =>
|
|
91
104
|
P.validate(P.U256BE, (n) => {
|
|
92
|
-
aInRange('n', n,
|
|
105
|
+
aInRange('n', n, _1n, Fn.ORDER);
|
|
93
106
|
return n;
|
|
94
107
|
}))();
|
|
95
108
|
// Shared for both per-signer pubnonce bytes and aggregate aggnonce bytes. Because it accepts
|
|
@@ -216,7 +229,7 @@ function keyAggCoeffInternal(
|
|
|
216
229
|
// pk2 is the all-zero sentinel from GetSecondKey, so every real signer key still hashes.
|
|
217
230
|
abytes(publicKey1, PUBKEY_LEN);
|
|
218
231
|
abytes(publicKey2, PUBKEY_LEN);
|
|
219
|
-
if (equalBytes(publicKey1, publicKey2)) return
|
|
232
|
+
if (equalBytes(publicKey1, publicKey2)) return _1n;
|
|
220
233
|
return taggedInt('KeyAgg coefficient', L, publicKey1);
|
|
221
234
|
}
|
|
222
235
|
|
|
@@ -248,7 +261,7 @@ export function keyAggregate(
|
|
|
248
261
|
publicKeys: TArg<Uint8Array[]>,
|
|
249
262
|
tweaks: TArg<Uint8Array[]> = [],
|
|
250
263
|
isXonly: boolean[] = []
|
|
251
|
-
) {
|
|
264
|
+
): KeyAggregate {
|
|
252
265
|
// BIP327 KeyAgg inputs require `0 < u < 2^32`, and ApplyTweak consumes a one-for-one
|
|
253
266
|
// list of boolean tweak modes; callers should enforce that public contract here.
|
|
254
267
|
abytesArray(publicKeys, PUBKEY_LEN);
|
|
@@ -270,6 +283,10 @@ export function keyAggregate(
|
|
|
270
283
|
}
|
|
271
284
|
aggPublicKey = aggPublicKey.add(Pi.multiply(keyAggCoeffInternal(publicKeys[i], pk2, L)));
|
|
272
285
|
}
|
|
286
|
+
// BIP327 KeyAggInternal: "Fail if is_infinite(Q)". Computationally unreachable for
|
|
287
|
+
// hash-derived coefficients, but the spec mandates the explicit check before tweaking.
|
|
288
|
+
if (isZero(aggPublicKey))
|
|
289
|
+
throw new Error('keyAggregate: aggregate public key cannot be infinity');
|
|
273
290
|
let gAcc = Fn.ONE;
|
|
274
291
|
let tweakAcc = Fn.ZERO;
|
|
275
292
|
// Apply tweaks
|
|
@@ -302,6 +319,9 @@ export function keyAggregate(
|
|
|
302
319
|
* ```
|
|
303
320
|
*/
|
|
304
321
|
export function keyAggExport(ctx: ReturnType<typeof keyAggregate>): TRet<Uint8Array> {
|
|
322
|
+
validateObject(ctx as Record<string, any>, {}, {}, 'ctx');
|
|
323
|
+
if (!(ctx.aggPublicKey instanceof Point))
|
|
324
|
+
throw new TypeError('"ctx.aggPublicKey" expected point, got type=' + typeof ctx.aggPublicKey);
|
|
305
325
|
// BIP327 GetXonlyPubkey returns xbytes(Q), so this is the 32-byte x-only aggregate key
|
|
306
326
|
// instead of the 33-byte compressed SEC1 form.
|
|
307
327
|
return pointToBytes(ctx.aggPublicKey) as TRet<Uint8Array>;
|
|
@@ -502,6 +522,7 @@ export class Session {
|
|
|
502
522
|
tweaks: Uint8Array[] = [],
|
|
503
523
|
isXonly: boolean[] = []
|
|
504
524
|
) {
|
|
525
|
+
abytes(aggNonce, 66);
|
|
505
526
|
abytesArray(publicKeys, 33);
|
|
506
527
|
abytesArray(tweaks, 32);
|
|
507
528
|
aXonly(isXonly);
|
|
@@ -520,7 +541,10 @@ export class Session {
|
|
|
520
541
|
this.gAcc = gAcc;
|
|
521
542
|
this.tweakAcc = tweakAcc;
|
|
522
543
|
this.b = taggedInt('MuSig/noncecoef', aggNonce, pointToBytes(aggPublicKey), msg);
|
|
523
|
-
|
|
544
|
+
// b and the nonce points are public session values, so the faster variable-time
|
|
545
|
+
// multiplication is safe here; it also matches the reference point_mul, which
|
|
546
|
+
// maps a (negligible-probability) zero coefficient to infinity instead of failing.
|
|
547
|
+
const R = R1.add(R2.multiplyUnsafe(this.b));
|
|
524
548
|
this.R = isZero(R) ? Point.BASE : R;
|
|
525
549
|
this.e = taggedInt('BIP0340/challenge', pointToBytes(this.R), pointToBytes(aggPublicKey), msg);
|
|
526
550
|
this.tweaks = tweaks.map((t) => Uint8Array.from(t));
|
|
@@ -555,13 +579,16 @@ export class Session {
|
|
|
555
579
|
// BIP327 PartialSigVerifyInternal: `Let s = int(psig); fail if s >= n`, so s=0 must stay
|
|
556
580
|
// in the public verification equation and return false on mismatch instead of throwing.
|
|
557
581
|
const { R1, R2 } = PubNonce.decode(publicNonce);
|
|
558
|
-
|
|
582
|
+
// Verification only handles public data (nonces, pubkeys, hash-derived scalars),
|
|
583
|
+
// so the faster variable-time multiplications are safe here; they also match the
|
|
584
|
+
// reference point_mul, which maps zero scalars to infinity instead of failing.
|
|
585
|
+
const Re_s_ = R1.add(R2.multiplyUnsafe(b));
|
|
559
586
|
const Re_s = hasEven(R.y) ? Re_s_ : Re_s_.negate();
|
|
560
587
|
const P = Point.fromBytes(publicKey);
|
|
561
588
|
const a = this.getSessionKeyAggCoeff(P);
|
|
562
|
-
const g = Fn.mul(evenScalar(Q,
|
|
589
|
+
const g = Fn.mul(evenScalar(Q, _1n), gAcc);
|
|
563
590
|
const left = Point.BASE.multiplyUnsafe(s);
|
|
564
|
-
const right = Re_s.add(P.
|
|
591
|
+
const right = Re_s.add(P.multiplyUnsafe(Fn.mul(e, Fn.mul(a, g))));
|
|
565
592
|
return left.equals(right);
|
|
566
593
|
}
|
|
567
594
|
|
|
@@ -587,7 +614,7 @@ export class Session {
|
|
|
587
614
|
// Modifying input arguments is pretty bad.
|
|
588
615
|
secretNonce.fill(0, 0, 64);
|
|
589
616
|
if (!Fn.isValid(k1_)) throw new Error('wrong k1');
|
|
590
|
-
if (!Fn.isValid(k2_)) throw new Error('wrong
|
|
617
|
+
if (!Fn.isValid(k2_)) throw new Error('wrong k2');
|
|
591
618
|
const k1 = evenScalar(R, k1_);
|
|
592
619
|
const k2 = evenScalar(R, k2_);
|
|
593
620
|
const d_ = Fn.fromBytes(secret);
|
|
@@ -596,7 +623,7 @@ export class Session {
|
|
|
596
623
|
const pk = P.toBytes(true);
|
|
597
624
|
if (!equalBytes(pk, originalPk)) throw new Error('Public key does not match nonceGen argument');
|
|
598
625
|
const a = this.getSessionKeyAggCoeff(P);
|
|
599
|
-
const g = evenScalar(Q,
|
|
626
|
+
const g = evenScalar(Q, _1n);
|
|
600
627
|
const d = Fn.mul(g, Fn.mul(gAcc, d_));
|
|
601
628
|
/// k1 + (b*k2) + (e*a*d)
|
|
602
629
|
const s = Fn.add(k1, Fn.add(Fn.mul(b, k2), Fn.mul(e, Fn.mul(a, d))));
|
|
@@ -654,13 +681,13 @@ export class Session {
|
|
|
654
681
|
// [] is not a valid aggregate-signature input even though the sum starts from zero.
|
|
655
682
|
if (partialSigs.length < 1) throw new RangeError('partialSigs.length must be >= 1');
|
|
656
683
|
const { Q, tweakAcc, R, e } = this;
|
|
657
|
-
let s =
|
|
684
|
+
let s = _0n;
|
|
658
685
|
for (let i = 0; i < partialSigs.length; i++) {
|
|
659
686
|
const si = Fn.fromBytes(partialSigs[i], true);
|
|
660
687
|
if (!Fn.isValid(si)) throw new InvalidContributionErr(i, 'psig');
|
|
661
688
|
s = Fn.add(s, si);
|
|
662
689
|
}
|
|
663
|
-
const g = evenScalar(Q,
|
|
690
|
+
const g = evenScalar(Q, _1n);
|
|
664
691
|
s = Fn.add(s, Fn.mul(e, Fn.mul(g, tweakAcc))); // s + e * g * tweakAcc
|
|
665
692
|
return concatBytes(pointToBytes(R), Fn.toBytes(s)) as TRet<Uint8Array>;
|
|
666
693
|
}
|