@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
@@ -28,9 +28,11 @@
28
28
  import { hmac as nobleHmac } from '@noble/hashes/hmac.js';
29
29
  import { ahash } from '@noble/hashes/utils.js';
30
30
  import {
31
+ abignumber,
31
32
  abool,
32
33
  abytes,
33
34
  aInRange,
35
+ asafenumber,
34
36
  bitLen,
35
37
  bitMask,
36
38
  bytesToHex,
@@ -39,12 +41,14 @@ import {
39
41
  createHmacDrbg,
40
42
  hexToBytes,
41
43
  isBytes,
42
- memoized,
43
44
  numberToHexUnpadded,
44
45
  validateObject,
45
46
  randomBytes as wcRandomBytes,
46
47
  type CHash,
48
+ type HmacFn,
47
49
  type Signer,
50
+ type TArg,
51
+ type TRet,
48
52
  } from '../utils.ts';
49
53
  import {
50
54
  createCurveFields,
@@ -60,12 +64,14 @@ import {
60
64
  } from './curve.ts';
61
65
  import {
62
66
  FpInvertBatch,
67
+ FpIsSquare,
63
68
  getMinHashLength,
64
69
  mapHashToField,
65
70
  validateField,
66
71
  type IField,
67
72
  } from './modular.ts';
68
73
 
74
+ /** Shared affine point shape used by Weierstrass helpers. */
69
75
  export type { AffinePoint };
70
76
 
71
77
  type EndoBasis = [[bigint, bigint], [bigint, bigint]];
@@ -90,24 +96,45 @@ type EndoBasis = [[bigint, bigint], [bigint, bigint]];
90
96
  * Gauss lattice reduction calculates them from initial basis vectors `(n, 0), (-λ, 0)`
91
97
  *
92
98
  * Check out `test/misc/endomorphism.js` and
93
- * [gist](https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066).
99
+ * {@link https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066 | this endomorphism gist}.
94
100
  */
95
101
  export type EndomorphismOpts = {
102
+ /** Cube root of unity used by the GLV endomorphism. */
96
103
  beta: bigint;
104
+ /** Reduced lattice basis used for scalar splitting. */
97
105
  basises?: EndoBasis;
106
+ /**
107
+ * Optional custom scalar-splitting helper.
108
+ * Receives one scalar and returns two half-sized scalar components.
109
+ */
98
110
  splitScalar?: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
99
111
  };
100
- // We construct basis in such way that den is always positive and equals n, but num sign depends on basis (not on secret value)
112
+ // We construct the basis so `den` is always positive and equals `n`,
113
+ // but the `num` sign depends on the basis, not on the secret value.
114
+ // Exact half-way cases round away from zero, which keeps the split symmetric
115
+ // around the reduced-basis boundaries used by endomorphism decomposition.
101
116
  const divNearest = (num: bigint, den: bigint) => (num + (num >= 0 ? den : -den) / _2n) / den;
102
117
 
103
- export type ScalarEndoParts = { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
118
+ /** Two half-sized scalar components returned by endomorphism splitting. */
119
+ export type ScalarEndoParts = {
120
+ /** Whether the first split scalar should be negated. */
121
+ k1neg: boolean;
122
+ /** Absolute value of the first split scalar. */
123
+ k1: bigint;
124
+ /** Whether the second split scalar should be negated. */
125
+ k2neg: boolean;
126
+ /** Absolute value of the second split scalar. */
127
+ k2: bigint;
128
+ };
104
129
 
105
- /**
106
- * Splits scalar for GLV endomorphism.
107
- */
130
+ /** Splits scalar for GLV endomorphism. */
108
131
  export function _splitEndoScalar(k: bigint, basis: EndoBasis, n: bigint): ScalarEndoParts {
109
132
  // Split scalar into two such that part is ~half bits: `abs(part) < sqrt(N)`
110
133
  // Since part can be negative, we need to do this on point.
134
+ // Callers must provide a reduced GLV basis whose vectors satisfy
135
+ // `a + b * lambda ≡ 0 (mod n)`; this helper only sees the basis and `n`.
136
+ // Reject unreduced scalars instead of silently treating them mod n.
137
+ aInRange('scalar', k, _0n, n);
111
138
  // TODO: verifyScalar function which consumes lambda
112
139
  const [[a1, b1], [a2, b2]] = basis;
113
140
  const c1 = divNearest(b2 * k, n);
@@ -121,10 +148,11 @@ export function _splitEndoScalar(k: bigint, basis: EndoBasis, n: bigint): Scalar
121
148
  if (k1neg) k1 = -k1;
122
149
  if (k2neg) k2 = -k2;
123
150
  // Double check that resulting scalar less than half bits of N: otherwise wNAF will fail.
124
- // This should only happen on wrong basises. Also, math inside is too complex and I don't trust it.
151
+ // This should only happen on wrong bases.
152
+ // Also, the math inside is complex enough that this guard is worth keeping.
125
153
  const MAX_NUM = bitMask(Math.ceil(bitLen(n) / 2)) + _1n; // Half bits of N
126
154
  if (k1 < _0n || k1 >= MAX_NUM || k2 < _0n || k2 >= MAX_NUM) {
127
- throw new Error('splitScalar (endomorphism): failed, k=' + k);
155
+ throw new Error('splitScalar (endomorphism): failed for k');
128
156
  }
129
157
  return { k1neg, k1, k2neg, k2 };
130
158
  }
@@ -143,7 +171,7 @@ export function _splitEndoScalar(k: bigint, basis: EndoBasis, n: bigint): Scalar
143
171
  * * `false` means "disable extra entropy, use purely deterministic k"
144
172
  * * `Uint8Array` passed means "incorporate following data into k generation"
145
173
  *
146
- * https://paulmillr.com/posts/deterministic-signatures/
174
+ * See {@link https://paulmillr.com/posts/deterministic-signatures/ | deterministic signatures}.
147
175
  */
148
176
  export type ECDSAExtraEntropy = boolean | Uint8Array;
149
177
  /**
@@ -157,36 +185,45 @@ export type ECDSASignatureFormat = 'compact' | 'recovered' | 'der';
157
185
  * When a custom hash is used, it must be set to `false`.
158
186
  */
159
187
  export type ECDSARecoverOpts = {
188
+ /** Whether to hash the message before signature recovery. */
160
189
  prehash?: boolean;
161
190
  };
162
191
  /**
163
192
  * - `prehash`: (default: true) indicates whether to do sha256(message).
164
193
  * When a custom hash is used, it must be set to `false`.
165
- * - `lowS`: (default: true) prohibits signatures which have (sig.s >= CURVE.n/2n).
194
+ * - `lowS`: (default: true) prohibits signatures with `sig.s >= CURVE.n/2n`.
166
195
  * Compatible with BTC/ETH. Setting `lowS: false` allows to create malleable signatures,
167
196
  * which is default openssl behavior.
168
197
  * Non-malleable signatures can still be successfully verified in openssl.
169
198
  * - `format`: (default: 'compact') 'compact' or 'recovered' with recovery byte
170
199
  */
171
200
  export type ECDSAVerifyOpts = {
201
+ /** Whether to hash the message before verification. */
172
202
  prehash?: boolean;
203
+ /** Whether to reject high-S signatures. */
173
204
  lowS?: boolean;
205
+ /** Signature encoding to accept. */
174
206
  format?: ECDSASignatureFormat;
175
207
  };
176
208
  /**
177
209
  * - `prehash`: (default: true) indicates whether to do sha256(message).
178
210
  * When a custom hash is used, it must be set to `false`.
179
- * - `lowS`: (default: true) prohibits signatures which have (sig.s >= CURVE.n/2n).
211
+ * - `lowS`: (default: true) prohibits signatures with `sig.s >= CURVE.n/2n`.
180
212
  * Compatible with BTC/ETH. Setting `lowS: false` allows to create malleable signatures,
181
213
  * which is default openssl behavior.
182
214
  * Non-malleable signatures can still be successfully verified in openssl.
183
215
  * - `format`: (default: 'compact') 'compact' or 'recovered' with recovery byte
184
- * - `extraEntropy`: (default: false) creates sigs with increased security, see {@link ECDSAExtraEntropy}
216
+ * - `extraEntropy`: (default: false) creates signatures with increased
217
+ * security, see {@link ECDSAExtraEntropy}
185
218
  */
186
219
  export type ECDSASignOpts = {
220
+ /** Whether to hash the message before signing. */
187
221
  prehash?: boolean;
222
+ /** Whether to normalize signatures into the low-S half-order. */
188
223
  lowS?: boolean;
224
+ /** Signature encoding to produce. */
189
225
  format?: ECDSASignatureFormat;
226
+ /** Optional hedging input for deterministic k generation. */
190
227
  extraEntropy?: ECDSAExtraEntropy;
191
228
  };
192
229
 
@@ -199,19 +236,23 @@ function validateSigFormat(format: string): ECDSASignatureFormat {
199
236
  function validateSigOpts<T extends ECDSASignOpts, D extends Required<ECDSASignOpts>>(
200
237
  opts: T,
201
238
  def: D
202
- ): Required<ECDSASignOpts> {
203
- const optsn: ECDSASignOpts = {};
204
- for (let optName of Object.keys(def)) {
239
+ ): D {
240
+ validateObject(opts);
241
+ const optsn = {} as D;
242
+ // Normalize only the declared option subset from `def`; unknown keys are
243
+ // intentionally ignored so shared / superset option bags stay valid here too.
244
+ // `extraEntropy` stays an opaque payload until the signing path consumes it.
245
+ for (let optName of Object.keys(def) as (keyof D)[]) {
205
246
  // @ts-ignore
206
247
  optsn[optName] = opts[optName] === undefined ? def[optName] : opts[optName];
207
248
  }
208
249
  abool(optsn.lowS!, 'lowS');
209
250
  abool(optsn.prehash!, 'prehash');
210
251
  if (optsn.format !== undefined) validateSigFormat(optsn.format);
211
- return optsn as Required<ECDSASignOpts>;
252
+ return optsn;
212
253
  }
213
254
 
214
- /** Instance methods for 3D XYZ projective points. */
255
+ /** Projective XYZ point used by short Weierstrass curves. */
215
256
  export interface WeierstrassPoint<T> extends CurvePoint<T, WeierstrassPoint<T>> {
216
257
  /** projective X coordinate. Different from affine x. */
217
258
  readonly X: T;
@@ -223,15 +264,28 @@ export interface WeierstrassPoint<T> extends CurvePoint<T, WeierstrassPoint<T>>
223
264
  get x(): T;
224
265
  /** affine y coordinate. Different from projective Y. */
225
266
  get y(): T;
226
- /** Encodes point using IEEE P1363 (DER) encoding. First byte is 2/3/4. Default = isCompressed. */
227
- toBytes(isCompressed?: boolean): Uint8Array;
267
+ /**
268
+ * Encode the point into compressed or uncompressed SEC1 bytes.
269
+ * @param isCompressed - Whether to use the compressed form.
270
+ * @returns Encoded point bytes.
271
+ */
272
+ toBytes(isCompressed?: boolean): TRet<Uint8Array>;
273
+ /**
274
+ * Encode the point into compressed or uncompressed SEC1 hex.
275
+ * @param isCompressed - Whether to use the compressed form.
276
+ * @returns Encoded point hex.
277
+ */
228
278
  toHex(isCompressed?: boolean): string;
229
279
  }
230
280
 
231
- /** Static methods for 3D XYZ projective points. */
281
+ /** Constructor and metadata helpers for Weierstrass points. */
232
282
  export interface WeierstrassPointCons<T> extends CurvePointCons<WeierstrassPoint<T>> {
233
283
  /** Does NOT validate if the point is valid. Use `.assertValidity()`. */
234
284
  new (X: T, Y: T, Z: T): WeierstrassPoint<T>;
285
+ /**
286
+ * Return the curve parameters captured by this point constructor.
287
+ * @returns Curve parameters.
288
+ */
235
289
  CURVE(): WeierstrassOpts<T>;
236
290
  }
237
291
 
@@ -247,67 +301,116 @@ export interface WeierstrassPointCons<T> extends CurvePointCons<WeierstrassPoint
247
301
  * * Gy: y coordinate of generator point
248
302
  */
249
303
  export type WeierstrassOpts<T> = Readonly<{
304
+ /** Base-field modulus. */
250
305
  p: bigint;
306
+ /** Prime subgroup order. */
251
307
  n: bigint;
308
+ /** Curve cofactor. */
252
309
  h: bigint;
310
+ /** Weierstrass curve parameter `a`. */
253
311
  a: T;
312
+ /** Weierstrass curve parameter `b`. */
254
313
  b: T;
314
+ /** Generator x coordinate. */
255
315
  Gx: T;
316
+ /** Generator y coordinate. */
256
317
  Gy: T;
257
318
  }>;
258
319
 
259
- // When a cofactor != 1, there can be an effective methods to:
260
- // 1. Determine whether a point is torsion-free
261
- // 2. Clear torsion component
320
+ /**
321
+ * Optional helpers and overrides for a Weierstrass point constructor.
322
+ *
323
+ * When a cofactor != 1, there can be effective methods to:
324
+ * 1. Determine whether a point is torsion-free
325
+ * 2. Clear torsion component
326
+ */
262
327
  export type WeierstrassExtraOpts<T> = Partial<{
328
+ /** Optional base-field override. */
263
329
  Fp: IField<T>;
330
+ /** Optional scalar-field override. */
264
331
  Fn: IField<bigint>;
332
+ /** Whether the point constructor accepts infinity points. */
265
333
  allowInfinityPoint: boolean;
334
+ /** Optional GLV endomorphism data. */
266
335
  endo: EndomorphismOpts;
336
+ /** Optional torsion-check override. */
267
337
  isTorsionFree: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>) => boolean;
338
+ /** Optional cofactor-clearing override. */
268
339
  clearCofactor: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>) => WeierstrassPoint<T>;
269
- fromBytes: (bytes: Uint8Array) => AffinePoint<T>;
340
+ /** Optional custom point decoder. */
341
+ fromBytes: (bytes: TArg<Uint8Array>) => AffinePoint<T>;
342
+ /** Optional custom point encoder. */
270
343
  toBytes: (
271
344
  c: WeierstrassPointCons<T>,
272
345
  point: WeierstrassPoint<T>,
273
346
  isCompressed: boolean
274
- ) => Uint8Array;
347
+ ) => TRet<Uint8Array>;
275
348
  }>;
276
349
 
277
350
  /**
278
351
  * Options for ECDSA signatures over a Weierstrass curve.
279
352
  *
280
- * * lowS: (default: true) whether produced / verified signatures occupy low half of ecdsaOpts.p. Prevents malleability.
353
+ * * lowS: (default: true) whether produced or verified signatures occupy the
354
+ * low half of `ecdsaOpts.n`. Prevents malleability.
281
355
  * * hmac: (default: noble-hashes hmac) function, would be used to init hmac-drbg for k generation.
282
356
  * * randomBytes: (default: webcrypto os-level CSPRNG) custom method for fetching secure randomness.
283
- * * bits2int, bits2int_modN: used in sigs, sometimes overridden by curves
357
+ * * bits2int, bits2int_modN: used in sigs, sometimes overridden by curves. Custom hooks are
358
+ * treated as pure functions over validated bytes and MUST NOT mutate caller-owned buffers or
359
+ * closure-captured option bags. `bits2int_modN` must also return a canonical scalar in
360
+ * `[0..Point.Fn.ORDER-1]`.
284
361
  */
285
362
  export type ECDSAOpts = Partial<{
363
+ /** Default low-S policy for this ECDSA instance. */
286
364
  lowS: boolean;
287
- hmac: (key: Uint8Array, message: Uint8Array) => Uint8Array;
288
- randomBytes: (bytesLength?: number) => Uint8Array;
289
- bits2int: (bytes: Uint8Array) => bigint;
290
- bits2int_modN: (bytes: Uint8Array) => bigint;
365
+ /** HMAC implementation used by RFC6979 DRBG. */
366
+ hmac: HmacFn;
367
+ /** RNG override used by helper constructors. */
368
+ randomBytes: (bytesLength?: number) => TRet<Uint8Array>;
369
+ /** Hash-to-integer conversion override. */
370
+ bits2int: (bytes: TArg<Uint8Array>) => bigint;
371
+ /** Hash-to-integer-mod-n conversion override. Returns a canonical scalar in `[0..Fn.ORDER-1]`. */
372
+ bits2int_modN: (bytes: TArg<Uint8Array>) => bigint;
291
373
  }>;
292
374
 
293
- /**
294
- * Elliptic Curve Diffie-Hellman interface.
295
- * Provides keygen, secret-to-public conversion, calculating shared secrets.
296
- */
375
+ /** Elliptic Curve Diffie-Hellman helper namespace. */
297
376
  export interface ECDH {
298
- keygen: (seed?: Uint8Array) => { secretKey: Uint8Array; publicKey: Uint8Array };
299
- getPublicKey: (secretKey: Uint8Array, isCompressed?: boolean) => Uint8Array;
377
+ /**
378
+ * Generate a secret/public key pair.
379
+ * @param seed - Optional seed material.
380
+ * @returns Secret/public key pair.
381
+ */
382
+ keygen: (seed?: TArg<Uint8Array>) => { secretKey: TRet<Uint8Array>; publicKey: TRet<Uint8Array> };
383
+ /**
384
+ * Derive the public key from a secret key.
385
+ * @param secretKey - Secret key bytes.
386
+ * @param isCompressed - Whether to emit compressed SEC1 bytes.
387
+ * @returns Encoded public key.
388
+ */
389
+ getPublicKey: (secretKey: TArg<Uint8Array>, isCompressed?: boolean) => TRet<Uint8Array>;
390
+ /**
391
+ * Compute the shared secret point from a secret key and peer public key.
392
+ * @param secretKeyA - Local secret key bytes.
393
+ * @param publicKeyB - Peer public key bytes.
394
+ * @param isCompressed - Whether to emit compressed SEC1 bytes.
395
+ * @returns Encoded shared point.
396
+ */
300
397
  getSharedSecret: (
301
- secretKeyA: Uint8Array,
302
- publicKeyB: Uint8Array,
398
+ secretKeyA: TArg<Uint8Array>,
399
+ publicKeyB: TArg<Uint8Array>,
303
400
  isCompressed?: boolean
304
- ) => Uint8Array;
401
+ ) => TRet<Uint8Array>;
402
+ /** Point constructor used by this ECDH instance. */
305
403
  Point: WeierstrassPointCons<bigint>;
404
+ /** Validation and random-key helpers. */
306
405
  utils: {
307
- isValidSecretKey: (secretKey: Uint8Array) => boolean;
308
- isValidPublicKey: (publicKey: Uint8Array, isCompressed?: boolean) => boolean;
309
- randomSecretKey: (seed?: Uint8Array) => Uint8Array;
406
+ /** Check whether a secret key has the expected encoding. */
407
+ isValidSecretKey: (secretKey: TArg<Uint8Array>) => boolean;
408
+ /** Check whether a public key decodes to a valid point. */
409
+ isValidPublicKey: (publicKey: TArg<Uint8Array>, isCompressed?: boolean) => boolean;
410
+ /** Generate a valid random secret key. */
411
+ randomSecretKey: (seed?: TArg<Uint8Array>) => TRet<Uint8Array>;
310
412
  };
413
+ /** Byte lengths for keys and signatures exposed by this curve. */
311
414
  lengths: CurveLengths;
312
415
  }
313
416
 
@@ -316,39 +419,119 @@ export interface ECDH {
316
419
  * Only supported for prime fields, not Fp2 (extension fields).
317
420
  */
318
421
  export interface ECDSA extends ECDH {
319
- sign: (message: Uint8Array, secretKey: Uint8Array, opts?: ECDSASignOpts) => Uint8Array;
422
+ /**
423
+ * Sign a message with the given secret key.
424
+ * @param message - Message bytes.
425
+ * @param secretKey - Secret key bytes.
426
+ * @param opts - Optional signing tweaks. See {@link ECDSASignOpts}.
427
+ * @returns Encoded signature bytes.
428
+ */
429
+ sign: (
430
+ message: TArg<Uint8Array>,
431
+ secretKey: TArg<Uint8Array>,
432
+ opts?: TArg<ECDSASignOpts>
433
+ ) => TRet<Uint8Array>;
434
+ /**
435
+ * Verify a signature against a message and public key.
436
+ * @param signature - Encoded signature bytes.
437
+ * @param message - Message bytes.
438
+ * @param publicKey - Encoded public key.
439
+ * @param opts - Optional verification tweaks. See {@link ECDSAVerifyOpts}.
440
+ * @returns Whether the signature is valid.
441
+ */
320
442
  verify: (
321
- signature: Uint8Array,
322
- message: Uint8Array,
323
- publicKey: Uint8Array,
324
- opts?: ECDSAVerifyOpts
443
+ signature: TArg<Uint8Array>,
444
+ message: TArg<Uint8Array>,
445
+ publicKey: TArg<Uint8Array>,
446
+ opts?: TArg<ECDSAVerifyOpts>
325
447
  ) => boolean;
326
- recoverPublicKey(signature: Uint8Array, message: Uint8Array, opts?: ECDSARecoverOpts): Uint8Array;
448
+ /**
449
+ * Recover the public key encoded into a recoverable signature.
450
+ * @param signature - Recoverable signature bytes.
451
+ * @param message - Message bytes.
452
+ * @param opts - Optional recovery tweaks. See {@link ECDSARecoverOpts}.
453
+ * @returns Encoded recovered public key.
454
+ */
455
+ recoverPublicKey(
456
+ signature: TArg<Uint8Array>,
457
+ message: TArg<Uint8Array>,
458
+ opts?: TArg<ECDSARecoverOpts>
459
+ ): TRet<Uint8Array>;
460
+ /** Signature constructor and parser helpers. */
327
461
  Signature: ECDSASignatureCons;
328
462
  }
463
+ /**
464
+ * @param m - Error message.
465
+ * @example
466
+ * Throw a DER-specific error when signature parsing encounters invalid bytes.
467
+ *
468
+ * ```ts
469
+ * new DERErr('bad der');
470
+ * ```
471
+ */
329
472
  export class DERErr extends Error {
330
473
  constructor(m = '') {
331
474
  super(m);
332
475
  }
333
476
  }
477
+ /** DER helper namespace used by ECDSA signature parsing and encoding. */
334
478
  export type IDER = {
335
479
  // asn.1 DER encoding utils
480
+ /**
481
+ * DER-specific error constructor.
482
+ * @param m - Error message.
483
+ * @returns DER-specific error instance.
484
+ */
336
485
  Err: typeof DERErr;
337
486
  // Basic building block is TLV (Tag-Length-Value)
487
+ /** Low-level tag-length-value helpers used by DER encoders. */
338
488
  _tlv: {
489
+ /**
490
+ * Encode one TLV record.
491
+ * @param tag - ASN.1 tag byte.
492
+ * @param data - Hex-encoded value payload.
493
+ * @returns Encoded TLV string.
494
+ */
339
495
  encode: (tag: number, data: string) => string;
340
496
  // v - value, l - left bytes (unparsed)
341
- decode(tag: number, data: Uint8Array): { v: Uint8Array; l: Uint8Array };
497
+ /**
498
+ * Decode one TLV record and return the value plus leftover bytes.
499
+ * @param tag - Expected ASN.1 tag byte.
500
+ * @param data - Remaining DER bytes.
501
+ * @returns Parsed value plus leftover bytes.
502
+ */
503
+ decode(tag: number, data: TArg<Uint8Array>): TRet<{ v: Uint8Array; l: Uint8Array }>;
342
504
  };
343
505
  // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,
344
506
  // since we always use positive integers here. It must always be empty:
345
507
  // - add zero byte if exists
346
508
  // - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding)
509
+ /** Positive-integer DER helpers used by ECDSA signature encoding. */
347
510
  _int: {
511
+ /**
512
+ * Encode one positive bigint as a DER INTEGER.
513
+ * @param num - Positive integer to encode.
514
+ * @returns Encoded DER INTEGER.
515
+ */
348
516
  encode(num: bigint): string;
349
- decode(data: Uint8Array): bigint;
517
+ /**
518
+ * Decode one DER INTEGER into a bigint.
519
+ * @param data - DER INTEGER bytes.
520
+ * @returns Decoded bigint.
521
+ */
522
+ decode(data: TArg<Uint8Array>): bigint;
350
523
  };
351
- toSig(hex: string | Uint8Array): { r: bigint; s: bigint };
524
+ /**
525
+ * Parse a DER signature into `{ r, s }`.
526
+ * @param bytes - DER signature bytes.
527
+ * @returns Parsed signature components.
528
+ */
529
+ toSig(bytes: TArg<Uint8Array>): { r: bigint; s: bigint };
530
+ /**
531
+ * Encode `{ r, s }` as a DER signature.
532
+ * @param sig - Signature components.
533
+ * @returns DER-encoded signature hex.
534
+ */
352
535
  hexFromSig(sig: { r: bigint; s: bigint }): string;
353
536
  };
354
537
  /**
@@ -356,7 +539,14 @@ export type IDER = {
356
539
  *
357
540
  * [0x30 (SEQUENCE), bytelength, 0x02 (INTEGER), intLength, R, 0x02 (INTEGER), intLength, S]
358
541
  *
359
- * Docs: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/, https://luca.ntop.org/Teaching/Appunti/asn1.html
542
+ * Docs: {@link https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/ | Let's Encrypt ASN.1 guide} and
543
+ * {@link https://luca.ntop.org/Teaching/Appunti/asn1.html | Luca Deri's ASN.1 notes}.
544
+ * @example
545
+ * ASN.1 DER encoding utilities.
546
+ *
547
+ * ```ts
548
+ * const der = DER.hexFromSig({ r: 1n, s: 2n });
549
+ * ```
360
550
  */
361
551
  export const DER: IDER = {
362
552
  // asn.1 DER encoding utils
@@ -365,7 +555,12 @@ export const DER: IDER = {
365
555
  _tlv: {
366
556
  encode: (tag: number, data: string): string => {
367
557
  const { Err: E } = DER;
368
- if (tag < 0 || tag > 256) throw new E('tlv.encode: wrong tag');
558
+ asafenumber(tag, 'tag');
559
+ if (tag < 0 || tag > 255) throw new E('tlv.encode: wrong tag');
560
+ if (typeof data !== 'string')
561
+ throw new TypeError('"data" expected string, got type=' + typeof data);
562
+ // Internal helper: callers hand this already-validated hex payload, so we only enforce
563
+ // byte alignment here instead of re-validating every nibble.
369
564
  if (data.length & 1) throw new E('tlv.encode: unpadded data');
370
565
  const dataLen = data.length / 2;
371
566
  const len = numberToHexUnpadded(dataLen);
@@ -376,20 +571,23 @@ export const DER: IDER = {
376
571
  return t + lenLen + len + data;
377
572
  },
378
573
  // v - value, l - left bytes (unparsed)
379
- decode(tag: number, data: Uint8Array): { v: Uint8Array; l: Uint8Array } {
574
+ decode(tag: number, data: TArg<Uint8Array>): TRet<{ v: Uint8Array; l: Uint8Array }> {
380
575
  const { Err: E } = DER;
576
+ data = abytes(data, undefined, 'DER data');
381
577
  let pos = 0;
382
- if (tag < 0 || tag > 256) throw new E('tlv.encode: wrong tag');
578
+ if (tag < 0 || tag > 255) throw new E('tlv.encode: wrong tag');
383
579
  if (data.length < 2 || data[pos++] !== tag) throw new E('tlv.decode: wrong tlv');
384
580
  const first = data[pos++];
385
- const isLong = !!(first & 0b1000_0000); // First bit of first length byte is flag for short/long form
581
+ // First bit of first length byte is the short/long form flag.
582
+ const isLong = !!(first & 0b1000_0000);
386
583
  let length = 0;
387
584
  if (!isLong) length = first;
388
585
  else {
389
586
  // Long form: [longFlag(1bit), lengthLength(7bit), length (BE)]
390
587
  const lenLen = first & 0b0111_1111;
391
588
  if (!lenLen) throw new E('tlv.decode(long): indefinite length not supported');
392
- if (lenLen > 4) throw new E('tlv.decode(long): byte length is too big'); // this will overflow u32 in js
589
+ // This would overflow u32 in JS.
590
+ if (lenLen > 4) throw new E('tlv.decode(long): byte length is too big');
393
591
  const lengthBytes = data.subarray(pos, pos + lenLen);
394
592
  if (lengthBytes.length !== lenLen) throw new E('tlv.decode: length bytes not complete');
395
593
  if (lengthBytes[0] === 0) throw new E('tlv.decode(long): zero leftmost byte');
@@ -399,7 +597,7 @@ export const DER: IDER = {
399
597
  }
400
598
  const v = data.subarray(pos, pos + length);
401
599
  if (v.length !== length) throw new E('tlv.decode: wrong value length');
402
- return { v, l: data.subarray(pos + length) };
600
+ return { v, l: data.subarray(pos + length) } as TRet<{ v: Uint8Array; l: Uint8Array }>;
403
601
  },
404
602
  },
405
603
  // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,
@@ -409,6 +607,7 @@ export const DER: IDER = {
409
607
  _int: {
410
608
  encode(num: bigint): string {
411
609
  const { Err: E } = DER;
610
+ abignumber(num);
412
611
  if (num < _0n) throw new E('integer: negative integers are not allowed');
413
612
  let hex = numberToHexUnpadded(num);
414
613
  // Pad with zero byte if negative flag is present
@@ -416,15 +615,17 @@ export const DER: IDER = {
416
615
  if (hex.length & 1) throw new E('unexpected DER parsing assertion: unpadded hex');
417
616
  return hex;
418
617
  },
419
- decode(data: Uint8Array): bigint {
618
+ decode(data: TArg<Uint8Array>): bigint {
420
619
  const { Err: E } = DER;
620
+ if (data.length < 1) throw new E('invalid signature integer: empty');
421
621
  if (data[0] & 0b1000_0000) throw new E('invalid signature integer: negative');
422
- if (data[0] === 0x00 && !(data[1] & 0b1000_0000))
622
+ // Single-byte zero `00` is the canonical DER INTEGER encoding for zero.
623
+ if (data.length > 1 && data[0] === 0x00 && !(data[1] & 0b1000_0000))
423
624
  throw new E('invalid signature integer: unnecessary leading zero');
424
625
  return bytesToNumberBE(data);
425
626
  },
426
627
  },
427
- toSig(bytes: Uint8Array): { r: bigint; s: bigint } {
628
+ toSig(bytes: TArg<Uint8Array>): { r: bigint; s: bigint } {
428
629
  // parse DER signature
429
630
  const { Err: E, _int: int, _tlv: tlv } = DER;
430
631
  const data = abytes(bytes, undefined, 'signature');
@@ -443,36 +644,46 @@ export const DER: IDER = {
443
644
  return tlv.encode(0x30, seq);
444
645
  },
445
646
  };
647
+ Object.freeze(DER._tlv);
648
+ Object.freeze(DER._int);
649
+ Object.freeze(DER);
446
650
 
447
651
  // Be friendly to bad ECMAScript parsers by not using bigint literals
448
652
  // prettier-ignore
449
- const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4);
653
+ const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1), _2n = /* @__PURE__ */ BigInt(2), _3n = /* @__PURE__ */ BigInt(3), _4n = /* @__PURE__ */ BigInt(4);
450
654
 
451
655
  /**
452
656
  * Creates weierstrass Point constructor, based on specified curve options.
453
657
  *
454
658
  * See {@link WeierstrassOpts}.
659
+ * @param params - Curve parameters. See {@link WeierstrassOpts}.
660
+ * @param extraOpts - Optional helpers and overrides. See {@link WeierstrassExtraOpts}.
661
+ * @returns Weierstrass point constructor.
662
+ * @throws If the curve parameters, overrides, or point codecs are invalid. {@link Error}
455
663
  *
456
664
  * @example
457
- ```js
458
- const opts = {
459
- p: 0xfffffffffffffffffffffffffffffffeffffac73n,
460
- n: 0x100000000000000000001b8fa16dfab9aca16b6b3n,
461
- h: 1n,
462
- a: 0n,
463
- b: 7n,
464
- Gx: 0x3b4c382ce37aa192a4019e763036f4f5dd4d7ebbn,
465
- Gy: 0x938cf935318fdced6bc28286531733c3f03c4feen,
466
- };
467
- const secp160k1_Point = weierstrass(opts);
468
- ```
665
+ * Construct a point type from explicit Weierstrass curve parameters.
666
+ *
667
+ * ```js
668
+ * const opts = {
669
+ * p: 0xfffffffffffffffffffffffffffffffeffffac73n,
670
+ * n: 0x100000000000000000001b8fa16dfab9aca16b6b3n,
671
+ * h: 1n,
672
+ * a: 0n,
673
+ * b: 7n,
674
+ * Gx: 0x3b4c382ce37aa192a4019e763036f4f5dd4d7ebbn,
675
+ * Gy: 0x938cf935318fdced6bc28286531733c3f03c4feen,
676
+ * };
677
+ * const secp160k1_Point = weierstrass(opts);
678
+ * ```
469
679
  */
470
680
  export function weierstrass<T>(
471
681
  params: WeierstrassOpts<T>,
472
682
  extraOpts: WeierstrassExtraOpts<T> = {}
473
683
  ): WeierstrassPointCons<T> {
474
684
  const validated = createCurveFields('weierstrass', params, extraOpts);
475
- const { Fp, Fn } = validated;
685
+ const Fp = validated.Fp as IField<T>;
686
+ const Fn = validated.Fn as IField<bigint>;
476
687
  let CURVE = validated.CURVE as WeierstrassOpts<T>;
477
688
  const { h: cofactor, n: CURVE_ORDER } = CURVE;
478
689
  validateObject(
@@ -488,7 +699,9 @@ export function weierstrass<T>(
488
699
  }
489
700
  );
490
701
 
491
- const { endo } = extraOpts;
702
+ // Snapshot constructor-time flags whose later mutation would otherwise change
703
+ // validity semantics of an already-built point type.
704
+ const { endo, allowInfinityPoint } = extraOpts;
492
705
  if (endo) {
493
706
  // validateObject(endo, { beta: 'bigint', splitScalar: 'function' });
494
707
  if (!Fp.is0(CURVE.a) || typeof endo.beta !== 'bigint' || !Array.isArray(endo.basises)) {
@@ -496,7 +709,7 @@ export function weierstrass<T>(
496
709
  }
497
710
  }
498
711
 
499
- const lengths = getWLengths(Fp, Fn);
712
+ const lengths = getWLengths(Fp as TArg<IField<T>>, Fn);
500
713
 
501
714
  function assertCompressionIsSupported() {
502
715
  if (!Fp.isOdd) throw new Error('compression is not supported: Field does not have .isOdd()');
@@ -507,24 +720,33 @@ export function weierstrass<T>(
507
720
  _c: WeierstrassPointCons<T>,
508
721
  point: WeierstrassPoint<T>,
509
722
  isCompressed: boolean
510
- ): Uint8Array {
723
+ ): TRet<Uint8Array> {
724
+ // SEC 1 v2.0 §2.3.3 encodes infinity as the single octet 0x00. Only curves
725
+ // that opt into infinity as a public point value should expose that byte form.
726
+ if (allowInfinityPoint && point.is0()) return Uint8Array.of(0) as TRet<Uint8Array>;
511
727
  const { x, y } = point.toAffine();
512
728
  const bx = Fp.toBytes(x);
513
729
  abool(isCompressed, 'isCompressed');
514
730
  if (isCompressed) {
515
731
  assertCompressionIsSupported();
516
732
  const hasEvenY = !Fp.isOdd!(y);
517
- return concatBytes(pprefix(hasEvenY), bx);
733
+ return concatBytes(pprefix(hasEvenY), bx) as TRet<Uint8Array>;
518
734
  } else {
519
- return concatBytes(Uint8Array.of(0x04), bx, Fp.toBytes(y));
735
+ return concatBytes(Uint8Array.of(0x04), bx, Fp.toBytes(y)) as TRet<Uint8Array>;
520
736
  }
521
737
  }
522
- function pointFromBytes(bytes: Uint8Array) {
738
+ function pointFromBytes(bytes: TArg<Uint8Array>) {
523
739
  abytes(bytes, undefined, 'Point');
524
740
  const { publicKey: comp, publicKeyUncompressed: uncomp } = lengths; // e.g. for 32-byte: 33, 65
525
741
  const length = bytes.length;
526
742
  const head = bytes[0];
527
743
  const tail = bytes.subarray(1);
744
+ if (allowInfinityPoint && length === 1 && head === 0x00) return { x: Fp.ZERO, y: Fp.ZERO };
745
+ // SEC 1 v2.0 §2.3.4 decodes 0x00 as infinity, but §3.2.2 public-key validation
746
+ // rejects infinity. We therefore keep 0x00 rejected by default because callers
747
+ // reuse this parser as the strict public-key boundary, and only admit it when
748
+ // the curve explicitly opts into infinity as a public point value. secp256k1
749
+ // crosstests show OpenSSL raw point codecs accept 0x00 too.
528
750
  // No actual validation is done here: use .assertValidity()
529
751
  if (length === comp && (head === 0x02 || head === 0x03)) {
530
752
  const x = Fp.fromBytes(tail);
@@ -556,8 +778,8 @@ export function weierstrass<T>(
556
778
  }
557
779
  }
558
780
 
559
- const encodePoint = extraOpts.toBytes || pointToBytes;
560
- const decodePoint = extraOpts.fromBytes || pointFromBytes;
781
+ const encodePoint = extraOpts.toBytes === undefined ? pointToBytes : extraOpts.toBytes;
782
+ const decodePoint = extraOpts.fromBytes === undefined ? pointFromBytes : extraOpts.fromBytes;
561
783
  function weierstrassEquation(x: T): T {
562
784
  const x2 = Fp.sqr(x); // x * x
563
785
  const x3 = Fp.mul(x2, x); // x² * x
@@ -572,7 +794,8 @@ export function weierstrass<T>(
572
794
  return Fp.eql(left, right);
573
795
  }
574
796
 
575
- // Validate whether the passed curve params are valid.
797
+ // Keep constructor-time generator validation cheap: callers are responsible for supplying the
798
+ // correct prime-order base point, while eager subgroup checks here would slow heavy module imports.
576
799
  // Test 1: equation y² = x³ + ax + b should work for generator point.
577
800
  if (!isValidXY(CURVE.Gx, CURVE.Gy)) throw new Error('bad curve params: generator point');
578
801
 
@@ -588,7 +811,7 @@ export function weierstrass<T>(
588
811
  return n;
589
812
  }
590
813
 
591
- function aprjpoint(other: unknown) {
814
+ function aprjpoint(other: unknown): asserts other is Point {
592
815
  if (!(other instanceof Point)) throw new Error('Weierstrass Point expected');
593
816
  }
594
817
 
@@ -597,44 +820,6 @@ export function weierstrass<T>(
597
820
  return _splitEndoScalar(k, endo.basises, Fn.ORDER);
598
821
  }
599
822
 
600
- // Memoized toAffine / validity check. They are heavy. Points are immutable.
601
-
602
- // Converts Projective point to affine (x, y) coordinates.
603
- // Can accept precomputed Z^-1 - for example, from invertBatch.
604
- // (X, Y, Z) ∋ (x=X/Z, y=Y/Z)
605
- const toAffineMemo = memoized((p: Point, iz?: T): AffinePoint<T> => {
606
- const { X, Y, Z } = p;
607
- // Fast-path for normalized points
608
- if (Fp.eql(Z, Fp.ONE)) return { x: X, y: Y };
609
- const is0 = p.is0();
610
- // If invZ was 0, we return zero point. However we still want to execute
611
- // all operations, so we replace invZ with a random number, 1.
612
- if (iz == null) iz = is0 ? Fp.ONE : Fp.inv(Z);
613
- const x = Fp.mul(X, iz);
614
- const y = Fp.mul(Y, iz);
615
- const zz = Fp.mul(Z, iz);
616
- if (is0) return { x: Fp.ZERO, y: Fp.ZERO };
617
- if (!Fp.eql(zz, Fp.ONE)) throw new Error('invZ was invalid');
618
- return { x, y };
619
- });
620
- // NOTE: on exception this will crash 'cached' and no value will be set.
621
- // Otherwise true will be return
622
- const assertValidMemo = memoized((p: Point) => {
623
- if (p.is0()) {
624
- // (0, 1, 0) aka ZERO is invalid in most contexts.
625
- // In BLS, ZERO can be serialized, so we allow it.
626
- // (0, 0, 0) is invalid representation of ZERO.
627
- if (extraOpts.allowInfinityPoint && !Fp.is0(p.Y)) return;
628
- throw new Error('bad point: ZERO');
629
- }
630
- // Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
631
- const { x, y } = p.toAffine();
632
- if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error('bad point: x or y not field elements');
633
- if (!isValidXY(x, y)) throw new Error('bad point: equation left != right');
634
- if (!p.isTorsionFree()) throw new Error('bad point: not in prime-order subgroup');
635
- return true;
636
- });
637
-
638
823
  function finishEndo(
639
824
  endoBeta: EndomorphismOpts['beta'],
640
825
  k1p: Point,
@@ -670,6 +855,9 @@ export function weierstrass<T>(
670
855
  /** Does NOT validate if the point is valid. Use `.assertValidity()`. */
671
856
  constructor(X: T, Y: T, Z: T) {
672
857
  this.X = acoord('x', X);
858
+ // This is not just about ZERO / infinity: ambient curves can have real
859
+ // finite points with y=0. Those points are 2-torsion, so they cannot lie
860
+ // in the odd prime-order subgroups this point type is meant to represent.
673
861
  this.Y = acoord('y', Y, true);
674
862
  this.Z = acoord('z', Z);
675
863
  Object.freeze(this);
@@ -689,7 +877,7 @@ export function weierstrass<T>(
689
877
  return new Point(x, y, Fp.ONE);
690
878
  }
691
879
 
692
- static fromBytes(bytes: Uint8Array): Point {
880
+ static fromBytes(bytes: TArg<Uint8Array>): Point {
693
881
  const P = Point.fromAffine(decodePoint(abytes(bytes, undefined, 'point')));
694
882
  P.assertValidity();
695
883
  return P;
@@ -709,7 +897,7 @@ export function weierstrass<T>(
709
897
  /**
710
898
  *
711
899
  * @param windowSize
712
- * @param isLazy true will defer table computation until the first multiplication
900
+ * @param isLazy - true will defer table computation until the first multiplication
713
901
  * @returns
714
902
  */
715
903
  precompute(windowSize: number = 8, isLazy = true): Point {
@@ -721,7 +909,21 @@ export function weierstrass<T>(
721
909
  // TODO: return `this`
722
910
  /** A point on curve is valid if it conforms to equation. */
723
911
  assertValidity(): void {
724
- assertValidMemo(this);
912
+ const p = this;
913
+ if (p.is0()) {
914
+ // (0, 1, 0) aka ZERO is invalid in most contexts.
915
+ // In BLS, ZERO can be serialized, so we allow it.
916
+ // Keep the accepted infinity encoding canonical: projective-equivalent (X, Y, 0) points
917
+ // like (1, 1, 0) compare equal to ZERO, but only (0, 1, 0) should pass this guard.
918
+ if (extraOpts.allowInfinityPoint && Fp.is0(p.X) && Fp.eql(p.Y, Fp.ONE) && Fp.is0(p.Z))
919
+ return;
920
+ throw new Error('bad point: ZERO');
921
+ }
922
+ // Some 3rd-party test vectors require different wording between here & `fromCompressedHex`
923
+ const { x, y } = p.toAffine();
924
+ if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error('bad point: x or y not field elements');
925
+ if (!isValidXY(x, y)) throw new Error('bad point: equation left != right');
926
+ if (!p.isTorsionFree()) throw new Error('bad point: not in prime-order subgroup');
725
927
  }
726
928
 
727
929
  hasEvenY(): boolean {
@@ -731,7 +933,7 @@ export function weierstrass<T>(
731
933
  }
732
934
 
733
935
  /** Compare one point to another. */
734
- equals(other: Point): boolean {
936
+ equals(other: WeierstrassPoint<T>): boolean {
735
937
  aprjpoint(other);
736
938
  const { X: X1, Y: Y1, Z: Z1 } = this;
737
939
  const { X: X2, Y: Y2, Z: Z2 } = other;
@@ -792,7 +994,7 @@ export function weierstrass<T>(
792
994
  // There is 30% faster Jacobian formula, but it is not complete.
793
995
  // https://eprint.iacr.org/2015/1060, algorithm 1
794
996
  // Cost: 12M + 0S + 3*a + 3*b3 + 23add.
795
- add(other: Point): Point {
997
+ add(other: WeierstrassPoint<T>): Point {
796
998
  aprjpoint(other);
797
999
  const { X: X1, Y: Y1, Z: Z1 } = this;
798
1000
  const { X: X2, Y: Y2, Z: Z2 } = other;
@@ -842,7 +1044,10 @@ export function weierstrass<T>(
842
1044
  return new Point(X3, Y3, Z3);
843
1045
  }
844
1046
 
845
- subtract(other: Point) {
1047
+ subtract(other: WeierstrassPoint<T>) {
1048
+ // Validate before calling `negate()` so wrong inputs fail with the point guard
1049
+ // instead of leaking a foreign `negate()` error.
1050
+ aprjpoint(other);
846
1051
  return this.add(other.negate());
847
1052
  }
848
1053
 
@@ -856,12 +1061,15 @@ export function weierstrass<T>(
856
1061
  * but takes 2x longer to generate and consumes 2x memory.
857
1062
  * Uses precomputes when available.
858
1063
  * Uses endomorphism for Koblitz curves.
859
- * @param scalar by which the point would be multiplied
1064
+ * @param scalar - by which the point would be multiplied
860
1065
  * @returns New point
861
1066
  */
862
1067
  multiply(scalar: bigint): Point {
863
1068
  const { endo } = extraOpts;
864
- if (!Fn.isValidNot0(scalar)) throw new Error('invalid scalar: out of range'); // 0 is invalid
1069
+ // Keep the subgroup-scalar contract strict instead of reducing 0 / n to ZERO.
1070
+ // In key/signature-style callers, those values usually mean broken hash/scalar plumbing,
1071
+ // and failing closed is safer than silently producing the identity point.
1072
+ if (!Fn.isValidNot0(scalar)) throw new RangeError('invalid scalar: out of range'); // 0 is invalid
865
1073
  let point: Point, fake: Point; // Fake point is used to const-time mult
866
1074
  const mul = (n: bigint) => wnaf.cached(this, n, (p) => normalizeZ(Point, p));
867
1075
  /** See docs for {@link EndomorphismOpts} */
@@ -885,10 +1093,13 @@ export function weierstrass<T>(
885
1093
  * It's faster, but should only be used when you don't care about
886
1094
  * an exposed secret key e.g. sig verification, which works over *public* keys.
887
1095
  */
888
- multiplyUnsafe(sc: bigint): Point {
1096
+ multiplyUnsafe(scalar: bigint): Point {
889
1097
  const { endo } = extraOpts;
890
1098
  const p = this as Point;
891
- if (!Fn.isValid(sc)) throw new Error('invalid scalar: out of range'); // 0 is valid
1099
+ const sc = scalar;
1100
+ // Public-scalar callers may need 0, but n and larger values stay rejected here too.
1101
+ // Reducing them mod n would turn bad caller input into an accidental identity point.
1102
+ if (!Fn.isValid(sc)) throw new RangeError('invalid scalar: out of range'); // 0 is valid
892
1103
  if (sc === _0n || p.is0()) return Point.ZERO; // 0
893
1104
  if (sc === _1n) return p; // 1
894
1105
  if (wnaf.hasCache(this)) return this.multiply(sc); // precomputes
@@ -905,10 +1116,25 @@ export function weierstrass<T>(
905
1116
 
906
1117
  /**
907
1118
  * Converts Projective point to affine (x, y) coordinates.
908
- * @param invertedZ Z^-1 (inverted zero) - optional, precomputation is useful for invertBatch
1119
+ * (X, Y, Z) (x=X/Z, y=Y/Z).
1120
+ * @param invertedZ - Z^-1 (inverted zero) - optional, precomputation is useful for invertBatch
909
1121
  */
910
1122
  toAffine(invertedZ?: T): AffinePoint<T> {
911
- return toAffineMemo(this, invertedZ);
1123
+ const p = this;
1124
+ let iz = invertedZ;
1125
+ const { X, Y, Z } = p;
1126
+ // Fast-path for normalized points
1127
+ if (Fp.eql(Z, Fp.ONE)) return { x: X, y: Y };
1128
+ const is0 = p.is0();
1129
+ // If invZ was 0, we return zero point. However we still want to execute
1130
+ // all operations, so we replace invZ with a random number, 1.
1131
+ if (iz == null) iz = is0 ? Fp.ONE : Fp.inv(Z);
1132
+ const x = Fp.mul(X, iz);
1133
+ const y = Fp.mul(Y, iz);
1134
+ const zz = Fp.mul(Z, iz);
1135
+ if (is0) return { x: Fp.ZERO, y: Fp.ZERO };
1136
+ if (!Fp.eql(zz, Fp.ONE)) throw new Error('invZ was invalid');
1137
+ return { x, y };
912
1138
  }
913
1139
 
914
1140
  /**
@@ -926,16 +1152,21 @@ export function weierstrass<T>(
926
1152
  const { clearCofactor } = extraOpts;
927
1153
  if (cofactor === _1n) return this; // Fast-path
928
1154
  if (clearCofactor) return clearCofactor(Point, this) as Point;
1155
+ // Default fallback assumes the cofactor fits the usual subgroup-scalar
1156
+ // multiplyUnsafe() contract. Curves with larger / structured cofactors
1157
+ // should define a clearCofactor override anyway (e.g. psi/Frobenius maps).
929
1158
  return this.multiplyUnsafe(cofactor);
930
1159
  }
931
1160
 
932
1161
  isSmallOrder(): boolean {
933
- // can we use this.clearCofactor()?
934
- return this.multiplyUnsafe(cofactor).is0();
1162
+ if (cofactor === _1n) return this.is0(); // Fast-path
1163
+ return this.clearCofactor().is0();
935
1164
  }
936
1165
 
937
- toBytes(isCompressed = true): Uint8Array {
1166
+ toBytes(isCompressed = true): TRet<Uint8Array> {
938
1167
  abool(isCompressed, 'isCompressed');
1168
+ // Same policy as pointFromBytes(): keep ZERO out of the default byte surface because
1169
+ // callers use these encodings as public keys, where SEC 1 validation rejects infinity.
939
1170
  this.assertValidity();
940
1171
  return encodePoint(Point, this, isCompressed);
941
1172
  }
@@ -950,31 +1181,75 @@ export function weierstrass<T>(
950
1181
  }
951
1182
  const bits = Fn.BITS;
952
1183
  const wnaf = new wNAF(Point, extraOpts.endo ? Math.ceil(bits / 2) : bits);
953
- Point.BASE.precompute(8); // Enable precomputes. Slows down first publicKey computation by 20ms.
1184
+ // Tiny toy curves can have scalar fields narrower than 8 bits. Skip the
1185
+ // eager W=8 cache there instead of rejecting an otherwise valid constructor.
1186
+ if (bits >= 8) Point.BASE.precompute(8); // Enable precomputes. Slows down first publicKey computation by 20ms.
1187
+ Object.freeze(Point.prototype);
1188
+ Object.freeze(Point);
954
1189
  return Point;
955
1190
  }
956
1191
 
957
- /** Methods of ECDSA signature instance. */
1192
+ /** Parsed ECDSA signature with helpers for recovery and re-encoding. */
958
1193
  export interface ECDSASignature {
1194
+ /** Signature component `r`. */
959
1195
  readonly r: bigint;
1196
+ /** Signature component `s`. */
960
1197
  readonly s: bigint;
1198
+ /** Optional recovery bit for recoverable signatures. */
961
1199
  readonly recovery?: number;
1200
+ /**
1201
+ * Return a copy of the signature with a recovery bit attached.
1202
+ * @param recovery - Recovery bit to attach.
1203
+ * @returns Signature with an attached recovery bit.
1204
+ */
962
1205
  addRecoveryBit(recovery: number): ECDSASignature & { readonly recovery: number };
1206
+ /**
1207
+ * Check whether the signature uses the high-S half-order.
1208
+ * @returns Whether the signature uses the high-S half-order.
1209
+ */
963
1210
  hasHighS(): boolean;
964
- recoverPublicKey(messageHash: Uint8Array): WeierstrassPoint<bigint>;
965
- toBytes(format?: string): Uint8Array;
1211
+ /**
1212
+ * Recover the public key from the hashed message and recovery bit.
1213
+ * @param messageHash - Hashed message bytes.
1214
+ * @returns Recovered public-key point.
1215
+ */
1216
+ recoverPublicKey(messageHash: TArg<Uint8Array>): WeierstrassPoint<bigint>;
1217
+ /**
1218
+ * Encode the signature into bytes.
1219
+ * @param format - Signature encoding to produce.
1220
+ * @returns Encoded signature bytes.
1221
+ */
1222
+ toBytes(format?: string): TRet<Uint8Array>;
1223
+ /**
1224
+ * Encode the signature into hex.
1225
+ * @param format - Signature encoding to produce.
1226
+ * @returns Encoded signature hex.
1227
+ */
966
1228
  toHex(format?: string): string;
967
1229
  }
968
- /** Methods of ECDSA signature constructor. */
1230
+ /** Constructor and decoding helpers for ECDSA signatures. */
969
1231
  export type ECDSASignatureCons = {
1232
+ /** Create a signature from `r`, `s`, and an optional recovery bit. */
970
1233
  new (r: bigint, s: bigint, recovery?: number): ECDSASignature;
971
- fromBytes(bytes: Uint8Array, format?: ECDSASignatureFormat): ECDSASignature;
1234
+ /**
1235
+ * Decode a signature from bytes.
1236
+ * @param bytes - Encoded signature bytes.
1237
+ * @param format - Signature encoding to parse.
1238
+ * @returns Parsed signature.
1239
+ */
1240
+ fromBytes(bytes: TArg<Uint8Array>, format?: ECDSASignatureFormat): ECDSASignature;
1241
+ /**
1242
+ * Decode a signature from hex.
1243
+ * @param hex - Encoded signature hex.
1244
+ * @param format - Signature encoding to parse.
1245
+ * @returns Parsed signature.
1246
+ */
972
1247
  fromHex(hex: string, format?: ECDSASignatureFormat): ECDSASignature;
973
1248
  };
974
1249
 
975
1250
  // Points start with byte 0x02 when y is even; otherwise 0x03
976
- function pprefix(hasEvenY: boolean): Uint8Array {
977
- return Uint8Array.of(hasEvenY ? 0x02 : 0x03);
1251
+ function pprefix(hasEvenY: boolean): TRet<Uint8Array> {
1252
+ return Uint8Array.of(hasEvenY ? 0x02 : 0x03) as TRet<Uint8Array>;
978
1253
  }
979
1254
 
980
1255
  /**
@@ -982,16 +1257,29 @@ function pprefix(hasEvenY: boolean): Uint8Array {
982
1257
  * TODO: check if there is a way to merge this with uvRatio in Edwards; move to modular.
983
1258
  * b = True and y = sqrt(u / v) if (u / v) is square in F, and
984
1259
  * b = False and y = sqrt(Z * (u / v)) otherwise.
985
- * @param Fp
986
- * @param Z
987
- * @returns
1260
+ * RFC 9380 expects callers to provide `v != 0`; this helper does not enforce it.
1261
+ * @param Fp - Field implementation.
1262
+ * @param Z - Simplified SWU map parameter.
1263
+ * @returns Square-root ratio helper.
1264
+ * @example
1265
+ * Build the square-root ratio helper used by SWU map implementations.
1266
+ *
1267
+ * ```ts
1268
+ * import { SWUFpSqrtRatio } from '@noble/curves/abstract/weierstrass.js';
1269
+ * import { Field } from '@noble/curves/abstract/modular.js';
1270
+ * const Fp = Field(17n);
1271
+ * const sqrtRatio = SWUFpSqrtRatio(Fp, 3n);
1272
+ * const out = sqrtRatio(4n, 1n);
1273
+ * ```
988
1274
  */
989
1275
  export function SWUFpSqrtRatio<T>(
990
- Fp: IField<T>,
1276
+ Fp: TArg<IField<T>>,
991
1277
  Z: T
992
1278
  ): (u: T, v: T) => { isValid: boolean; value: T } {
1279
+ // Fail with the usual field-shape error before touching pow/cmov on malformed field shims.
1280
+ const F = validateField(Fp as IField<T>) as IField<T>;
993
1281
  // Generic implementation
994
- const q = Fp.ORDER;
1282
+ const q = F.ORDER;
995
1283
  let l = _0n;
996
1284
  for (let o = q - _1n; o % _2n === _0n; o /= _2n) l += _1n;
997
1285
  const c1 = l; // 1. c1, the largest integer such that 2^c1 divides q - 1.
@@ -1003,54 +1291,60 @@ export function SWUFpSqrtRatio<T>(
1003
1291
  const c3 = (c2 - _1n) / _2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic
1004
1292
  const c4 = _2n_pow_c1 - _1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic
1005
1293
  const c5 = _2n_pow_c1_1; // 5. c5 = 2^(c1 - 1) # Integer arithmetic
1006
- const c6 = Fp.pow(Z, c2); // 6. c6 = Z^c2
1007
- const c7 = Fp.pow(Z, (c2 + _1n) / _2n); // 7. c7 = Z^((c2 + 1) / 2)
1294
+ const c6 = F.pow(Z, c2); // 6. c6 = Z^c2
1295
+ const c7 = F.pow(Z, (c2 + _1n) / _2n); // 7. c7 = Z^((c2 + 1) / 2)
1296
+ // RFC 9380 Appendix F.2.1.1 defines sqrt_ratio(u, v) only for v != 0.
1297
+ // We keep v=0 on the regular result path with isValid=false instead of
1298
+ // throwing so the helper stays closer to the RFC's fixed control flow.
1008
1299
  let sqrtRatio = (u: T, v: T): { isValid: boolean; value: T } => {
1009
1300
  let tv1 = c6; // 1. tv1 = c6
1010
- let tv2 = Fp.pow(v, c4); // 2. tv2 = v^c4
1011
- let tv3 = Fp.sqr(tv2); // 3. tv3 = tv2^2
1012
- tv3 = Fp.mul(tv3, v); // 4. tv3 = tv3 * v
1013
- let tv5 = Fp.mul(u, tv3); // 5. tv5 = u * tv3
1014
- tv5 = Fp.pow(tv5, c3); // 6. tv5 = tv5^c3
1015
- tv5 = Fp.mul(tv5, tv2); // 7. tv5 = tv5 * tv2
1016
- tv2 = Fp.mul(tv5, v); // 8. tv2 = tv5 * v
1017
- tv3 = Fp.mul(tv5, u); // 9. tv3 = tv5 * u
1018
- let tv4 = Fp.mul(tv3, tv2); // 10. tv4 = tv3 * tv2
1019
- tv5 = Fp.pow(tv4, c5); // 11. tv5 = tv4^c5
1020
- let isQR = Fp.eql(tv5, Fp.ONE); // 12. isQR = tv5 == 1
1021
- tv2 = Fp.mul(tv3, c7); // 13. tv2 = tv3 * c7
1022
- tv5 = Fp.mul(tv4, tv1); // 14. tv5 = tv4 * tv1
1023
- tv3 = Fp.cmov(tv2, tv3, isQR); // 15. tv3 = CMOV(tv2, tv3, isQR)
1024
- tv4 = Fp.cmov(tv5, tv4, isQR); // 16. tv4 = CMOV(tv5, tv4, isQR)
1301
+ let tv2 = F.pow(v, c4); // 2. tv2 = v^c4
1302
+ let tv3 = F.sqr(tv2); // 3. tv3 = tv2^2
1303
+ tv3 = F.mul(tv3, v); // 4. tv3 = tv3 * v
1304
+ let tv5 = F.mul(u, tv3); // 5. tv5 = u * tv3
1305
+ tv5 = F.pow(tv5, c3); // 6. tv5 = tv5^c3
1306
+ tv5 = F.mul(tv5, tv2); // 7. tv5 = tv5 * tv2
1307
+ tv2 = F.mul(tv5, v); // 8. tv2 = tv5 * v
1308
+ tv3 = F.mul(tv5, u); // 9. tv3 = tv5 * u
1309
+ let tv4 = F.mul(tv3, tv2); // 10. tv4 = tv3 * tv2
1310
+ tv5 = F.pow(tv4, c5); // 11. tv5 = tv4^c5
1311
+ let isQR = F.eql(tv5, F.ONE); // 12. isQR = tv5 == 1
1312
+ tv2 = F.mul(tv3, c7); // 13. tv2 = tv3 * c7
1313
+ tv5 = F.mul(tv4, tv1); // 14. tv5 = tv4 * tv1
1314
+ tv3 = F.cmov(tv2, tv3, isQR); // 15. tv3 = CMOV(tv2, tv3, isQR)
1315
+ tv4 = F.cmov(tv5, tv4, isQR); // 16. tv4 = CMOV(tv5, tv4, isQR)
1025
1316
  // 17. for i in (c1, c1 - 1, ..., 2):
1026
1317
  for (let i = c1; i > _1n; i--) {
1027
1318
  let tv5 = i - _2n; // 18. tv5 = i - 2
1028
1319
  tv5 = _2n << (tv5 - _1n); // 19. tv5 = 2^tv5
1029
- let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5
1030
- const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1
1031
- tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1
1032
- tv1 = Fp.mul(tv1, tv1); // 23. tv1 = tv1 * tv1
1033
- tvv5 = Fp.mul(tv4, tv1); // 24. tv5 = tv4 * tv1
1034
- tv3 = Fp.cmov(tv2, tv3, e1); // 25. tv3 = CMOV(tv2, tv3, e1)
1035
- tv4 = Fp.cmov(tvv5, tv4, e1); // 26. tv4 = CMOV(tv5, tv4, e1)
1320
+ let tvv5 = F.pow(tv4, tv5); // 20. tv5 = tv4^tv5
1321
+ const e1 = F.eql(tvv5, F.ONE); // 21. e1 = tv5 == 1
1322
+ tv2 = F.mul(tv3, tv1); // 22. tv2 = tv3 * tv1
1323
+ tv1 = F.mul(tv1, tv1); // 23. tv1 = tv1 * tv1
1324
+ tvv5 = F.mul(tv4, tv1); // 24. tv5 = tv4 * tv1
1325
+ tv3 = F.cmov(tv2, tv3, e1); // 25. tv3 = CMOV(tv2, tv3, e1)
1326
+ tv4 = F.cmov(tvv5, tv4, e1); // 26. tv4 = CMOV(tv5, tv4, e1)
1036
1327
  }
1037
- return { isValid: isQR, value: tv3 };
1328
+ // RFC 9380 Appendix F.2.1.1 defines sqrt_ratio(u, v) for v != 0.
1329
+ // When u = 0 and v != 0, u / v = 0 is square and the computed root is
1330
+ // still 0, so widen only the final flag and keep the full control flow.
1331
+ return { isValid: !F.is0(v) && (isQR || F.is0(u)), value: tv3 };
1038
1332
  };
1039
- if (Fp.ORDER % _4n === _3n) {
1333
+ if (F.ORDER % _4n === _3n) {
1040
1334
  // sqrt_ratio_3mod4(u, v)
1041
- const c1 = (Fp.ORDER - _3n) / _4n; // 1. c1 = (q - 3) / 4 # Integer arithmetic
1042
- const c2 = Fp.sqrt(Fp.neg(Z)); // 2. c2 = sqrt(-Z)
1335
+ const c1 = (F.ORDER - _3n) / _4n; // 1. c1 = (q - 3) / 4 # Integer arithmetic
1336
+ const c2 = F.sqrt(F.neg(Z)); // 2. c2 = sqrt(-Z)
1043
1337
  sqrtRatio = (u: T, v: T) => {
1044
- let tv1 = Fp.sqr(v); // 1. tv1 = v^2
1045
- const tv2 = Fp.mul(u, v); // 2. tv2 = u * v
1046
- tv1 = Fp.mul(tv1, tv2); // 3. tv1 = tv1 * tv2
1047
- let y1 = Fp.pow(tv1, c1); // 4. y1 = tv1^c1
1048
- y1 = Fp.mul(y1, tv2); // 5. y1 = y1 * tv2
1049
- const y2 = Fp.mul(y1, c2); // 6. y2 = y1 * c2
1050
- const tv3 = Fp.mul(Fp.sqr(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v
1051
- const isQR = Fp.eql(tv3, u); // 9. isQR = tv3 == u
1052
- let y = Fp.cmov(y2, y1, isQR); // 10. y = CMOV(y2, y1, isQR)
1053
- return { isValid: isQR, value: y }; // 11. return (isQR, y) isQR ? y : y*c2
1338
+ let tv1 = F.sqr(v); // 1. tv1 = v^2
1339
+ const tv2 = F.mul(u, v); // 2. tv2 = u * v
1340
+ tv1 = F.mul(tv1, tv2); // 3. tv1 = tv1 * tv2
1341
+ let y1 = F.pow(tv1, c1); // 4. y1 = tv1^c1
1342
+ y1 = F.mul(y1, tv2); // 5. y1 = y1 * tv2
1343
+ const y2 = F.mul(y1, c2); // 6. y2 = y1 * c2
1344
+ const tv3 = F.mul(F.sqr(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v
1345
+ const isQR = F.eql(tv3, u); // 9. isQR = tv3 == u
1346
+ let y = F.cmov(y2, y1, isQR); // 10. y = CMOV(y2, y1, isQR)
1347
+ return { isValid: !F.is0(v) && isQR, value: y }; // 11. return (isQR, y) isQR ? y : y*c2
1054
1348
  };
1055
1349
  }
1056
1350
  // No curves uses that
@@ -1059,63 +1353,99 @@ export function SWUFpSqrtRatio<T>(
1059
1353
  }
1060
1354
  /**
1061
1355
  * Simplified Shallue-van de Woestijne-Ulas Method
1062
- * https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2
1356
+ * See {@link https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2 | RFC 9380 section 6.6.2}.
1357
+ * @param Fp - Field implementation.
1358
+ * @param opts - SWU parameters:
1359
+ * - `A`: Curve parameter `A`.
1360
+ * - `B`: Curve parameter `B`.
1361
+ * - `Z`: Simplified SWU map parameter.
1362
+ * @returns Deterministic map-to-curve function.
1363
+ * @throws If the SWU parameters are invalid or the field lacks the required helpers. {@link Error}
1364
+ * @example
1365
+ * Map one field element to a Weierstrass curve point with the SWU recipe.
1366
+ *
1367
+ * ```ts
1368
+ * import { mapToCurveSimpleSWU } from '@noble/curves/abstract/weierstrass.js';
1369
+ * import { Field } from '@noble/curves/abstract/modular.js';
1370
+ * const Fp = Field(17n);
1371
+ * const map = mapToCurveSimpleSWU(Fp, { A: 1n, B: 2n, Z: 3n });
1372
+ * const point = map(5n);
1373
+ * ```
1063
1374
  */
1064
1375
  export function mapToCurveSimpleSWU<T>(
1065
- Fp: IField<T>,
1376
+ Fp: TArg<IField<T>>,
1066
1377
  opts: {
1067
1378
  A: T;
1068
1379
  B: T;
1069
1380
  Z: T;
1070
1381
  }
1071
1382
  ): (u: T) => { x: T; y: T } {
1072
- validateField(Fp);
1383
+ const F = validateField(Fp as IField<T>) as IField<T>;
1073
1384
  const { A, B, Z } = opts;
1074
- if (!Fp.isValid(A) || !Fp.isValid(B) || !Fp.isValid(Z))
1385
+ if (!F.isValidNot0(A) || !F.isValidNot0(B) || !F.isValid(Z))
1075
1386
  throw new Error('mapToCurveSimpleSWU: invalid opts');
1076
- const sqrtRatio = SWUFpSqrtRatio(Fp, Z);
1077
- if (!Fp.isOdd) throw new Error('Field does not have .isOdd()');
1387
+ // RFC 9380 §6.6.2 and Appendix H.2 require:
1388
+ // 1. Z is non-square in F
1389
+ // 2. Z != -1 in F
1390
+ // 3. g(x) - Z is irreducible over F
1391
+ // 4. g(B / (Z * A)) is square in F
1392
+ // We can enforce 1, 2, and 4 with the current field API.
1393
+ // Criterion 3 is not checked here because generic `IField<T>` does not expose
1394
+ // polynomial-ring / irreducibility operations, and this helper is used for
1395
+ // both prime and extension fields.
1396
+ if (F.eql(Z, F.neg(F.ONE)) || FpIsSquare(F, Z))
1397
+ throw new Error('mapToCurveSimpleSWU: invalid opts');
1398
+ // RFC 9380 Appendix H.2 criterion 4: g(B / (Z * A)) is square in F.
1399
+ // x = B / (Z * A)
1400
+ const x = F.mul(B, F.inv(F.mul(Z, A)));
1401
+ // g(x) = x^3 + A*x + B
1402
+ const gx = F.add(F.add(F.mul(F.sqr(x), x), F.mul(A, x)), B);
1403
+ if (!FpIsSquare(F, gx)) throw new Error('mapToCurveSimpleSWU: invalid opts');
1404
+ const sqrtRatio = SWUFpSqrtRatio(F, Z);
1405
+ if (!F.isOdd) throw new Error('Field does not have .isOdd()');
1078
1406
  // Input: u, an element of F.
1079
1407
  // Output: (x, y), a point on E.
1080
1408
  return (u: T): { x: T; y: T } => {
1081
1409
  // prettier-ignore
1082
1410
  let tv1, tv2, tv3, tv4, tv5, tv6, x, y;
1083
- tv1 = Fp.sqr(u); // 1. tv1 = u^2
1084
- tv1 = Fp.mul(tv1, Z); // 2. tv1 = Z * tv1
1085
- tv2 = Fp.sqr(tv1); // 3. tv2 = tv1^2
1086
- tv2 = Fp.add(tv2, tv1); // 4. tv2 = tv2 + tv1
1087
- tv3 = Fp.add(tv2, Fp.ONE); // 5. tv3 = tv2 + 1
1088
- tv3 = Fp.mul(tv3, B); // 6. tv3 = B * tv3
1089
- tv4 = Fp.cmov(Z, Fp.neg(tv2), !Fp.eql(tv2, Fp.ZERO)); // 7. tv4 = CMOV(Z, -tv2, tv2 != 0)
1090
- tv4 = Fp.mul(tv4, A); // 8. tv4 = A * tv4
1091
- tv2 = Fp.sqr(tv3); // 9. tv2 = tv3^2
1092
- tv6 = Fp.sqr(tv4); // 10. tv6 = tv4^2
1093
- tv5 = Fp.mul(tv6, A); // 11. tv5 = A * tv6
1094
- tv2 = Fp.add(tv2, tv5); // 12. tv2 = tv2 + tv5
1095
- tv2 = Fp.mul(tv2, tv3); // 13. tv2 = tv2 * tv3
1096
- tv6 = Fp.mul(tv6, tv4); // 14. tv6 = tv6 * tv4
1097
- tv5 = Fp.mul(tv6, B); // 15. tv5 = B * tv6
1098
- tv2 = Fp.add(tv2, tv5); // 16. tv2 = tv2 + tv5
1099
- x = Fp.mul(tv1, tv3); // 17. x = tv1 * tv3
1411
+ tv1 = F.sqr(u); // 1. tv1 = u^2
1412
+ tv1 = F.mul(tv1, Z); // 2. tv1 = Z * tv1
1413
+ tv2 = F.sqr(tv1); // 3. tv2 = tv1^2
1414
+ tv2 = F.add(tv2, tv1); // 4. tv2 = tv2 + tv1
1415
+ tv3 = F.add(tv2, F.ONE); // 5. tv3 = tv2 + 1
1416
+ tv3 = F.mul(tv3, B); // 6. tv3 = B * tv3
1417
+ tv4 = F.cmov(Z, F.neg(tv2), !F.eql(tv2, F.ZERO)); // 7. tv4 = CMOV(Z, -tv2, tv2 != 0)
1418
+ tv4 = F.mul(tv4, A); // 8. tv4 = A * tv4
1419
+ tv2 = F.sqr(tv3); // 9. tv2 = tv3^2
1420
+ tv6 = F.sqr(tv4); // 10. tv6 = tv4^2
1421
+ tv5 = F.mul(tv6, A); // 11. tv5 = A * tv6
1422
+ tv2 = F.add(tv2, tv5); // 12. tv2 = tv2 + tv5
1423
+ tv2 = F.mul(tv2, tv3); // 13. tv2 = tv2 * tv3
1424
+ tv6 = F.mul(tv6, tv4); // 14. tv6 = tv6 * tv4
1425
+ tv5 = F.mul(tv6, B); // 15. tv5 = B * tv6
1426
+ tv2 = F.add(tv2, tv5); // 16. tv2 = tv2 + tv5
1427
+ x = F.mul(tv1, tv3); // 17. x = tv1 * tv3
1100
1428
  const { isValid, value } = sqrtRatio(tv2, tv6); // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6)
1101
- y = Fp.mul(tv1, u); // 19. y = tv1 * u -> Z * u^3 * y1
1102
- y = Fp.mul(y, value); // 20. y = y * y1
1103
- x = Fp.cmov(x, tv3, isValid); // 21. x = CMOV(x, tv3, is_gx1_square)
1104
- y = Fp.cmov(y, value, isValid); // 22. y = CMOV(y, y1, is_gx1_square)
1105
- const e1 = Fp.isOdd!(u) === Fp.isOdd!(y); // 23. e1 = sgn0(u) == sgn0(y)
1106
- y = Fp.cmov(Fp.neg(y), y, e1); // 24. y = CMOV(-y, y, e1)
1107
- const tv4_inv = FpInvertBatch(Fp, [tv4], true)[0];
1108
- x = Fp.mul(x, tv4_inv); // 25. x = x / tv4
1429
+ y = F.mul(tv1, u); // 19. y = tv1 * u -> Z * u^3 * y1
1430
+ y = F.mul(y, value); // 20. y = y * y1
1431
+ x = F.cmov(x, tv3, isValid); // 21. x = CMOV(x, tv3, is_gx1_square)
1432
+ y = F.cmov(y, value, isValid); // 22. y = CMOV(y, y1, is_gx1_square)
1433
+ const e1 = F.isOdd!(u) === F.isOdd!(y); // 23. e1 = sgn0(u) == sgn0(y)
1434
+ y = F.cmov(F.neg(y), y, e1); // 24. y = CMOV(-y, y, e1)
1435
+ const tv4_inv = FpInvertBatch(F, [tv4], true)[0];
1436
+ x = F.mul(x, tv4_inv); // 25. x = x / tv4
1109
1437
  return { x, y };
1110
1438
  };
1111
1439
  }
1112
1440
 
1113
- function getWLengths<T>(Fp: IField<T>, Fn: IField<bigint>) {
1441
+ function getWLengths<T>(Fp: TArg<IField<T>>, Fn: TArg<IField<bigint>>) {
1114
1442
  return {
1115
1443
  secretKey: Fn.BYTES,
1116
1444
  publicKey: 1 + Fp.BYTES,
1117
1445
  publicKeyUncompressed: 1 + 2 * Fp.BYTES,
1118
1446
  publicKeyHasPrefix: true,
1447
+ // Raw compact `(r || s)` signature width; DER and recovered signatures use
1448
+ // different lengths outside this helper.
1119
1449
  signature: 2 * Fn.BYTES,
1120
1450
  };
1121
1451
  }
@@ -1123,16 +1453,34 @@ function getWLengths<T>(Fp: IField<T>, Fn: IField<bigint>) {
1123
1453
  /**
1124
1454
  * Sometimes users only need getPublicKey, getSharedSecret, and secret key handling.
1125
1455
  * This helper ensures no signature functionality is present. Less code, smaller bundle size.
1456
+ * @param Point - Weierstrass point constructor.
1457
+ * @param ecdhOpts - Optional randomness helpers:
1458
+ * - `randomBytes` (optional): Optional RNG override.
1459
+ * @returns ECDH helper namespace.
1460
+ * @example
1461
+ * Sometimes users only need getPublicKey, getSharedSecret, and secret key handling.
1462
+ *
1463
+ * ```ts
1464
+ * import { ecdh } from '@noble/curves/abstract/weierstrass.js';
1465
+ * import { p256 } from '@noble/curves/nist.js';
1466
+ * const dh = ecdh(p256.Point);
1467
+ * const alice = dh.keygen();
1468
+ * const shared = dh.getSharedSecret(alice.secretKey, alice.publicKey);
1469
+ * ```
1126
1470
  */
1127
1471
  export function ecdh(
1128
1472
  Point: WeierstrassPointCons<bigint>,
1129
- ecdhOpts: { randomBytes?: (bytesLength?: number) => Uint8Array } = {}
1473
+ ecdhOpts: TArg<{ randomBytes?: (bytesLength?: number) => TRet<Uint8Array> }> = {}
1130
1474
  ): ECDH {
1131
1475
  const { Fn } = Point;
1132
- const randomBytes_ = ecdhOpts.randomBytes || wcRandomBytes;
1133
- const lengths = Object.assign(getWLengths(Point.Fp, Fn), { seed: getMinHashLength(Fn.ORDER) });
1476
+ const randomBytes_ = ecdhOpts.randomBytes === undefined ? wcRandomBytes : ecdhOpts.randomBytes;
1477
+ // Keep the advertised seed length aligned with mapHashToField(), which keeps a hard 16-byte
1478
+ // minimum even on toy curves.
1479
+ const lengths = Object.assign(getWLengths(Point.Fp, Fn), {
1480
+ seed: Math.max(getMinHashLength(Fn.ORDER), 16),
1481
+ });
1134
1482
 
1135
- function isValidSecretKey(secretKey: Uint8Array) {
1483
+ function isValidSecretKey(secretKey: TArg<Uint8Array>) {
1136
1484
  try {
1137
1485
  const num = Fn.fromBytes(secretKey);
1138
1486
  return Fn.isValidNot0(num);
@@ -1141,7 +1489,7 @@ export function ecdh(
1141
1489
  }
1142
1490
  }
1143
1491
 
1144
- function isValidPublicKey(publicKey: Uint8Array, isCompressed?: boolean): boolean {
1492
+ function isValidPublicKey(publicKey: TArg<Uint8Array>, isCompressed?: boolean): boolean {
1145
1493
  const { publicKey: comp, publicKeyUncompressed } = lengths;
1146
1494
  try {
1147
1495
  const l = publicKey.length;
@@ -1157,43 +1505,52 @@ export function ecdh(
1157
1505
  * Produces cryptographically secure secret key from random of size
1158
1506
  * (groupLen + ceil(groupLen / 2)) with modulo bias being negligible.
1159
1507
  */
1160
- function randomSecretKey(seed = randomBytes_(lengths.seed)): Uint8Array {
1161
- return mapHashToField(abytes(seed, lengths.seed, 'seed'), Fn.ORDER);
1508
+ function randomSecretKey(seed?: TArg<Uint8Array>): TRet<Uint8Array> {
1509
+ seed = seed === undefined ? randomBytes_(lengths.seed) : seed;
1510
+ return mapHashToField(abytes(seed, lengths.seed, 'seed'), Fn.ORDER) as TRet<Uint8Array>;
1162
1511
  }
1163
1512
 
1164
1513
  /**
1165
1514
  * Computes public key for a secret key. Checks for validity of the secret key.
1166
- * @param isCompressed whether to return compact (default), or full key
1515
+ * @param isCompressed - whether to return compact (default), or full key
1167
1516
  * @returns Public key, full when isCompressed=false; short when isCompressed=true
1168
1517
  */
1169
- function getPublicKey(secretKey: Uint8Array, isCompressed = true): Uint8Array {
1518
+ function getPublicKey(secretKey: TArg<Uint8Array>, isCompressed = true): TRet<Uint8Array> {
1170
1519
  return Point.BASE.multiply(Fn.fromBytes(secretKey)).toBytes(isCompressed);
1171
1520
  }
1172
1521
 
1173
1522
  /**
1174
1523
  * Quick and dirty check for item being public key. Does not validate hex, or being on-curve.
1175
1524
  */
1176
- function isProbPub(item: Uint8Array): boolean | undefined {
1525
+ function isProbPub(item: TArg<Uint8Array>): boolean | undefined {
1177
1526
  const { secretKey, publicKey, publicKeyUncompressed } = lengths;
1527
+ const allowedLengths = (Fn as { _lengths?: readonly number[] })._lengths;
1178
1528
  if (!isBytes(item)) return undefined;
1179
- if (('_lengths' in Fn && Fn._lengths) || secretKey === publicKey) return undefined;
1180
1529
  const l = abytes(item, undefined, 'key').length;
1181
- return l === publicKey || l === publicKeyUncompressed;
1530
+ const isPub = l === publicKey || l === publicKeyUncompressed;
1531
+ const isSec = l === secretKey || !!allowedLengths?.includes(l);
1532
+ // P-521 accepts both 65- and 66-byte secret keys, so overlapping lengths stay ambiguous.
1533
+ if (isPub && isSec) return undefined;
1534
+ return isPub;
1182
1535
  }
1183
1536
 
1184
1537
  /**
1185
1538
  * ECDH (Elliptic Curve Diffie Hellman).
1186
- * Computes shared public key from secret key A and public key B.
1539
+ * Computes encoded shared point from secret key A and public key B.
1187
1540
  * Checks: 1) secret key validity 2) shared key is on-curve.
1188
- * Does NOT hash the result.
1189
- * @param isCompressed whether to return compact (default), or full key
1190
- * @returns shared public key
1541
+ * Does NOT hash the result or expose the SEC 1 x-coordinate-only `z`.
1542
+ * Returns the encoded shared point on purpose: callers that need `x_P`
1543
+ * can derive it from the encoded point, but `x_P` alone cannot recover the
1544
+ * point/parity back.
1545
+ * This helper only exposes the fully validated public-key path, not cofactor DH.
1546
+ * @param isCompressed - whether to return compact (default), or full key
1547
+ * @returns shared point encoding
1191
1548
  */
1192
1549
  function getSharedSecret(
1193
- secretKeyA: Uint8Array,
1194
- publicKeyB: Uint8Array,
1550
+ secretKeyA: TArg<Uint8Array>,
1551
+ publicKeyB: TArg<Uint8Array>,
1195
1552
  isCompressed = true
1196
- ): Uint8Array {
1553
+ ): TRet<Uint8Array> {
1197
1554
  if (isProbPub(secretKeyA) === true) throw new Error('first arg must be private key');
1198
1555
  if (isProbPub(publicKeyB) === false) throw new Error('second arg must be public key');
1199
1556
  const s = Fn.fromBytes(secretKeyA);
@@ -1207,6 +1564,8 @@ export function ecdh(
1207
1564
  randomSecretKey,
1208
1565
  };
1209
1566
  const keygen = createKeygen(randomSecretKey, getPublicKey);
1567
+ Object.freeze(utils);
1568
+ Object.freeze(lengths);
1210
1569
 
1211
1570
  return Object.freeze({ getPublicKey, getSharedSecret, keygen, Point, utils, lengths });
1212
1571
  }
@@ -1214,24 +1573,38 @@ export function ecdh(
1214
1573
  /**
1215
1574
  * Creates ECDSA signing interface for given elliptic curve `Point` and `hash` function.
1216
1575
  *
1217
- * @param Point created using {@link weierstrass} function
1218
- * @param hash used for 1) message prehash-ing 2) k generation in `sign`, using hmac_drbg(hash)
1219
- * @param ecdsaOpts rarely needed, see {@link ECDSAOpts}
1576
+ * @param Point - created using {@link weierstrass} function
1577
+ * @param hash - used for 1) message prehash-ing 2) k generation in `sign`, using hmac_drbg(hash)
1578
+ * @param ecdsaOpts - rarely needed, see {@link ECDSAOpts}:
1579
+ * - `lowS`: Default low-S policy.
1580
+ * - `hmac`: HMAC implementation used by RFC6979 DRBG.
1581
+ * - `randomBytes`: Optional RNG override.
1582
+ * - `bits2int`: Optional hash-to-int conversion override.
1583
+ * - `bits2int_modN`: Optional hash-to-int-mod-n conversion override.
1220
1584
  *
1585
+ * @returns ECDSA helper namespace.
1221
1586
  * @example
1222
- * ```js
1223
- * const p256_Point = weierstrass(...);
1224
- * const p256_sha256 = ecdsa(p256_Point, sha256);
1225
- * const p256_sha224 = ecdsa(p256_Point, sha224);
1226
- * const p256_sha224_r = ecdsa(p256_Point, sha224, { randomBytes: (length) => { ... } });
1587
+ * Create an ECDSA signer/verifier bundle for one curve implementation.
1588
+ *
1589
+ * ```ts
1590
+ * import { ecdsa } from '@noble/curves/abstract/weierstrass.js';
1591
+ * import { p256 } from '@noble/curves/nist.js';
1592
+ * import { sha256 } from '@noble/hashes/sha2.js';
1593
+ * const p256ecdsa = ecdsa(p256.Point, sha256);
1594
+ * const { secretKey, publicKey } = p256ecdsa.keygen();
1595
+ * const msg = new TextEncoder().encode('hello noble');
1596
+ * const sig = p256ecdsa.sign(msg, secretKey);
1597
+ * const isValid = p256ecdsa.verify(sig, msg, publicKey);
1227
1598
  * ```
1228
1599
  */
1229
1600
  export function ecdsa(
1230
1601
  Point: WeierstrassPointCons<bigint>,
1231
- hash: CHash,
1232
- ecdsaOpts: ECDSAOpts = {}
1602
+ hash: TArg<CHash>,
1603
+ ecdsaOpts: TArg<ECDSAOpts> = {}
1233
1604
  ): ECDSA {
1234
- ahash(hash);
1605
+ // Custom hash / bits2int hooks are treated as pure functions over validated caller-owned bytes.
1606
+ const hash_ = hash as CHash;
1607
+ ahash(hash_);
1235
1608
  validateObject(
1236
1609
  ecdsaOpts,
1237
1610
  {},
@@ -1244,8 +1617,11 @@ export function ecdsa(
1244
1617
  }
1245
1618
  );
1246
1619
  ecdsaOpts = Object.assign({}, ecdsaOpts);
1247
- const randomBytes = ecdsaOpts.randomBytes || wcRandomBytes;
1248
- const hmac = ecdsaOpts.hmac || ((key, msg) => nobleHmac(hash, key, msg));
1620
+ const randomBytes = ecdsaOpts.randomBytes === undefined ? wcRandomBytes : ecdsaOpts.randomBytes;
1621
+ const hmac =
1622
+ ecdsaOpts.hmac === undefined
1623
+ ? (key: TArg<Uint8Array>, msg: TArg<Uint8Array>) => nobleHmac(hash_, key, msg)
1624
+ : (ecdsaOpts.hmac as HmacFn);
1249
1625
 
1250
1626
  const { Fp, Fn } = Point;
1251
1627
  const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn;
@@ -1256,7 +1632,11 @@ export function ecdsa(
1256
1632
  format: 'compact' as ECDSASignatureFormat,
1257
1633
  extraEntropy: false,
1258
1634
  };
1259
- const hasLargeCofactor = CURVE_ORDER * _2n < Fp.ORDER; // Won't CURVE().h > 2n be more effective?
1635
+ // SEC 1 4.1.6 public-key recovery tries x = r + jn for j = 0..h. Our recovered-signature
1636
+ // format only stores one overflow bit, so it can only distinguish q.x = r from q.x = r + n.
1637
+ // A third lift would have the form q.x = r + 2n. Since valid ECDSA r is in 1..n-1, the
1638
+ // smallest such lift is 1 + 2n, not 2n.
1639
+ const hasLargeRecoveryLifts = CURVE_ORDER * _2n + _1n < Fp.ORDER;
1260
1640
 
1261
1641
  function isBiggerThanHalfOrder(number: bigint) {
1262
1642
  const HALF = CURVE_ORDER >> _1n;
@@ -1267,19 +1647,18 @@ export function ecdsa(
1267
1647
  throw new Error(`invalid signature ${title}: out of range 1..Point.Fn.ORDER`);
1268
1648
  return num;
1269
1649
  }
1270
- function assertSmallCofactor(): void {
1271
- // ECDSA recovery is hard for cofactor > 1 curves.
1272
- // In sign, `r = q.x mod n`, and here we recover q.x from r.
1273
- // While recovering q.x >= n, we need to add r+n for cofactor=1 curves.
1274
- // However, for cofactor>1, r+n may not get q.x:
1275
- // r+n*i would need to be done instead where i is unknown.
1650
+ function assertRecoverableCurve(): void {
1651
+ // ECDSA recovery only supports curves where the current recovery id can distinguish
1652
+ // q.x = r and q.x = r + n; larger lifts may need additional `r + n*i` branches.
1653
+ // SEC 1 4.1.6 recovers candidates via x = r + jn, but this format only encodes j = 0 or 1.
1654
+ // The next possible candidate is q.x = r + 2n, and its smallest valid value is 1 + 2n.
1276
1655
  // To easily get i, we either need to:
1277
1656
  // a. increase amount of valid recid values (4, 5...); OR
1278
- // b. prohibit non-prime-order signatures (recid > 1).
1279
- if (hasLargeCofactor)
1657
+ // b. prohibit recovered signatures for those curves.
1658
+ if (hasLargeRecoveryLifts)
1280
1659
  throw new Error('"recovered" sig type is not supported for cofactor >2 curves');
1281
1660
  }
1282
- function validateSigLength(bytes: Uint8Array, format: ECDSASignatureFormat) {
1661
+ function validateSigLength(bytes: TArg<Uint8Array>, format: ECDSASignatureFormat) {
1283
1662
  validateSigFormat(format);
1284
1663
  const size = lengths.signature!;
1285
1664
  const sizer = format === 'compact' ? size : format === 'recovered' ? size + 1 : undefined;
@@ -1298,7 +1677,7 @@ export function ecdsa(
1298
1677
  this.r = validateRS('r', r); // r in [1..N-1];
1299
1678
  this.s = validateRS('s', s); // s in [1..N-1];
1300
1679
  if (recovery != null) {
1301
- assertSmallCofactor();
1680
+ assertRecoverableCurve();
1302
1681
  if (![0, 1, 2, 3].includes(recovery)) throw new Error('invalid recovery id');
1303
1682
  this.recovery = recovery;
1304
1683
  }
@@ -1306,7 +1685,7 @@ export function ecdsa(
1306
1685
  }
1307
1686
 
1308
1687
  static fromBytes(
1309
- bytes: Uint8Array,
1688
+ bytes: TArg<Uint8Array>,
1310
1689
  format: ECDSASignatureFormat = defaultSigOpts.format
1311
1690
  ): Signature {
1312
1691
  validateSigLength(bytes, format);
@@ -1340,7 +1719,9 @@ export function ecdsa(
1340
1719
  return new Signature(this.r, this.s, recovery) as RecoveredSignature;
1341
1720
  }
1342
1721
 
1343
- recoverPublicKey(messageHash: Uint8Array): WeierstrassPoint<bigint> {
1722
+ // Unlike the top-level helper below, this method expects a digest that has
1723
+ // already been hashed to the curve's message representative.
1724
+ recoverPublicKey(messageHash: TArg<Uint8Array>): WeierstrassPoint<bigint> {
1344
1725
  const { r, s } = this;
1345
1726
  const recovery = this.assertRecovery();
1346
1727
  const radj = recovery === 2 || recovery === 3 ? r + CURVE_ORDER : r;
@@ -1363,17 +1744,17 @@ export function ecdsa(
1363
1744
  return isBiggerThanHalfOrder(this.s);
1364
1745
  }
1365
1746
 
1366
- toBytes(format: ECDSASignatureFormat = defaultSigOpts.format) {
1747
+ toBytes(format: ECDSASignatureFormat = defaultSigOpts.format): TRet<Uint8Array> {
1367
1748
  validateSigFormat(format);
1368
- if (format === 'der') return hexToBytes(DER.hexFromSig(this));
1749
+ if (format === 'der') return hexToBytes(DER.hexFromSig(this)) as TRet<Uint8Array>;
1369
1750
  const { r, s } = this;
1370
1751
  const rb = Fn.toBytes(r);
1371
1752
  const sb = Fn.toBytes(s);
1372
1753
  if (format === 'recovered') {
1373
- assertSmallCofactor();
1374
- return concatBytes(Uint8Array.of(this.assertRecovery()), rb, sb);
1754
+ assertRecoverableCurve();
1755
+ return concatBytes(Uint8Array.of(this.assertRecovery()), rb, sb) as TRet<Uint8Array>;
1375
1756
  }
1376
- return concatBytes(rb, sb);
1757
+ return concatBytes(rb, sb) as TRet<Uint8Array>;
1377
1758
  }
1378
1759
 
1379
1760
  toHex(format?: ECDSASignatureFormat) {
@@ -1381,39 +1762,44 @@ export function ecdsa(
1381
1762
  }
1382
1763
  }
1383
1764
  type RecoveredSignature = Signature & { recovery: number };
1765
+ Object.freeze(Signature.prototype);
1766
+ Object.freeze(Signature);
1384
1767
 
1385
1768
  // RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets.
1386
1769
  // FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which matches bits2int.
1387
1770
  // bits2int can produce res>N, we can do mod(res, N) since the bitLen is the same.
1388
1771
  // int2octets can't be used; pads small msgs with 0: unacceptatble for trunc as per RFC vectors
1389
- const bits2int =
1390
- ecdsaOpts.bits2int ||
1391
- function bits2int_def(bytes: Uint8Array): bigint {
1392
- // Our custom check "just in case", for protection against DoS
1393
- if (bytes.length > 8192) throw new Error('input is too large');
1394
- // For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m)
1395
- // for some cases, since bytes.length * 8 is not actual bitLength.
1396
- const num = bytesToNumberBE(bytes); // check for == u8 done here
1397
- const delta = bytes.length * 8 - fnBits; // truncate to nBitLength leftmost bits
1398
- return delta > 0 ? num >> BigInt(delta) : num;
1399
- };
1400
- const bits2int_modN =
1401
- ecdsaOpts.bits2int_modN ||
1402
- function bits2int_modN_def(bytes: Uint8Array): bigint {
1403
- return Fn.create(bits2int(bytes)); // can't use bytesToNumberBE here
1404
- };
1405
- // Pads output with zero as per spec
1772
+ const bits2int: (bytes: TArg<Uint8Array>) => bigint =
1773
+ ecdsaOpts.bits2int === undefined
1774
+ ? function bits2int_def(bytes: TArg<Uint8Array>): bigint {
1775
+ // Our custom check "just in case", for protection against DoS
1776
+ if (bytes.length > 8192) throw new Error('input is too large');
1777
+ // For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m)
1778
+ // for some cases, since bytes.length * 8 is not actual bitLength.
1779
+ const num = bytesToNumberBE(bytes); // check for == u8 done here
1780
+ const delta = bytes.length * 8 - fnBits; // truncate to nBitLength leftmost bits
1781
+ return delta > 0 ? num >> BigInt(delta) : num;
1782
+ }
1783
+ : (ecdsaOpts.bits2int as (bytes: TArg<Uint8Array>) => bigint);
1784
+ const bits2int_modN: (bytes: TArg<Uint8Array>) => bigint =
1785
+ ecdsaOpts.bits2int_modN === undefined
1786
+ ? function bits2int_modN_def(bytes: TArg<Uint8Array>): bigint {
1787
+ return Fn.create(bits2int(bytes)); // can't use bytesToNumberBE here
1788
+ }
1789
+ : (ecdsaOpts.bits2int_modN as (bytes: TArg<Uint8Array>) => bigint);
1406
1790
  const ORDER_MASK = bitMask(fnBits);
1791
+ // Pads output with zero as per spec.
1407
1792
  /** Converts to bytes. Checks if num in `[0..ORDER_MASK-1]` e.g.: `[0..2^256-1]`. */
1408
- function int2octets(num: bigint): Uint8Array {
1409
- // IMPORTANT: the check ensures working for case `Fn.BYTES != Fn.BITS * 8`
1793
+ function int2octets(num: bigint): TRet<Uint8Array> {
1410
1794
  aInRange('num < 2^' + fnBits, num, _0n, ORDER_MASK);
1411
- return Fn.toBytes(num);
1795
+ return Fn.toBytes(num) as TRet<Uint8Array>;
1412
1796
  }
1413
1797
 
1414
- function validateMsgAndHash(message: Uint8Array, prehash: boolean) {
1798
+ function validateMsgAndHash(message: TArg<Uint8Array>, prehash: boolean): TRet<Uint8Array> {
1415
1799
  abytes(message, undefined, 'message');
1416
- return prehash ? abytes(hash(message), undefined, 'prehashed message') : message;
1800
+ return (
1801
+ prehash ? abytes(hash_(message), undefined, 'prehashed message') : message
1802
+ ) as TRet<Uint8Array>;
1417
1803
  }
1418
1804
 
1419
1805
  /**
@@ -1424,7 +1810,11 @@ export function ecdsa(
1424
1810
  * Warning: we cannot assume here that message has same amount of bytes as curve order,
1425
1811
  * this will be invalid at least for P521. Also it can be bigger for P224 + SHA256.
1426
1812
  */
1427
- function prepSig(message: Uint8Array, secretKey: Uint8Array, opts: ECDSASignOpts) {
1813
+ function prepSig(
1814
+ message: TArg<Uint8Array>,
1815
+ secretKey: TArg<Uint8Array>,
1816
+ opts: TArg<ECDSASignOpts>
1817
+ ) {
1428
1818
  const { lowS, prehash, extraEntropy } = validateSigOpts(opts, defaultSigOpts);
1429
1819
  message = validateMsgAndHash(message, prehash); // RFC6979 3.2 A: h1 = H(m)
1430
1820
  // We can't later call bits2octets, since nested bits2int is broken for curves
@@ -1433,7 +1823,7 @@ export function ecdsa(
1433
1823
  const h1int = bits2int_modN(message);
1434
1824
  const d = Fn.fromBytes(secretKey); // validate secret key, convert to bigint
1435
1825
  if (!Fn.isValidNot0(d)) throw new Error('invalid private key');
1436
- const seedArgs = [int2octets(d), int2octets(h1int)];
1826
+ const seedArgs: TArg<Uint8Array>[] = [int2octets(d), int2octets(h1int)];
1437
1827
  // extraEntropy. RFC6979 3.6: additional k' (optional).
1438
1828
  if (extraEntropy != null && extraEntropy !== false) {
1439
1829
  // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
@@ -1441,7 +1831,7 @@ export function ecdsa(
1441
1831
  const e = extraEntropy === true ? randomBytes(lengths.secretKey) : extraEntropy;
1442
1832
  seedArgs.push(abytes(e, undefined, 'extraEntropy')); // check for being bytes
1443
1833
  }
1444
- const seed = concatBytes(...seedArgs); // Step D of RFC6979 3.2
1834
+ const seed = concatBytes(...seedArgs) as TRet<Uint8Array>; // Step D of RFC6979 3.2
1445
1835
  const m = h1int; // no need to call bits2int second time here, it is inside truncateHash!
1446
1836
  // Converts signature params into point w r/s, checks result for validity.
1447
1837
  // To transform k => Signature:
@@ -1451,7 +1841,7 @@ export function ecdsa(
1451
1841
  // Can use scalar blinding b^-1(bm + bdr) where b ∈ [1,q−1] according to
1452
1842
  // https://tches.iacr.org/index.php/TCHES/article/view/7337/6509. We've decided against it:
1453
1843
  // a) dependency on CSPRNG b) 15% slowdown c) doesn't really help since bigints are not CT
1454
- function k2sig(kBytes: Uint8Array): Signature | undefined {
1844
+ function k2sig(kBytes: TArg<Uint8Array>): Signature | undefined {
1455
1845
  // RFC 6979 Section 3.2, step 3: k = bits2int(T)
1456
1846
  // Important: all mod() calls here must be done over N
1457
1847
  const k = bits2int(kBytes); // Cannot use fields methods, since it is group element
@@ -1468,13 +1858,15 @@ export function ecdsa(
1468
1858
  normS = Fn.neg(s); // if lowS was passed, ensure s is always in the bottom half of N
1469
1859
  recovery ^= 1;
1470
1860
  }
1471
- return new Signature(r, normS, hasLargeCofactor ? undefined : recovery);
1861
+ return new Signature(r, normS, hasLargeRecoveryLifts ? undefined : recovery);
1472
1862
  }
1473
1863
  return { seed, k2sig };
1474
1864
  }
1475
1865
 
1476
1866
  /**
1477
- * Signs message hash with a secret key.
1867
+ * Signs a message or message hash with a secret key.
1868
+ * With the default `prehash: true`, raw message bytes are hashed internally;
1869
+ * only `{ prehash: false }` expects a caller-supplied digest.
1478
1870
  *
1479
1871
  * ```
1480
1872
  * sign(m, d) where
@@ -1484,9 +1876,13 @@ export function ecdsa(
1484
1876
  * s = (m + dr) / k mod n
1485
1877
  * ```
1486
1878
  */
1487
- function sign(message: Uint8Array, secretKey: Uint8Array, opts: ECDSASignOpts = {}): Uint8Array {
1879
+ function sign(
1880
+ message: TArg<Uint8Array>,
1881
+ secretKey: TArg<Uint8Array>,
1882
+ opts: TArg<ECDSASignOpts> = {}
1883
+ ): TRet<Uint8Array> {
1488
1884
  const { seed, k2sig } = prepSig(message, secretKey, opts); // Steps A, D of RFC6979 3.2.
1489
- const drbg = createHmacDrbg<Signature>(hash.outputLen, Fn.BYTES, hmac);
1885
+ const drbg = createHmacDrbg<Signature>(hash_.outputLen, Fn.BYTES, hmac);
1490
1886
  const sig = drbg(seed, k2sig); // Steps B, C, D, E, F, G
1491
1887
  return sig.toBytes(opts.format);
1492
1888
  }
@@ -1505,10 +1901,10 @@ export function ecdsa(
1505
1901
  * ```
1506
1902
  */
1507
1903
  function verify(
1508
- signature: Uint8Array,
1509
- message: Uint8Array,
1510
- publicKey: Uint8Array,
1511
- opts: ECDSAVerifyOpts = {}
1904
+ signature: TArg<Uint8Array>,
1905
+ message: TArg<Uint8Array>,
1906
+ publicKey: TArg<Uint8Array>,
1907
+ opts: TArg<ECDSAVerifyOpts> = {}
1512
1908
  ): boolean {
1513
1909
  const { lowS, prehash, format } = validateSigOpts(opts, defaultSigOpts);
1514
1910
  publicKey = abytes(publicKey, undefined, 'publicKey');
@@ -1537,10 +1933,12 @@ export function ecdsa(
1537
1933
  }
1538
1934
 
1539
1935
  function recoverPublicKey(
1540
- signature: Uint8Array,
1541
- message: Uint8Array,
1542
- opts: ECDSARecoverOpts = {}
1543
- ): Uint8Array {
1936
+ signature: TArg<Uint8Array>,
1937
+ message: TArg<Uint8Array>,
1938
+ opts: TArg<ECDSARecoverOpts> = {}
1939
+ ): TRet<Uint8Array> {
1940
+ // Top-level recovery mirrors `sign()` / `verify()`: it hashes raw message
1941
+ // bytes first unless the caller passes `{ prehash: false }`.
1544
1942
  const { prehash } = validateSigOpts(opts, defaultSigOpts);
1545
1943
  message = validateMsgAndHash(message, prehash);
1546
1944
  return Signature.fromBytes(signature, 'recovered').recoverPublicKey(message).toBytes();
@@ -1557,6 +1955,6 @@ export function ecdsa(
1557
1955
  verify,
1558
1956
  recoverPublicKey,
1559
1957
  Signature,
1560
- hash,
1958
+ hash: hash_,
1561
1959
  }) satisfies Signer;
1562
1960
  }