@noble/curves 2.0.1 → 2.2.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.
Files changed (110) hide show
  1. package/README.md +214 -122
  2. package/abstract/bls.d.ts +299 -16
  3. package/abstract/bls.d.ts.map +1 -1
  4. package/abstract/bls.js +82 -22
  5. package/abstract/bls.js.map +1 -1
  6. package/abstract/curve.d.ts +274 -27
  7. package/abstract/curve.d.ts.map +1 -1
  8. package/abstract/curve.js +177 -23
  9. package/abstract/curve.js.map +1 -1
  10. package/abstract/edwards.d.ts +166 -30
  11. package/abstract/edwards.d.ts.map +1 -1
  12. package/abstract/edwards.js +221 -86
  13. package/abstract/edwards.js.map +1 -1
  14. package/abstract/fft.d.ts +322 -10
  15. package/abstract/fft.d.ts.map +1 -1
  16. package/abstract/fft.js +154 -12
  17. package/abstract/fft.js.map +1 -1
  18. package/abstract/frost.d.ts +293 -0
  19. package/abstract/frost.d.ts.map +1 -0
  20. package/abstract/frost.js +704 -0
  21. package/abstract/frost.js.map +1 -0
  22. package/abstract/hash-to-curve.d.ts +173 -24
  23. package/abstract/hash-to-curve.d.ts.map +1 -1
  24. package/abstract/hash-to-curve.js +170 -31
  25. package/abstract/hash-to-curve.js.map +1 -1
  26. package/abstract/modular.d.ts +429 -37
  27. package/abstract/modular.d.ts.map +1 -1
  28. package/abstract/modular.js +414 -119
  29. package/abstract/modular.js.map +1 -1
  30. package/abstract/montgomery.d.ts +83 -12
  31. package/abstract/montgomery.d.ts.map +1 -1
  32. package/abstract/montgomery.js +32 -7
  33. package/abstract/montgomery.js.map +1 -1
  34. package/abstract/oprf.d.ts +164 -91
  35. package/abstract/oprf.d.ts.map +1 -1
  36. package/abstract/oprf.js +88 -29
  37. package/abstract/oprf.js.map +1 -1
  38. package/abstract/poseidon.d.ts +138 -7
  39. package/abstract/poseidon.d.ts.map +1 -1
  40. package/abstract/poseidon.js +178 -15
  41. package/abstract/poseidon.js.map +1 -1
  42. package/abstract/tower.d.ts +122 -3
  43. package/abstract/tower.d.ts.map +1 -1
  44. package/abstract/tower.js +323 -139
  45. package/abstract/tower.js.map +1 -1
  46. package/abstract/weierstrass.d.ts +339 -76
  47. package/abstract/weierstrass.d.ts.map +1 -1
  48. package/abstract/weierstrass.js +395 -205
  49. package/abstract/weierstrass.js.map +1 -1
  50. package/bls12-381.d.ts +16 -2
  51. package/bls12-381.d.ts.map +1 -1
  52. package/bls12-381.js +199 -209
  53. package/bls12-381.js.map +1 -1
  54. package/bn254.d.ts +11 -2
  55. package/bn254.d.ts.map +1 -1
  56. package/bn254.js +93 -38
  57. package/bn254.js.map +1 -1
  58. package/ed25519.d.ts +125 -14
  59. package/ed25519.d.ts.map +1 -1
  60. package/ed25519.js +202 -40
  61. package/ed25519.js.map +1 -1
  62. package/ed448.d.ts +108 -14
  63. package/ed448.d.ts.map +1 -1
  64. package/ed448.js +194 -42
  65. package/ed448.js.map +1 -1
  66. package/index.js +7 -1
  67. package/index.js.map +1 -1
  68. package/misc.d.ts +106 -7
  69. package/misc.d.ts.map +1 -1
  70. package/misc.js +141 -32
  71. package/misc.js.map +1 -1
  72. package/nist.d.ts +112 -11
  73. package/nist.d.ts.map +1 -1
  74. package/nist.js +139 -17
  75. package/nist.js.map +1 -1
  76. package/package.json +11 -6
  77. package/secp256k1.d.ts +92 -15
  78. package/secp256k1.d.ts.map +1 -1
  79. package/secp256k1.js +211 -28
  80. package/secp256k1.js.map +1 -1
  81. package/src/abstract/bls.ts +350 -67
  82. package/src/abstract/curve.ts +327 -44
  83. package/src/abstract/edwards.ts +367 -143
  84. package/src/abstract/fft.ts +369 -36
  85. package/src/abstract/frost.ts +1092 -0
  86. package/src/abstract/hash-to-curve.ts +255 -56
  87. package/src/abstract/modular.ts +591 -144
  88. package/src/abstract/montgomery.ts +114 -30
  89. package/src/abstract/oprf.ts +383 -194
  90. package/src/abstract/poseidon.ts +235 -35
  91. package/src/abstract/tower.ts +428 -159
  92. package/src/abstract/weierstrass.ts +710 -312
  93. package/src/bls12-381.ts +239 -236
  94. package/src/bn254.ts +107 -46
  95. package/src/ed25519.ts +227 -55
  96. package/src/ed448.ts +227 -57
  97. package/src/index.ts +7 -1
  98. package/src/misc.ts +154 -35
  99. package/src/nist.ts +143 -20
  100. package/src/secp256k1.ts +284 -41
  101. package/src/utils.ts +583 -81
  102. package/src/webcrypto.ts +302 -73
  103. package/utils.d.ts +457 -24
  104. package/utils.d.ts.map +1 -1
  105. package/utils.js +410 -53
  106. package/utils.js.map +1 -1
  107. package/webcrypto.d.ts +167 -25
  108. package/webcrypto.d.ts.map +1 -1
  109. package/webcrypto.js +165 -58
  110. package/webcrypto.js.map +1 -1
package/src/secp256k1.ts CHANGED
@@ -9,6 +9,13 @@
9
9
  import { sha256 } from '@noble/hashes/sha2.js';
10
10
  import { randomBytes } from '@noble/hashes/utils.js';
11
11
  import { createKeygen, type CurveLengths } from './abstract/curve.ts';
12
+ import {
13
+ createFROST,
14
+ type FROST,
15
+ type FrostPublic,
16
+ type FrostSecret,
17
+ type Nonces,
18
+ } from './abstract/frost.ts';
12
19
  import { createHasher, type H2CHasher, isogenyMap } from './abstract/hash-to-curve.ts';
13
20
  import { Field, mapHashToField, pow2 } from './abstract/modular.ts';
14
21
  import {
@@ -21,7 +28,14 @@ import {
21
28
  type WeierstrassOpts,
22
29
  type WeierstrassPointCons,
23
30
  } from './abstract/weierstrass.ts';
24
- import { abytes, asciiToBytes, bytesToNumberBE, concatBytes } from './utils.ts';
31
+ import {
32
+ abytes,
33
+ asciiToBytes,
34
+ bytesToNumberBE,
35
+ concatBytes,
36
+ type TArg,
37
+ type TRet,
38
+ } from './utils.ts';
25
39
 
26
40
  // Seems like generator was produced from some seed:
27
41
  // `Pointk1.BASE.multiply(Pointk1.Fn.inv(2n, N)).toAffine().x`
@@ -88,6 +102,8 @@ const Pointk1 = /* @__PURE__ */ weierstrass(secp256k1_CURVE, {
88
102
  * pass `{ prehash: false }` to sign / verify.
89
103
  *
90
104
  * @example
105
+ * Generate one secp256k1 keypair, sign a message, and verify it.
106
+ *
91
107
  * ```js
92
108
  * import { secp256k1 } from '@noble/curves/secp256k1.js';
93
109
  * const { secretKey, publicKey } = secp256k1.keygen();
@@ -104,22 +120,24 @@ export const secp256k1: ECDSA = /* @__PURE__ */ ecdsa(Pointk1, sha256);
104
120
  // https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
105
121
  /** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
106
122
  const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};
107
- function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
123
+ // BIP-340 phrases tags as UTF-8, but all current standardized names here are 7-bit ASCII.
124
+ function taggedHash(tag: string, ...messages: TArg<Uint8Array[]>): TRet<Uint8Array> {
108
125
  let tagP = TAGGED_HASH_PREFIXES[tag];
109
126
  if (tagP === undefined) {
110
127
  const tagH = sha256(asciiToBytes(tag));
111
128
  tagP = concatBytes(tagH, tagH);
112
129
  TAGGED_HASH_PREFIXES[tag] = tagP;
113
130
  }
114
- return sha256(concatBytes(tagP, ...messages));
131
+ return sha256(concatBytes(tagP, ...messages)) as TRet<Uint8Array>;
115
132
  }
116
133
 
117
134
  // ECDSA compact points are 33-byte. Schnorr is 32: we strip first byte 0x02 or 0x03
118
- const pointToBytes = (point: PointType<bigint>) => point.toBytes(true).slice(1);
135
+ const pointToBytes = (point: TArg<PointType<bigint>>): TRet<Uint8Array> =>
136
+ point.toBytes(true).slice(1) as TRet<Uint8Array>;
119
137
  const hasEven = (y: bigint) => y % _2n === _0n;
120
138
 
121
139
  // Calculate point, scalar and bytes
122
- function schnorrGetExtPubKey(priv: Uint8Array) {
140
+ function schnorrGetExtPubKey(priv: TArg<Uint8Array>) {
123
141
  const { Fn, BASE } = Pointk1;
124
142
  const d_ = Fn.fromBytes(priv);
125
143
  const p = BASE.multiply(d_); // P = d'⋅G; 0 < d' < n check is done inside
@@ -143,52 +161,64 @@ function lift_x(x: bigint): PointType<bigint> {
143
161
  p.assertValidity();
144
162
  return p;
145
163
  }
164
+ // BIP-340 callers still need to supply canonical 32-byte inputs where required; this alias only
165
+ // parses big-endian bytes and does not enforce the fixed-width contract itself.
146
166
  const num = bytesToNumberBE;
147
- /**
148
- * Create tagged hash, convert it to bigint, reduce modulo-n.
149
- */
150
- function challenge(...args: Uint8Array[]): bigint {
167
+ /** Create tagged hash, convert it to bigint, reduce modulo-n. */
168
+ function challenge(...args: TArg<Uint8Array[]>): bigint {
151
169
  return Pointk1.Fn.create(num(taggedHash('BIP0340/challenge', ...args)));
152
170
  }
153
171
 
154
- /**
155
- * Schnorr public key is just `x` coordinate of Point as per BIP340.
156
- */
157
- function schnorrGetPublicKey(secretKey: Uint8Array): Uint8Array {
172
+ /** Schnorr public key is just `x` coordinate of Point as per BIP340. */
173
+ function schnorrGetPublicKey(secretKey: TArg<Uint8Array>): TRet<Uint8Array> {
158
174
  return schnorrGetExtPubKey(secretKey).bytes; // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G)
159
175
  }
160
176
 
161
177
  /**
162
178
  * Creates Schnorr signature as per BIP340. Verifies itself before returning anything.
163
- * auxRand is optional and is not the sole source of k generation: bad CSPRNG won't be dangerous.
179
+ * `auxRand` is optional and is not the sole source of `k` generation: bad CSPRNG output will not
180
+ * be catastrophic, but BIP-340 still recommends fresh auxiliary randomness when available to harden
181
+ * deterministic signing against side-channel and fault-injection attacks.
164
182
  */
165
183
  function schnorrSign(
166
- message: Uint8Array,
167
- secretKey: Uint8Array,
168
- auxRand: Uint8Array = randomBytes(32)
169
- ): Uint8Array {
170
- const { Fn } = Pointk1;
184
+ message: TArg<Uint8Array>,
185
+ secretKey: TArg<Uint8Array>,
186
+ auxRand: TArg<Uint8Array> = randomBytes(32)
187
+ ): TRet<Uint8Array> {
188
+ const { Fn, BASE } = Pointk1;
171
189
  const m = abytes(message, undefined, 'message');
172
190
  const { bytes: px, scalar: d } = schnorrGetExtPubKey(secretKey); // checks for isWithinCurveOrder
173
191
  const a = abytes(auxRand, 32, 'auxRand'); // Auxiliary random data a: a 32-byte array
174
- const t = Fn.toBytes(d ^ num(taggedHash('BIP0340/aux', a))); // Let t be the byte-wise xor of bytes(d) and hash/aux(a)
192
+ // Let t be the byte-wise xor of bytes(d) and hash/aux(a).
193
+ const t = Fn.toBytes(d ^ num(taggedHash('BIP0340/aux', a)));
175
194
  const rand = taggedHash('BIP0340/nonce', t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)
176
- // Let k' = int(rand) mod n. Fail if k' = 0. Let R = k'⋅G
177
- const { bytes: rx, scalar: k } = schnorrGetExtPubKey(rand);
195
+ // BIP340 defines k' = int(rand) mod n. We can't reuse schnorrGetExtPubKey(rand)
196
+ // here: that helper parses canonical secret keys and rejects rand >= n instead
197
+ // of reducing the nonce hash modulo the group order.
198
+ const k_ = Fn.create(num(rand));
199
+ // BIP-340: "Let k' = int(rand) mod n. Fail if k' = 0. Let R = k'⋅G."
200
+ if (k_ === 0n) throw new Error('sign failed: k is zero');
201
+ const p = BASE.multiply(k_); // Rejects zero; only the raw nonce hash needs reduction.
202
+ const k = hasEven(p.y) ? k_ : Fn.neg(k_);
203
+ const rx = pointToBytes(p);
178
204
  const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n.
179
205
  const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n).
180
206
  sig.set(rx, 0);
181
207
  sig.set(Fn.toBytes(Fn.create(k + e * d)), 32);
182
208
  // If Verify(bytes(P), m, sig) (see below) returns failure, abort
183
209
  if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');
184
- return sig;
210
+ return sig as TRet<Uint8Array>;
185
211
  }
186
212
 
187
213
  /**
188
214
  * Verifies Schnorr signature.
189
215
  * Will swallow errors & return false except for initial type validation of arguments.
190
216
  */
191
- function schnorrVerify(signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array): boolean {
217
+ function schnorrVerify(
218
+ signature: TArg<Uint8Array>,
219
+ message: TArg<Uint8Array>,
220
+ publicKey: TArg<Uint8Array>
221
+ ): boolean {
192
222
  const { Fp, Fn, BASE } = Pointk1;
193
223
  const sig = abytes(signature, 64, 'signature');
194
224
  const m = abytes(message, undefined, 'message');
@@ -198,9 +228,13 @@ function schnorrVerify(signature: Uint8Array, message: Uint8Array, publicKey: Ui
198
228
  const r = num(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.
199
229
  if (!Fp.isValidNot0(r)) return false;
200
230
  const s = num(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n.
231
+ // Stricter than BIP-340/libsecp256k1, which only reject s >= n. Honest signing reaches
232
+ // s = 0 only with negligible probability (k + e*d ≡ 0 mod n), so treat zero-s inputs as
233
+ // crafted edge cases and fail closed instead of carrying that extra verification surface.
201
234
  if (!Fn.isValidNot0(s)) return false;
202
235
 
203
- const e = challenge(Fn.toBytes(r), pointToBytes(P), m); // int(challenge(bytes(r)||bytes(P)||m))%n
236
+ // int(challenge(bytes(r) || bytes(P) || m)) % n
237
+ const e = challenge(Fn.toBytes(r), pointToBytes(P), m);
204
238
  // R = s⋅G - e⋅P, where -eP == (n-e)P
205
239
  const R = BASE.multiplyUnsafe(s).add(P.multiplyUnsafe(Fn.neg(e)));
206
240
  const { x, y } = R.toAffine();
@@ -212,24 +246,60 @@ function schnorrVerify(signature: Uint8Array, message: Uint8Array, publicKey: Ui
212
246
  }
213
247
  }
214
248
 
249
+ export const __TEST: { lift_x: typeof lift_x } = /* @__PURE__ */ Object.freeze({ lift_x });
250
+
251
+ /** Schnorr-specific secp256k1 API from BIP340. */
215
252
  export type SecpSchnorr = {
216
- keygen: (seed?: Uint8Array) => { secretKey: Uint8Array; publicKey: Uint8Array };
253
+ /**
254
+ * Generate one Schnorr secret/public keypair.
255
+ * @param seed - Optional seed for deterministic testing or custom randomness.
256
+ * @returns Fresh secret/public keypair.
257
+ */
258
+ keygen: (seed?: TArg<Uint8Array>) => { secretKey: TRet<Uint8Array>; publicKey: TRet<Uint8Array> };
259
+ /**
260
+ * Derive the x-only public key from a secret key.
261
+ * @param secretKey - Secret key bytes.
262
+ * @returns X-only public key bytes.
263
+ */
217
264
  getPublicKey: typeof schnorrGetPublicKey;
265
+ /**
266
+ * Create one BIP340 Schnorr signature.
267
+ * @param message - Message bytes to sign.
268
+ * @param secretKey - Secret key bytes.
269
+ * @param auxRand - Optional auxiliary randomness.
270
+ * @returns Compact Schnorr signature bytes.
271
+ */
218
272
  sign: typeof schnorrSign;
273
+ /**
274
+ * Verify one BIP340 Schnorr signature.
275
+ * @param signature - Compact signature bytes.
276
+ * @param message - Signed message bytes.
277
+ * @param publicKey - X-only public key bytes.
278
+ * @returns `true` when the signature is valid.
279
+ */
219
280
  verify: typeof schnorrVerify;
281
+ /** Underlying secp256k1 point constructor. */
220
282
  Point: WeierstrassPointCons<bigint>;
283
+ /** Helper utilities for Schnorr-specific key handling and tagged hashing. */
221
284
  utils: {
222
- randomSecretKey: (seed?: Uint8Array) => Uint8Array;
223
- pointToBytes: (point: PointType<bigint>) => Uint8Array;
285
+ /** Generate one Schnorr secret key. */
286
+ randomSecretKey: (seed?: TArg<Uint8Array>) => TRet<Uint8Array>;
287
+ /** Convert one point into its x-only BIP340 byte encoding. */
288
+ pointToBytes: (point: TArg<PointType<bigint>>) => TRet<Uint8Array>;
289
+ /** Lift one x coordinate into the unique even-Y point. */
224
290
  lift_x: typeof lift_x;
291
+ /** Compute a BIP340 tagged hash. */
225
292
  taggedHash: typeof taggedHash;
226
293
  };
294
+ /** Public byte lengths for keys, signatures, and seeds. */
227
295
  lengths: CurveLengths;
228
296
  };
229
297
  /**
230
298
  * Schnorr signatures over secp256k1.
231
- * https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
299
+ * See {@link https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki | BIP 340}.
232
300
  * @example
301
+ * Generate one BIP340 Schnorr keypair, sign a message, and verify it.
302
+ *
233
303
  * ```js
234
304
  * import { schnorr } from '@noble/curves/secp256k1.js';
235
305
  * const { secretKey, publicKey } = schnorr.keygen();
@@ -242,31 +312,34 @@ export type SecpSchnorr = {
242
312
  export const schnorr: SecpSchnorr = /* @__PURE__ */ (() => {
243
313
  const size = 32;
244
314
  const seedLength = 48;
245
- const randomSecretKey = (seed = randomBytes(seedLength)): Uint8Array => {
315
+ const randomSecretKey = (seed?: TArg<Uint8Array>): TRet<Uint8Array> => {
316
+ seed = seed === undefined ? randomBytes(seedLength) : seed;
246
317
  return mapHashToField(seed, secp256k1_CURVE.n);
247
318
  };
248
- return {
319
+ return Object.freeze({
249
320
  keygen: createKeygen(randomSecretKey, schnorrGetPublicKey),
250
321
  getPublicKey: schnorrGetPublicKey,
251
322
  sign: schnorrSign,
252
323
  verify: schnorrVerify,
253
324
  Point: Pointk1,
254
- utils: {
325
+ utils: Object.freeze({
255
326
  randomSecretKey,
256
327
  taggedHash,
257
328
  lift_x,
258
329
  pointToBytes,
259
- },
260
- lengths: {
330
+ }),
331
+ lengths: Object.freeze({
261
332
  secretKey: size,
262
333
  publicKey: size,
263
334
  publicKeyHasPrefix: false,
264
335
  signature: size * 2,
265
336
  seed: seedLength,
266
- },
267
- };
337
+ }),
338
+ });
268
339
  })();
269
340
 
341
+ // RFC 9380 Appendix E.1 3-isogeny coefficients for secp256k1, stored in ascending degree order.
342
+ // The final `1` in each denominator array is the explicit monic leading term.
270
343
  const isoMap = /* @__PURE__ */ (() =>
271
344
  isogenyMap(
272
345
  Fpk1,
@@ -300,19 +373,32 @@ const isoMap = /* @__PURE__ */ (() =>
300
373
  ],
301
374
  ].map((i) => i.map((j) => BigInt(j))) as [bigint[], bigint[], bigint[], bigint[]]
302
375
  ))();
303
- const mapSWU = /* @__PURE__ */ (() =>
304
- mapToCurveSimpleSWU(Fpk1, {
376
+ // RFC 9380 §8.7 secp256k1 E' parameters for the SWU-to-isogeny pipeline below.
377
+ let mapSWU: ((u: bigint) => { x: bigint; y: bigint }) | undefined;
378
+ const getMapSWU = () =>
379
+ mapSWU ||
380
+ (mapSWU = mapToCurveSimpleSWU(Fpk1, {
381
+ // Building the SWU sqrt-ratio helper eagerly adds noticeable `secp256k1.js` import cost, so
382
+ // defer it to first use; after that the cached mapper is reused directly.
305
383
  A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
306
384
  B: BigInt('1771'),
307
385
  Z: Fpk1.create(BigInt('-11')),
308
- }))();
386
+ }));
309
387
 
310
- /** Hashing / encoding to secp256k1 points / field. RFC 9380 methods. */
388
+ /**
389
+ * Hashing / encoding to secp256k1 points / field. RFC 9380 methods.
390
+ * @example
391
+ * Hash one message onto secp256k1.
392
+ *
393
+ * ```ts
394
+ * const point = secp256k1_hasher.hashToCurve(new TextEncoder().encode('hello noble'));
395
+ * ```
396
+ */
311
397
  export const secp256k1_hasher: H2CHasher<WeierstrassPointCons<bigint>> = /* @__PURE__ */ (() =>
312
398
  createHasher(
313
399
  Pointk1,
314
400
  (scalars: bigint[]) => {
315
- const { x, y } = mapSWU(Fpk1.create(scalars[0]));
401
+ const { x, y } = getMapSWU()(Fpk1.create(scalars[0]));
316
402
  return isoMap(x, y);
317
403
  },
318
404
  {
@@ -325,3 +411,160 @@ export const secp256k1_hasher: H2CHasher<WeierstrassPointCons<bigint>> = /* @__P
325
411
  hash: sha256,
326
412
  }
327
413
  ))();
414
+ /**
415
+ * FROST threshold signatures over secp256k1. RFC 9591.
416
+ * @example
417
+ * Create one trusted-dealer package for 2-of-3 secp256k1 signing.
418
+ *
419
+ * ```ts
420
+ * const alice = secp256k1_FROST.Identifier.derive('alice@example.com');
421
+ * const bob = secp256k1_FROST.Identifier.derive('bob@example.com');
422
+ * const carol = secp256k1_FROST.Identifier.derive('carol@example.com');
423
+ * const deal = secp256k1_FROST.trustedDealer({ min: 2, max: 3 }, [alice, bob, carol]);
424
+ * ```
425
+ */
426
+ export const secp256k1_FROST: TRet<FROST> = /* @__PURE__ */ (() =>
427
+ createFROST({
428
+ name: 'FROST-secp256k1-SHA256-v1',
429
+ Point: Pointk1,
430
+ hashToScalar: secp256k1_hasher.hashToScalar,
431
+ hash: sha256,
432
+ }))();
433
+
434
+ // Taproot utils
435
+ // `undefined` means "disable TapTweak entirely"; callers that want the BIP-341/BIP-386 empty
436
+ // merkle root must pass `new Uint8Array(0)` explicitly.
437
+ function tweak(point: PointType<bigint>, merkleRoot?: TArg<Uint8Array>): bigint {
438
+ if (merkleRoot === undefined) return _0n;
439
+ const x = pointToBytes(point);
440
+ const t = bytesToNumberBE(taggedHash('TapTweak', x, merkleRoot));
441
+ // BIP-341 taproot_tweak_pubkey/taproot_tweak_seckey: "if t >= SECP256K1_ORDER:
442
+ // raise ValueError". TapTweak must reject overflow instead of reducing modulo n.
443
+ if (!Pointk1.Fn.isValid(t)) throw new Error('invalid TapTweak hash');
444
+ return t;
445
+ }
446
+ function frostPubToEvenY(pub: TArg<FrostPublic>): TRet<FrostPublic> {
447
+ const VK = Pointk1.fromBytes(pub.commitments[0]);
448
+ // Keep aliasing on the already-even path so wrapper callers can skip unnecessary cloning.
449
+ if (hasEven(VK.y)) return pub as TRet<FrostPublic>;
450
+ return {
451
+ signers: { min: pub.signers.min, max: pub.signers.max },
452
+ commitments: pub.commitments.map((i) => Pointk1.fromBytes(i).negate().toBytes()),
453
+ verifyingShares: Object.fromEntries(
454
+ Object.entries(pub.verifyingShares).map(([k, v]) => [
455
+ k,
456
+ Pointk1.fromBytes(v).negate().toBytes(),
457
+ ])
458
+ ),
459
+ } as TRet<FrostPublic>;
460
+ }
461
+ function frostSecretToEvenY(s: TArg<FrostSecret>, pub: TArg<FrostPublic>): TRet<FrostSecret> {
462
+ const VK = Pointk1.fromBytes(pub.commitments[0]);
463
+ // Keep aliasing on the already-even path so wrapper callers can preserve package identity.
464
+ if (hasEven(VK.y)) return s as TRet<FrostSecret>;
465
+ const Fn = Pointk1.Fn;
466
+ return {
467
+ ...s,
468
+ signingShare: Fn.toBytes(Fn.neg(Fn.fromBytes(s.signingShare))),
469
+ } as TRet<FrostSecret>;
470
+ }
471
+ function frostNoncesToEvenY(PK: PointType<bigint>, nonces: TArg<Nonces>): TRet<Nonces> {
472
+ if (hasEven(PK.y)) return nonces as TRet<Nonces>;
473
+ const Fn = Pointk1.Fn;
474
+ return {
475
+ binding: Fn.toBytes(Fn.neg(Fn.fromBytes(nonces.binding))),
476
+ hiding: Fn.toBytes(Fn.neg(Fn.fromBytes(nonces.hiding))),
477
+ } as TRet<Nonces>;
478
+ }
479
+
480
+ function frostTweakSecret(
481
+ s: TArg<FrostSecret>,
482
+ pub: TArg<FrostPublic>,
483
+ merkleRoot?: TArg<Uint8Array>
484
+ ): TRet<FrostSecret> {
485
+ const Fn = Pointk1.Fn;
486
+ const keyPackage = frostSecretToEvenY(s, pub);
487
+ const evenPub = frostPubToEvenY(pub);
488
+ const t = tweak(Pointk1.fromBytes(evenPub.commitments[0]), merkleRoot);
489
+ const signingShare = Fn.toBytes(Fn.add(Fn.fromBytes(keyPackage.signingShare), t));
490
+ return {
491
+ identifier: keyPackage.identifier,
492
+ signingShare,
493
+ } as TRet<FrostSecret>;
494
+ }
495
+
496
+ function frostTweakPublic(
497
+ pub: TArg<FrostPublic>,
498
+ merkleRoot?: TArg<Uint8Array>
499
+ ): TRet<FrostPublic> {
500
+ const PKPackage = frostPubToEvenY(pub);
501
+ const t = tweak(Pointk1.fromBytes(PKPackage.commitments[0]), merkleRoot);
502
+ const tp = Pointk1.BASE.multiply(t);
503
+ const commitments = PKPackage.commitments.map((c, i) =>
504
+ (i === 0 ? Pointk1.fromBytes(c).add(tp) : Pointk1.fromBytes(c)).toBytes()
505
+ );
506
+ const verifyingShares: Record<string, Uint8Array> = {};
507
+ for (const k in PKPackage.verifyingShares) {
508
+ verifyingShares[k] = Pointk1.fromBytes(PKPackage.verifyingShares[k]).add(tp).toBytes();
509
+ }
510
+ return {
511
+ signers: { min: PKPackage.signers.min, max: PKPackage.signers.max },
512
+ commitments,
513
+ verifyingShares,
514
+ } as TRet<FrostPublic>;
515
+ }
516
+
517
+ /**
518
+ * FROST threshold signatures over secp256k1-schnorr-taproot. RFC 9591.
519
+ * DKG outputs are auto-tweaked with the empty Taproot merkle root for compatibility, while
520
+ * `trustedDealer()` outputs stay untweaked unless callers apply the Taproot tweak themselves.
521
+ * @example
522
+ * Create one trusted-dealer package for Taproot-compatible FROST signing.
523
+ *
524
+ * ```ts
525
+ * const alice = schnorr_FROST.Identifier.derive('alice@example.com');
526
+ * const bob = schnorr_FROST.Identifier.derive('bob@example.com');
527
+ * const carol = schnorr_FROST.Identifier.derive('carol@example.com');
528
+ * const deal = schnorr_FROST.trustedDealer({ min: 2, max: 3 }, [alice, bob, carol]);
529
+ * ```
530
+ */
531
+ export const schnorr_FROST: TRet<FROST> = /* @__PURE__ */ (() =>
532
+ createFROST({
533
+ name: 'FROST-secp256k1-SHA256-TR-v1',
534
+ Point: Pointk1,
535
+ hashToScalar: secp256k1_hasher.hashToScalar,
536
+ hash: sha256,
537
+ // Taproot related hacks
538
+ parsePublicKey(publicKey) {
539
+ // External Taproot keys are x-only, but local key packages still use compressed points.
540
+ if (publicKey.length === 32) return lift_x(bytesToNumberBE(publicKey));
541
+ if (publicKey.length === 33) return Pointk1.fromBytes(publicKey);
542
+ throw new Error(`expected x-only or compressed public key, got length=${publicKey.length}`);
543
+ },
544
+ adjustScalar(n: bigint) {
545
+ const PK = Pointk1.BASE.multiply(n);
546
+ return hasEven(PK.y) ? n : Pointk1.Fn.neg(n);
547
+ },
548
+ adjustPoint: (p) => (hasEven(p.y) ? p : p.negate()),
549
+ challenge(R, PK, msg) {
550
+ return challenge(pointToBytes(R), pointToBytes(PK), msg);
551
+ },
552
+ adjustNonces: frostNoncesToEvenY,
553
+ adjustGroupCommitmentShare: (GC, GCShare) => (!hasEven(GC.y) ? GCShare.negate() : GCShare),
554
+ adjustPublic: frostPubToEvenY,
555
+ adjustSecret: frostSecretToEvenY,
556
+ adjustTx: {
557
+ // Compat with official implementation
558
+ encode: (tx) => tx.subarray(1) as TRet<Uint8Array>,
559
+ decode: (tx) => concatBytes(Uint8Array.of(0x02), tx) as TRet<Uint8Array>,
560
+ },
561
+ adjustDKG: (k) => {
562
+ // Compatibility with frost-secp256k1-tr: DKG output is auto-tweaked with the
563
+ // empty Taproot merkle root, while dealer-generated keys stay untweaked.
564
+ const merkleRoot = new Uint8Array(0);
565
+ return {
566
+ public: frostTweakPublic(k.public, merkleRoot),
567
+ secret: frostTweakSecret(k.secret, k.public, merkleRoot),
568
+ };
569
+ },
570
+ }))();