@noble/curves 2.0.0 → 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 +89 -24
  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 +327 -10
  15. package/abstract/fft.d.ts.map +1 -1
  16. package/abstract/fft.js +155 -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 +135 -14
  59. package/ed25519.d.ts.map +1 -1
  60. package/ed25519.js +207 -41
  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 +34 -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 +356 -69
  82. package/src/abstract/curve.ts +327 -44
  83. package/src/abstract/edwards.ts +367 -143
  84. package/src/abstract/fft.ts +371 -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 +234 -56
  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
@@ -5,12 +5,13 @@
5
5
  * @module
6
6
  */
7
7
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
8
- import type { CHash } from '../utils.ts';
8
+ import type { CHash, TArg, TRet } from '../utils.ts';
9
9
  import {
10
10
  abytes,
11
11
  asafenumber,
12
12
  asciiToBytes,
13
13
  bytesToNumberBE,
14
+ copyBytes,
14
15
  concatBytes,
15
16
  isBytes,
16
17
  validateObject,
@@ -18,7 +19,17 @@ import {
18
19
  import type { AffinePoint, PC_ANY, PC_F, PC_P } from './curve.ts';
19
20
  import { FpInvertBatch, mod, type IField } from './modular.ts';
20
21
 
22
+ /** ASCII domain-separation tag or raw bytes. */
21
23
  export type AsciiOrBytes = string | Uint8Array;
24
+ type H2CDefaults = {
25
+ DST: AsciiOrBytes;
26
+ expand: 'xmd' | 'xof';
27
+ hash: CHash;
28
+ p: bigint;
29
+ m: number;
30
+ k: number;
31
+ encodeDST?: AsciiOrBytes;
32
+ };
22
33
 
23
34
  /**
24
35
  * * `DST` is a domain separation tag, defined in section 2.2.5
@@ -29,83 +40,154 @@ export type AsciiOrBytes = string | Uint8Array;
29
40
  * * `hash` conforming to `utils.CHash` interface, with `outputLen` / `blockLen` props
30
41
  */
31
42
  export type H2COpts = {
43
+ /** Domain separation tag. */
32
44
  DST: AsciiOrBytes;
45
+ /** Expander family used by RFC 9380. */
33
46
  expand: 'xmd' | 'xof';
47
+ /** Hash or XOF implementation used by the expander. */
34
48
  hash: CHash;
49
+ /** Base-field characteristic. */
35
50
  p: bigint;
51
+ /** Extension degree (`1` for prime fields). */
36
52
  m: number;
53
+ /** Target security level in bits. */
37
54
  k: number;
38
55
  };
56
+ /** Hash-only subset of RFC 9380 options used by per-call overrides. */
39
57
  export type H2CHashOpts = {
58
+ /** Expander family used by RFC 9380. */
40
59
  expand: 'xmd' | 'xof';
60
+ /** Hash or XOF implementation used by the expander. */
41
61
  hash: CHash;
42
62
  };
63
+ /**
64
+ * Map one hash-to-field output tuple onto affine curve coordinates.
65
+ * Implementations receive the validated scalar tuple by reference for performance and MUST treat it
66
+ * as read-only. Callers that need scratch space should copy before mutating.
67
+ * @param scalar - Field-element tuple produced by `hash_to_field`.
68
+ * @returns Affine point before subgroup clearing.
69
+ */
43
70
  export type MapToCurve<T> = (scalar: bigint[]) => AffinePoint<T>;
44
71
 
45
72
  // Separated from initialization opts, so users won't accidentally change per-curve parameters
46
73
  // (changing DST is ok!)
47
- export type H2CDSTOpts = { DST: AsciiOrBytes };
74
+ /** Per-call override for the domain-separation tag. */
75
+ export type H2CDSTOpts = {
76
+ /** Domain-separation tag override. */
77
+ DST: AsciiOrBytes;
78
+ };
79
+ /** Base hash-to-curve helpers shared by `hashToCurve` and `encodeToCurve`. */
48
80
  export type H2CHasherBase<PC extends PC_ANY> = {
49
- hashToCurve(msg: Uint8Array, options?: H2CDSTOpts): PC_P<PC>;
50
- hashToScalar(msg: Uint8Array, options?: H2CDSTOpts): bigint;
51
- deriveToCurve?(msg: Uint8Array, options?: H2CDSTOpts): PC_P<PC>;
81
+ /**
82
+ * Hash arbitrary bytes to one curve point.
83
+ * @param msg - Input message bytes.
84
+ * @param options - Optional domain-separation override. See {@link H2CDSTOpts}.
85
+ * @returns Curve point after hash-to-curve.
86
+ */
87
+ hashToCurve(msg: TArg<Uint8Array>, options?: TArg<H2CDSTOpts>): PC_P<PC>;
88
+ /**
89
+ * Hash arbitrary bytes to one scalar.
90
+ * @param msg - Input message bytes.
91
+ * @param options - Optional domain-separation override. See {@link H2CDSTOpts}.
92
+ * @returns Scalar reduced into the target field.
93
+ */
94
+ hashToScalar(msg: TArg<Uint8Array>, options?: TArg<H2CDSTOpts>): bigint;
95
+ /**
96
+ * Derive one curve point from non-uniform bytes without the random-oracle
97
+ * guarantees of `hashToCurve`.
98
+ * Accepts the same arguments as `hashToCurve`, but runs the encode-to-curve
99
+ * path instead of the random-oracle construction.
100
+ */
101
+ deriveToCurve?(msg: TArg<Uint8Array>, options?: TArg<H2CDSTOpts>): PC_P<PC>;
102
+ /** Point constructor for the target curve. */
52
103
  Point: PC;
53
104
  };
54
105
  /**
55
- * RFC 9380 methods, with cofactor clearing. See https://www.rfc-editor.org/rfc/rfc9380#section-3.
106
+ * RFC 9380 methods, with cofactor clearing. See {@link https://www.rfc-editor.org/rfc/rfc9380#section-3 | RFC 9380 section 3}.
56
107
  *
57
108
  * * hashToCurve: `map(hash(input))`, encodes RANDOM bytes to curve (WITH hashing)
58
109
  * * encodeToCurve: `map(hash(input))`, encodes NON-UNIFORM bytes to curve (WITH hashing)
59
110
  * * mapToCurve: `map(scalars)`, encodes NON-UNIFORM scalars to curve (NO hashing)
60
111
  */
61
112
  export type H2CHasher<PC extends PC_ANY> = H2CHasherBase<PC> & {
62
- encodeToCurve(msg: Uint8Array, options?: H2CDSTOpts): PC_P<PC>;
113
+ /**
114
+ * Encode non-uniform bytes to one curve point.
115
+ * @param msg - Input message bytes.
116
+ * @param options - Optional domain-separation override. See {@link H2CDSTOpts}.
117
+ * @returns Curve point after encode-to-curve.
118
+ */
119
+ encodeToCurve(msg: TArg<Uint8Array>, options?: TArg<H2CDSTOpts>): PC_P<PC>;
120
+ /** Deterministic map from `hash_to_field` tuples into affine coordinates. */
63
121
  mapToCurve: MapToCurve<PC_F<PC>>;
64
- defaults: H2COpts & { encodeDST?: AsciiOrBytes };
122
+ /** Default RFC 9380 options captured by this hasher bundle. */
123
+ defaults: H2CDefaults;
65
124
  };
66
125
 
67
126
  // Octet Stream to Integer. "spec" implementation of os2ip is 2.5x slower vs bytesToNumberBE.
68
127
  const os2ip = bytesToNumberBE;
69
128
 
70
- // Integer to Octet Stream (numberToBytesBE)
71
- function i2osp(value: number, length: number): Uint8Array {
129
+ // Integer to Octet Stream (numberToBytesBE).
130
+ function i2osp(value: number, length: number): TRet<Uint8Array> {
72
131
  asafenumber(value);
73
132
  asafenumber(length);
74
- if (value < 0 || value >= 1 << (8 * length)) throw new Error('invalid I2OSP input: ' + value);
133
+ // This helper stays on the JS bitwise/u32 fast-path. Callers that need wider encodings should
134
+ // use bigint + numberToBytesBE instead of routing large widths through this small helper.
135
+ if (length < 0 || length > 4) throw new Error('invalid I2OSP length: ' + length);
136
+ if (value < 0 || value > 2 ** (8 * length) - 1) throw new Error('invalid I2OSP input: ' + value);
75
137
  const res = Array.from({ length }).fill(0) as number[];
76
138
  for (let i = length - 1; i >= 0; i--) {
77
139
  res[i] = value & 0xff;
78
140
  value >>>= 8;
79
141
  }
80
- return new Uint8Array(res);
142
+ return new Uint8Array(res) as TRet<Uint8Array>;
81
143
  }
82
144
 
83
- function strxor(a: Uint8Array, b: Uint8Array): Uint8Array {
145
+ // RFC 9380 only applies strxor() to equal-length strings; callers must preserve that invariant.
146
+ function strxor(a: TArg<Uint8Array>, b: TArg<Uint8Array>): TRet<Uint8Array> {
84
147
  const arr = new Uint8Array(a.length);
85
148
  for (let i = 0; i < a.length; i++) {
86
149
  arr[i] = a[i] ^ b[i];
87
150
  }
88
- return arr;
151
+ return arr as TRet<Uint8Array>;
89
152
  }
90
153
 
91
154
  // User can always use utf8 if they want, by passing Uint8Array.
92
155
  // If string is passed, we treat it as ASCII: other formats are likely a mistake.
93
- function normDST(DST: AsciiOrBytes): Uint8Array {
156
+ function normDST(DST: TArg<AsciiOrBytes>): TRet<Uint8Array> {
94
157
  if (!isBytes(DST) && typeof DST !== 'string')
95
158
  throw new Error('DST must be Uint8Array or ascii string');
96
- return typeof DST === 'string' ? asciiToBytes(DST) : DST;
159
+ const dst = typeof DST === 'string' ? asciiToBytes(DST) : DST;
160
+ // RFC 9380 §3.1 requirement 2: tags "MUST have nonzero length".
161
+ if (dst.length === 0) throw new Error('DST must be non-empty');
162
+ return dst as TRet<Uint8Array>;
97
163
  }
98
164
 
99
165
  /**
100
- * Produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
101
- * [RFC 9380 5.3.1](https://www.rfc-editor.org/rfc/rfc9380#section-5.3.1).
166
+ * Produces a uniformly random byte string using a cryptographic hash
167
+ * function H that outputs b bits.
168
+ * See {@link https://www.rfc-editor.org/rfc/rfc9380#section-5.3.1 | RFC 9380 section 5.3.1}.
169
+ * @param msg - Input message.
170
+ * @param DST - Domain separation tag. This helper normalizes DST, rejects empty DSTs, and
171
+ * oversize-hashes DST when needed.
172
+ * @param lenInBytes - Output length.
173
+ * @param H - Hash function.
174
+ * @returns Uniform byte string.
175
+ * @throws If the message, DST, hash, or output length is invalid. {@link Error}
176
+ * @example
177
+ * Expand one message into uniform bytes with the XMD construction.
178
+ *
179
+ * ```ts
180
+ * import { expand_message_xmd } from '@noble/curves/abstract/hash-to-curve.js';
181
+ * import { sha256 } from '@noble/hashes/sha2.js';
182
+ * const uniform = expand_message_xmd(new TextEncoder().encode('hello noble'), 'DST', 32, sha256);
183
+ * ```
102
184
  */
103
185
  export function expand_message_xmd(
104
- msg: Uint8Array,
105
- DST: AsciiOrBytes,
186
+ msg: TArg<Uint8Array>,
187
+ DST: TArg<AsciiOrBytes>,
106
188
  lenInBytes: number,
107
- H: CHash
108
- ): Uint8Array {
189
+ H: TArg<CHash>
190
+ ): TRet<Uint8Array> {
109
191
  abytes(msg);
110
192
  asafenumber(lenInBytes);
111
193
  DST = normDST(DST);
@@ -115,12 +197,15 @@ export function expand_message_xmd(
115
197
  const ell = Math.ceil(lenInBytes / b_in_bytes);
116
198
  if (lenInBytes > 65535 || ell > 255) throw new Error('expand_message_xmd: invalid lenInBytes');
117
199
  const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
118
- const Z_pad = i2osp(0, r_in_bytes);
200
+ const Z_pad = new Uint8Array(r_in_bytes); // RFC 9380: Z_pad = I2OSP(0, s_in_bytes)
119
201
  const l_i_b_str = i2osp(lenInBytes, 2); // len_in_bytes_str
120
202
  const b = new Array<Uint8Array>(ell);
121
203
  const b_0 = H(concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
122
204
  b[0] = H(concatBytes(b_0, i2osp(1, 1), DST_prime));
123
- for (let i = 1; i <= ell; i++) {
205
+ // `b[0]` already stores RFC `b_1`, so only derive `b_2..b_ell` here. The old `<= ell`
206
+ // loop computed one extra tail block, which was usually sliced away but broke at max `ell=255`
207
+ // by reaching `I2OSP(256, 1)`.
208
+ for (let i = 1; i < ell; i++) {
124
209
  const args = [strxor(b_0, b[i - 1]), i2osp(i + 1, 1), DST_prime];
125
210
  b[i] = H(concatBytes(...args));
126
211
  }
@@ -133,20 +218,42 @@ export function expand_message_xmd(
133
218
  * 1. The collision resistance of H MUST be at least k bits.
134
219
  * 2. H MUST be an XOF that has been proved indifferentiable from
135
220
  * a random oracle under a reasonable cryptographic assumption.
136
- * [RFC 9380 5.3.2](https://www.rfc-editor.org/rfc/rfc9380#section-5.3.2).
221
+ * See {@link https://www.rfc-editor.org/rfc/rfc9380#section-5.3.2 | RFC 9380 section 5.3.2}.
222
+ * @param msg - Input message.
223
+ * @param DST - Domain separation tag. This helper normalizes DST, rejects empty DSTs, and
224
+ * oversize-hashes DST when needed.
225
+ * @param lenInBytes - Output length.
226
+ * @param k - Target security level.
227
+ * @param H - XOF hash function.
228
+ * @returns Uniform byte string.
229
+ * @throws If the message, DST, XOF, or output length is invalid. {@link Error}
230
+ * @example
231
+ * Expand one message into uniform bytes with the XOF construction.
232
+ *
233
+ * ```ts
234
+ * import { expand_message_xof } from '@noble/curves/abstract/hash-to-curve.js';
235
+ * import { shake256 } from '@noble/hashes/sha3.js';
236
+ * const uniform = expand_message_xof(
237
+ * new TextEncoder().encode('hello noble'),
238
+ * 'DST',
239
+ * 32,
240
+ * 128,
241
+ * shake256
242
+ * );
243
+ * ```
137
244
  */
138
245
  export function expand_message_xof(
139
- msg: Uint8Array,
140
- DST: AsciiOrBytes,
246
+ msg: TArg<Uint8Array>,
247
+ DST: TArg<AsciiOrBytes>,
141
248
  lenInBytes: number,
142
249
  k: number,
143
- H: CHash
144
- ): Uint8Array {
250
+ H: TArg<CHash>
251
+ ): TRet<Uint8Array> {
145
252
  abytes(msg);
146
253
  asafenumber(lenInBytes);
147
254
  DST = normDST(DST);
148
255
  // https://www.rfc-editor.org/rfc/rfc9380#section-5.3.3
149
- // DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
256
+ // RFC 9380 §5.3.3: DST = H("H2C-OVERSIZE-DST-" || a_very_long_DST, ceil(2 * k / 8)).
150
257
  if (DST.length > 255) {
151
258
  const dkLen = Math.ceil((2 * k) / 8);
152
259
  DST = H.create({ dkLen }).update(asciiToBytes('H2C-OVERSIZE-DST-')).update(DST).digest();
@@ -166,13 +273,33 @@ export function expand_message_xof(
166
273
 
167
274
  /**
168
275
  * Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F.
169
- * [RFC 9380 5.2](https://www.rfc-editor.org/rfc/rfc9380#section-5.2).
170
- * @param msg a byte string containing the message to hash
171
- * @param count the number of elements of F to output
172
- * @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
173
- * @returns [u_0, ..., u_(count - 1)], a list of field elements.
276
+ * See {@link https://www.rfc-editor.org/rfc/rfc9380#section-5.2 | RFC 9380 section 5.2}.
277
+ * @param msg - Input message bytes.
278
+ * @param count - Number of field elements to derive. Must be `>= 1`.
279
+ * @param options - RFC 9380 options. See {@link H2COpts}. `m` must be `>= 1`.
280
+ * @returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
281
+ * @throws If the expander choice or RFC 9380 options are invalid. {@link Error}
282
+ * @example
283
+ * Hash one message into field elements before mapping it onto a curve.
284
+ *
285
+ * ```ts
286
+ * import { hash_to_field } from '@noble/curves/abstract/hash-to-curve.js';
287
+ * import { sha256 } from '@noble/hashes/sha2.js';
288
+ * const scalars = hash_to_field(new TextEncoder().encode('hello noble'), 2, {
289
+ * DST: 'DST',
290
+ * p: 17n,
291
+ * m: 1,
292
+ * k: 128,
293
+ * expand: 'xmd',
294
+ * hash: sha256,
295
+ * });
296
+ * ```
174
297
  */
175
- export function hash_to_field(msg: Uint8Array, count: number, options: H2COpts): bigint[][] {
298
+ export function hash_to_field(
299
+ msg: TArg<Uint8Array>,
300
+ count: number,
301
+ options: TArg<H2COpts>
302
+ ): bigint[][] {
176
303
  validateObject(options, {
177
304
  p: 'bigint',
178
305
  m: 'number',
@@ -183,6 +310,10 @@ export function hash_to_field(msg: Uint8Array, count: number, options: H2COpts):
183
310
  asafenumber(hash.outputLen, 'valid hash');
184
311
  abytes(msg);
185
312
  asafenumber(count);
313
+ // RFC 9380 §5.2 defines hash_to_field over a list of one or more field elements and requires
314
+ // extension degree `m >= 1`; rejecting here avoids degenerate `[]` / `[[]]` helper outputs.
315
+ if (count < 1) throw new Error('hash_to_field: expected count >= 1');
316
+ if (m < 1) throw new Error('hash_to_field: expected m >= 1');
186
317
  const log2p = p.toString(2).length;
187
318
  const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above
188
319
  const len_in_bytes = count * m * L;
@@ -212,6 +343,21 @@ export function hash_to_field(msg: Uint8Array, count: number, options: H2COpts):
212
343
 
213
344
  type XY<T> = (x: T, y: T) => { x: T; y: T };
214
345
  type XYRatio<T> = [T[], T[], T[], T[]]; // xn/xd, yn/yd
346
+ /**
347
+ * @param field - Field implementation.
348
+ * @param map - Isogeny coefficients.
349
+ * @returns Isogeny mapping helper.
350
+ * @example
351
+ * Build one rational isogeny map, then apply it to affine x/y coordinates.
352
+ *
353
+ * ```ts
354
+ * import { isogenyMap } from '@noble/curves/abstract/hash-to-curve.js';
355
+ * import { Field } from '@noble/curves/abstract/modular.js';
356
+ * const Fp = Field(17n);
357
+ * const iso = isogenyMap(Fp, [[0n, 1n], [1n], [1n], [1n]]);
358
+ * const point = iso(3n, 5n);
359
+ * ```
360
+ */
215
361
  export function isogenyMap<T, F extends IField<T>>(field: F, map: XYRatio<T>): XY<T> {
216
362
  // Make same order as in spec
217
363
  const coeff = map.map((i) => Array.from(i).reverse());
@@ -219,10 +365,11 @@ export function isogenyMap<T, F extends IField<T>>(field: F, map: XYRatio<T>): X
219
365
  const [xn, xd, yn, yd] = coeff.map((val) =>
220
366
  val.reduce((acc, i) => field.add(field.mul(acc, x), i))
221
367
  );
222
- // 6.6.3
223
- // Exceptional cases of iso_map are inputs that cause the denominator of
224
- // either rational function to evaluate to zero; such cases MUST return
225
- // the identity point on E.
368
+ // RFC 9380 §6.6.3 / Appendix E: denominator-zero exceptional cases must
369
+ // return the identity on E.
370
+ // Shipped Weierstrass consumers encode that affine identity as all-zero
371
+ // coordinates, so `passZero=true` intentionally collapses zero
372
+ // denominators to `{ x: 0, y: 0 }`.
226
373
  const [xd_inv, yd_inv] = FpInvertBatch(field, [xd, yd], true);
227
374
  x = field.mul(xn, xd_inv); // xNum / xDen
228
375
  y = field.mul(y, field.mul(yn, yd_inv)); // y * (yNum / yDev)
@@ -230,39 +377,90 @@ export function isogenyMap<T, F extends IField<T>>(field: F, map: XYRatio<T>): X
230
377
  };
231
378
  }
232
379
 
233
- export const _DST_scalar: Uint8Array = asciiToBytes('HashToScalar-');
380
+ // Keep the shared DST removable when the selected bundle never hashes to scalar.
381
+ // Callers that need protocol-specific scalar domain separation must override this generic default.
382
+ // RFC 9497 §§4.1-4.5 use this ASCII prefix before appending the ciphersuite context string.
383
+ // Export a string instead of mutable bytes so callers cannot poison default hash-to-scalar behavior
384
+ // by mutating a shared Uint8Array in place.
385
+ export const _DST_scalar = 'HashToScalar-' as const;
234
386
 
235
- /** Creates hash-to-curve methods from EC Point and mapToCurve function. See {@link H2CHasher}. */
387
+ /**
388
+ * Creates hash-to-curve methods from EC Point and mapToCurve function. See {@link H2CHasher}.
389
+ * @param Point - Point constructor.
390
+ * @param mapToCurve - Map-to-curve function.
391
+ * @param defaults - Default hash-to-curve options. This object is frozen in place and reused as
392
+ * the shared defaults bundle for the returned helpers.
393
+ * @returns Hash-to-curve helper namespace.
394
+ * @throws If the map-to-curve callback or default hash-to-curve options are invalid. {@link Error}
395
+ * @example
396
+ * Bundle hash-to-curve, hash-to-scalar, and encode-to-curve helpers for one curve.
397
+ *
398
+ * ```ts
399
+ * import { createHasher } from '@noble/curves/abstract/hash-to-curve.js';
400
+ * import { p256 } from '@noble/curves/nist.js';
401
+ * import { sha256 } from '@noble/hashes/sha2.js';
402
+ * const hasher = createHasher(p256.Point, () => p256.Point.BASE.toAffine(), {
403
+ * DST: 'P256_XMD:SHA-256_SSWU_RO_',
404
+ * encodeDST: 'P256_XMD:SHA-256_SSWU_NU_',
405
+ * p: p256.Point.Fp.ORDER,
406
+ * m: 1,
407
+ * k: 128,
408
+ * expand: 'xmd',
409
+ * hash: sha256,
410
+ * });
411
+ * const point = hasher.encodeToCurve(new TextEncoder().encode('hello noble'));
412
+ * ```
413
+ */
236
414
  export function createHasher<PC extends PC_ANY>(
237
415
  Point: PC,
238
416
  mapToCurve: MapToCurve<PC_F<PC>>,
239
- defaults: H2COpts & { encodeDST?: AsciiOrBytes }
417
+ defaults: TArg<H2COpts & { encodeDST?: AsciiOrBytes }>
240
418
  ): H2CHasher<PC> {
241
419
  if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
420
+ // `Point` is intentionally not shape-validated eagerly here: point constructors vary across
421
+ // curve families, so this helper only checks the hooks it can validate cheaply. Misconfigured
422
+ // suites fail later when hashing first touches Point.fromAffine / Point.ZERO / clearCofactor().
423
+ const snapshot = (src: TArg<H2COpts & { encodeDST?: AsciiOrBytes }>): TRet<H2CDefaults> =>
424
+ Object.freeze({
425
+ ...src,
426
+ DST: isBytes(src.DST) ? copyBytes(src.DST) : src.DST,
427
+ ...(src.encodeDST === undefined
428
+ ? {}
429
+ : { encodeDST: isBytes(src.encodeDST) ? copyBytes(src.encodeDST) : src.encodeDST }),
430
+ }) as TRet<H2CDefaults>;
431
+ // Keep one private defaults snapshot for actual hashing and expose fresh
432
+ // detached snapshots via the public getter.
433
+ // Otherwise a caller could mutate `hasher.defaults.DST` in place and poison
434
+ // the singleton hasher for every other consumer in the same process.
435
+ const safeDefaults = snapshot(defaults);
242
436
  function map(num: bigint[]): PC_P<PC> {
243
437
  return Point.fromAffine(mapToCurve(num)) as PC_P<PC>;
244
438
  }
245
439
  function clear(initial: PC_P<PC>): PC_P<PC> {
246
440
  const P = initial.clearCofactor();
247
- if (P.equals(Point.ZERO)) return Point.ZERO as PC_P<PC>; // zero will throw in assert
441
+ // Keep ZERO as the algebraic cofactor-clearing result here; strict public point-validity
442
+ // surfaces may still reject it later, but createHasher.clear() itself is not that boundary.
443
+ if (P.equals(Point.ZERO)) return Point.ZERO as PC_P<PC>;
248
444
  P.assertValidity();
249
445
  return P as PC_P<PC>;
250
446
  }
251
447
 
252
- return {
253
- defaults: Object.freeze(defaults),
448
+ return Object.freeze({
449
+ get defaults() {
450
+ return snapshot(safeDefaults);
451
+ },
254
452
  Point,
255
453
 
256
- hashToCurve(msg: Uint8Array, options?: H2CDSTOpts): PC_P<PC> {
257
- const opts = Object.assign({}, defaults, options);
454
+ hashToCurve(msg: TArg<Uint8Array>, options?: TArg<H2CDSTOpts>): PC_P<PC> {
455
+ const opts = Object.assign({}, safeDefaults, options);
258
456
  const u = hash_to_field(msg, 2, opts);
259
457
  const u0 = map(u[0]);
260
458
  const u1 = map(u[1]);
261
459
  return clear(u0.add(u1) as PC_P<PC>);
262
460
  },
263
- encodeToCurve(msg: Uint8Array, options?: H2CDSTOpts): PC_P<PC> {
264
- const optsDst = defaults.encodeDST ? { DST: defaults.encodeDST } : {};
265
- const opts = Object.assign({}, defaults, optsDst, options);
461
+ encodeToCurve(msg: TArg<Uint8Array>, options?: TArg<H2CDSTOpts>): PC_P<PC> {
462
+ const optsDst = safeDefaults.encodeDST ? { DST: safeDefaults.encodeDST } : {};
463
+ const opts = Object.assign({}, safeDefaults, optsDst, options);
266
464
  const u = hash_to_field(msg, 1, opts);
267
465
  const u0 = map(u[0]);
268
466
  return clear(u0);
@@ -270,7 +468,7 @@ export function createHasher<PC extends PC_ANY>(
270
468
  /** See {@link H2CHasher} */
271
469
  mapToCurve(scalars: bigint | bigint[]): PC_P<PC> {
272
470
  // Curves with m=1 accept only single scalar
273
- if (defaults.m === 1) {
471
+ if (safeDefaults.m === 1) {
274
472
  if (typeof scalars !== 'bigint') throw new Error('expected bigint (m=1)');
275
473
  return clear(map([scalars]));
276
474
  }
@@ -281,12 +479,13 @@ export function createHasher<PC extends PC_ANY>(
281
479
  },
282
480
 
283
481
  // hash_to_scalar can produce 0: https://www.rfc-editor.org/errata/eid8393
284
- // RFC 9380, draft-irtf-cfrg-bbs-signatures-08
285
- hashToScalar(msg: Uint8Array, options?: H2CDSTOpts): bigint {
482
+ // RFC 9380, draft-irtf-cfrg-bbs-signatures-08. Default scalar DST is the shared generic
483
+ // `HashToScalar-` prefix above unless the caller overrides it per invocation.
484
+ hashToScalar(msg: TArg<Uint8Array>, options?: TArg<H2CDSTOpts>): bigint {
286
485
  // @ts-ignore
287
486
  const N = Point.Fn.ORDER;
288
- const opts = Object.assign({}, defaults, { p: N, m: 1, DST: _DST_scalar }, options);
487
+ const opts = Object.assign({}, safeDefaults, { p: N, m: 1, DST: _DST_scalar }, options);
289
488
  return hash_to_field(msg, 1, opts)[0][0];
290
489
  },
291
- };
490
+ });
292
491
  }