@noble/curves 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,8 +22,7 @@ Package consists of two parts:
22
22
 
23
23
  Curves incorporate work from previous noble packages
24
24
  ([secp256k1](https://github.com/paulmillr/noble-secp256k1),
25
- [ed25519](https://github.com/paulmillr/noble-ed25519),
26
- [bls12-381](https://github.com/paulmillr/noble-bls12-381)),
25
+ [ed25519](https://github.com/paulmillr/noble-ed25519)),
27
26
  which had security audits and were developed from 2019 to 2022.
28
27
  Check out [Upgrading](#upgrading) section if you've used them before.
29
28
 
@@ -31,14 +30,14 @@ Check out [Upgrading](#upgrading) section if you've used them before.
31
30
 
32
31
  > **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools.
33
32
 
34
- - Minimal dependencies, small files
33
+ - Protection against supply chain attacks
35
34
  - Easily auditable TypeScript/JS code
36
35
  - Supported in all major browsers and stable node.js versions
37
36
  - All releases are signed with PGP keys
38
37
  - Check out [homepage](https://paulmillr.com/noble/) & all libraries:
39
- [curves](https://github.com/paulmillr/noble-curves) ([secp256k1](https://github.com/paulmillr/noble-secp256k1),
40
- [ed25519](https://github.com/paulmillr/noble-ed25519),
41
- [bls12-381](https://github.com/paulmillr/noble-bls12-381)),
38
+ [curves](https://github.com/paulmillr/noble-curves)
39
+ ([secp256k1](https://github.com/paulmillr/noble-secp256k1),
40
+ [ed25519](https://github.com/paulmillr/noble-ed25519)),
42
41
  [hashes](https://github.com/paulmillr/noble-hashes)
43
42
 
44
43
  ## Usage
@@ -48,23 +47,7 @@ Use NPM in node.js / browser, or include single file from
48
47
 
49
48
  > npm install @noble/curves
50
49
 
51
- The library does not have an entry point. It allows you to select specific primitives and drop everything else. If you only want to use secp256k1, just use the library with rollup or other bundlers. This is done to make your bundles tiny.
52
-
53
- ```ts
54
- // Common.js and ECMAScript Modules (ESM)
55
- import { secp256k1 } from '@noble/curves/secp256k1';
56
-
57
- const key = secp256k1.utils.randomPrivateKey();
58
- const pub = secp256k1.getPublicKey(key);
59
- const msg = new Uint8Array(32).fill(1);
60
- const sig = secp256k1.sign(msg, key);
61
- secp256k1.verify(sig, msg, pub) === true;
62
- sig.recoverPublicKey(msg) === pub;
63
- const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
64
- const shared = secp256k1.getSharedSecret(key, someonesPub);
65
- ```
66
-
67
- All curves:
50
+ The library does not have an entry point. It allows you to select specific primitives and drop everything else. If you only want to use secp256k1, just use the library with rollup or other bundlers. This is done to make your bundles tiny. All curves:
68
51
 
69
52
  ```ts
70
53
  import { secp256k1 } from '@noble/curves/secp256k1';
@@ -80,7 +63,25 @@ import { bn254 } from '@noble/curves/bn';
80
63
  import { jubjub } from '@noble/curves/jubjub';
81
64
  ```
82
65
 
83
- To define a custom curve, check out API below.
66
+ Every curve can be used in the following way:
67
+
68
+ ```ts
69
+ import { secp256k1 } from '@noble/curves/secp256k1'; // Common.js and ECMAScript Modules (ESM)
70
+
71
+ const key = secp256k1.utils.randomPrivateKey();
72
+ const pub = secp256k1.getPublicKey(key);
73
+ const msg = new Uint8Array(32).fill(1);
74
+ const sig = secp256k1.sign(msg, key);
75
+ // weierstrass curves should use extraEntropy: https://moderncrypto.org/mail-archive/curves/2017/000925.html
76
+ const sigImprovedSecurity = secp256k1.sign(msg, key, { extraEntropy: true });
77
+ secp256k1.verify(sig, msg, pub) === true;
78
+ // secp, p*, pasta curves allow pub recovery
79
+ sig.recoverPublicKey(msg) === pub;
80
+ const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
81
+ const shared = secp256k1.getSharedSecret(key, someonesPub);
82
+ ```
83
+
84
+ To define a custom curve, check out docs below.
84
85
 
85
86
  ## API
86
87
 
@@ -109,17 +110,20 @@ import * as utils from '@noble/curves/abstract/utils';
109
110
  They allow to define a new curve in a few lines of code:
110
111
 
111
112
  ```ts
112
- import { Fp } from '@noble/curves/abstract/modular';
113
+ import { Field } from '@noble/curves/abstract/modular';
113
114
  import { weierstrass } from '@noble/curves/abstract/weierstrass';
114
115
  import { hmac } from '@noble/hashes/hmac';
115
116
  import { sha256 } from '@noble/hashes/sha256';
116
117
  import { concatBytes, randomBytes } from '@noble/hashes/utils';
117
118
 
118
- const secp256k1 = weierstrass({
119
+ // secq (NOT secp) 256k1: cycle of secp256k1 with Fp/N flipped.
120
+ // https://zcash.github.io/halo2/background/curves.html#cycles-of-curves
121
+ // https://personaelabs.org/posts/spartan-ecdsa
122
+ const secq256k1 = weierstrass({
119
123
  a: 0n,
120
124
  b: 7n,
121
- Fp: Fp(2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n),
122
- n: 2n ** 256n - 432420386565659656852420866394968145599n,
125
+ Fp: Field(2n ** 256n - 432420386565659656852420866394968145599n),
126
+ n: 2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n,
123
127
  Gx: 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
124
128
  Gy: 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
125
129
  hash: sha256,
@@ -329,47 +333,54 @@ The module allows to hash arbitrary strings to elliptic curve points.
329
333
 
330
334
  - `expand_message_xmd` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1) produces a uniformly random byte string using a cryptographic hash function H that outputs b bits..
331
335
 
332
- ```ts
333
- function expand_message_xmd(
334
- msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H: CHash
335
- ): Uint8Array;
336
- function expand_message_xof(
337
- msg: Uint8Array, DST: Uint8Array, lenInBytes: number, k: number, H: CHash
338
- ): Uint8Array;
339
- ```
336
+ ```ts
337
+ function expand_message_xmd(
338
+ msg: Uint8Array,
339
+ DST: Uint8Array,
340
+ lenInBytes: number,
341
+ H: CHash
342
+ ): Uint8Array;
343
+ function expand_message_xof(
344
+ msg: Uint8Array,
345
+ DST: Uint8Array,
346
+ lenInBytes: number,
347
+ k: number,
348
+ H: CHash
349
+ ): Uint8Array;
350
+ ```
340
351
 
341
352
  - `hash_to_field(msg, count, options)` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3)
342
- hashes arbitrary-length byte strings to a list of one or more elements of a finite field F.
343
- * `msg` a byte string containing the message to hash
344
- * `count` the number of elements of F to output
345
- * `options` `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`
346
- * Returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
347
-
348
- ```ts
349
- function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][];
350
- type htfOpts = {
351
- // DST: a domain separation tag
352
- // defined in section 2.2.5
353
- DST: string;
354
- // p: the characteristic of F
355
- // where F is a finite field of characteristic p and order q = p^m
356
- p: bigint;
357
- // m: the extension degree of F, m >= 1
358
- // where F is a finite field of characteristic p and order q = p^m
359
- m: number;
360
- // k: the target security level for the suite in bits
361
- // defined in section 5.1
362
- k: number;
363
- // option to use a message that has already been processed by
364
- // expand_message_xmd
365
- expand?: 'xmd' | 'xof';
366
- // Hash functions for: expand_message_xmd is appropriate for use with a
367
- // wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
368
- // BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
369
- // TODO: verify that hash is shake if expand==='xof' via types
370
- hash: CHash;
371
- };
372
- ```
353
+ hashes arbitrary-length byte strings to a list of one or more elements of a finite field F.
354
+ _ `msg` a byte string containing the message to hash
355
+ _ `count` the number of elements of F to output
356
+ _ `options` `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`
357
+ _ Returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
358
+
359
+ ```ts
360
+ function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][];
361
+ type htfOpts = {
362
+ // DST: a domain separation tag
363
+ // defined in section 2.2.5
364
+ DST: string;
365
+ // p: the characteristic of F
366
+ // where F is a finite field of characteristic p and order q = p^m
367
+ p: bigint;
368
+ // m: the extension degree of F, m >= 1
369
+ // where F is a finite field of characteristic p and order q = p^m
370
+ m: number;
371
+ // k: the target security level for the suite in bits
372
+ // defined in section 5.1
373
+ k: number;
374
+ // option to use a message that has already been processed by
375
+ // expand_message_xmd
376
+ expand?: 'xmd' | 'xof';
377
+ // Hash functions for: expand_message_xmd is appropriate for use with a
378
+ // wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
379
+ // BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
380
+ // TODO: verify that hash is shake if expand==='xof' via types
381
+ hash: CHash;
382
+ };
383
+ ```
373
384
 
374
385
  ### abstract/poseidon: Poseidon hash
375
386
 
@@ -516,11 +527,11 @@ Upgrading from @noble/secp256k1 1.7:
516
527
  - Compressed (33-byte) public keys are now returned by default, instead of uncompressed
517
528
  - Methods are now synchronous. Setting `secp.utils.hmacSha256` is no longer required
518
529
  - `sign()`
519
- - `der`, `recovered` options were removed
520
- - `canonical` was renamed to `lowS`
521
- - Return type is now `{ r: bigint, s: bigint, recovery: number }` instance of `Signature`
530
+ - `der`, `recovered` options were removed
531
+ - `canonical` was renamed to `lowS`
532
+ - Return type is now `{ r: bigint, s: bigint, recovery: number }` instance of `Signature`
522
533
  - `verify()`
523
- - `strict` was renamed to `lowS`
534
+ - `strict` was renamed to `lowS`
524
535
  - `recoverPublicKey()`: moved to sig instance `Signature#recoverPublicKey(msgHash)`
525
536
  - `Point` was removed: use `ProjectivePoint` in xyz coordinates
526
537
  - `utils`: Many methods were removed, others were moved to `schnorr` namespace
@@ -532,6 +543,7 @@ Upgrading from @noble/ed25519 1.7:
532
543
  - `Point` was removed: use `ExtendedPoint` in xyzt coordinates
533
544
  - `Signature` was removed
534
545
  - `getSharedSecret` was removed: use separate x25519 sub-module
546
+ - `bigint` is no longer allowed in `getPublicKey`, `sign`, `verify`. Reason: ed25519 is LE, can lead to bugs
535
547
 
536
548
  ## Contributing & testing
537
549
 
@@ -26,15 +26,19 @@ export declare function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: n
26
26
  /**
27
27
  * Creates a wNAF precomputation window. Used for caching.
28
28
  * Default window size is set by `utils.precompute()` and is equal to 8.
29
- * Which means we are caching 65536 points: 256 points for every bit from 0 to 256.
30
- * @returns 65K precomputed points, depending on W
29
+ * Number of precomputed points depends on the curve size:
30
+ * 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where:
31
+ * - 𝑊 is the window size
32
+ * - 𝑛 is the bitlength of the curve order.
33
+ * For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.
34
+ * @returns precomputed point tables flattened to a single array
31
35
  */
32
36
  precomputeWindow(elm: T, W: number): Group<T>[];
33
37
  /**
34
- * Implements w-ary non-adjacent form for calculating ec multiplication.
38
+ * Implements ec multiplication using precomputed tables and w-ary non-adjacent form.
35
39
  * @param W window size
36
- * @param affinePoint optional 2d point to save cached precompute windows on it.
37
- * @param n bits
40
+ * @param precomputes precomputed tables
41
+ * @param n scalar (we don't check here, but should be less than curve order)
38
42
  * @returns real and fake (for const-time) points
39
43
  */
40
44
  wNAF(W: number, precomputes: T[], n: bigint): {
@@ -7,8 +7,17 @@ const modular_js_1 = require("./modular.js");
7
7
  const utils_js_1 = require("./utils.js");
8
8
  const _0n = BigInt(0);
9
9
  const _1n = BigInt(1);
10
- // Elliptic curve multiplication of Point by scalar. Complicated and fragile. Uses wNAF method.
11
- // Windowed method is 10% faster, but takes 2x longer to generate & consumes 2x memory.
10
+ // Elliptic curve multiplication of Point by scalar. Fragile.
11
+ // Scalars should always be less than curve order: this should be checked inside of a curve itself.
12
+ // Creates precomputation tables for fast multiplication:
13
+ // - private scalar is split by fixed size windows of W bits
14
+ // - every window point is collected from window's table & added to accumulator
15
+ // - since windows are different, same point inside tables won't be accessed more than once per calc
16
+ // - each multiplication is 'Math.ceil(CURVE_ORDER / 𝑊) + 1' point additions (fixed for any scalar)
17
+ // - +1 window is neccessary for wNAF
18
+ // - wNAF reduces table size: 2x less memory + 2x faster generation, but 10% slower multiplication
19
+ // TODO: Research returning 2d JS array of windows, instead of a single window. This would allow
20
+ // windows to be in different memory locations
12
21
  function wNAF(c, bits) {
13
22
  const constTimeNegate = (condition, item) => {
14
23
  const neg = item.negate();
@@ -36,8 +45,12 @@ function wNAF(c, bits) {
36
45
  /**
37
46
  * Creates a wNAF precomputation window. Used for caching.
38
47
  * Default window size is set by `utils.precompute()` and is equal to 8.
39
- * Which means we are caching 65536 points: 256 points for every bit from 0 to 256.
40
- * @returns 65K precomputed points, depending on W
48
+ * Number of precomputed points depends on the curve size:
49
+ * 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where:
50
+ * - 𝑊 is the window size
51
+ * - 𝑛 is the bitlength of the curve order.
52
+ * For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.
53
+ * @returns precomputed point tables flattened to a single array
41
54
  */
42
55
  precomputeWindow(elm, W) {
43
56
  const { windows, windowSize } = opts(W);
@@ -57,14 +70,14 @@ function wNAF(c, bits) {
57
70
  return points;
58
71
  },
59
72
  /**
60
- * Implements w-ary non-adjacent form for calculating ec multiplication.
73
+ * Implements ec multiplication using precomputed tables and w-ary non-adjacent form.
61
74
  * @param W window size
62
- * @param affinePoint optional 2d point to save cached precompute windows on it.
63
- * @param n bits
75
+ * @param precomputes precomputed tables
76
+ * @param n scalar (we don't check here, but should be less than curve order)
64
77
  * @returns real and fake (for const-time) points
65
78
  */
66
79
  wNAF(W, precomputes, n) {
67
- // TODO: maybe check that scalar is less than group order? wNAF will fail otherwise
80
+ // TODO: maybe check that scalar is less than group order? wNAF behavious is undefined otherwise
68
81
  // But need to carefully remove other checks before wNAF. ORDER == bits here
69
82
  const { windows, windowSize } = opts(W);
70
83
  let p = c.ZERO;
@@ -109,7 +109,30 @@ function twistedEdwards(curveDef) {
109
109
  this._WINDOW_SIZE = windowSize;
110
110
  pointPrecomputes.delete(this);
111
111
  }
112
- assertValidity() { }
112
+ // Not required for fromHex(), which always creates valid points.
113
+ // Could be useful for fromAffine().
114
+ assertValidity() {
115
+ const { a, d } = CURVE;
116
+ if (this.is0())
117
+ throw new Error('bad point: ZERO'); // TODO: optimize, with vars below?
118
+ // Equation in affine coordinates: ax² + y² = 1 + dx²y²
119
+ // Equation in projective coordinates (X/Z, Y/Z, Z): (aX² + Y²)Z² = Z⁴ + dX²Y²
120
+ const { ex: X, ey: Y, ez: Z, et: T } = this;
121
+ const X2 = modP(X * X); // X²
122
+ const Y2 = modP(Y * Y); // Y²
123
+ const Z2 = modP(Z * Z); // Z²
124
+ const Z4 = modP(Z2 * Z2); // Z⁴
125
+ const aX2 = modP(X2 * a); // aX²
126
+ const left = modP(Z2 * modP(aX2 + Y2)); // (aX² + Y²)Z²
127
+ const right = modP(Z4 + modP(d * modP(X2 * Y2))); // Z⁴ + dX²Y²
128
+ if (left !== right)
129
+ throw new Error('bad point: equation left != right (1)');
130
+ // In Extended coordinates we also have T, which is x*y=T/Z: check X*Y == Z*T
131
+ const XY = modP(X * Y);
132
+ const ZT = modP(Z * T);
133
+ if (XY !== ZT)
134
+ throw new Error('bad point: equation left != right (2)');
135
+ }
113
136
  // Compare one point to another.
114
137
  equals(other) {
115
138
  isPoint(other);
@@ -11,7 +11,6 @@ export declare type Opts = {
11
11
  expand?: 'xmd' | 'xof';
12
12
  hash: CHash;
13
13
  };
14
- export declare function validateOpts(opts: Opts): void;
15
14
  export declare function stringToBytes(str: string): Uint8Array;
16
15
  export declare function expand_message_xmd(msg: Uint8Array, DST: Uint8Array, lenInBytes: number, H: CHash): Uint8Array;
17
16
  export declare function expand_message_xof(msg: Uint8Array, DST: Uint8Array, lenInBytes: number, k: number, H: CHash): Uint8Array;
@@ -1,23 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hashToCurve = exports.isogenyMap = exports.hash_to_field = exports.expand_message_xof = exports.expand_message_xmd = exports.stringToBytes = exports.validateOpts = void 0;
3
+ exports.hashToCurve = exports.isogenyMap = exports.hash_to_field = exports.expand_message_xof = exports.expand_message_xmd = exports.stringToBytes = void 0;
4
4
  const modular_js_1 = require("./modular.js");
5
5
  const utils_js_1 = require("./utils.js");
6
- function validateOpts(opts) {
7
- if (typeof opts.DST !== 'string')
8
- throw new Error('Invalid htf/DST');
9
- if (typeof opts.p !== 'bigint')
10
- throw new Error('Invalid htf/p');
11
- if (typeof opts.m !== 'number')
12
- throw new Error('Invalid htf/m');
13
- if (typeof opts.k !== 'number')
14
- throw new Error('Invalid htf/k');
15
- if (opts.expand !== 'xmd' && opts.expand !== 'xof' && opts.expand !== undefined)
16
- throw new Error('Invalid htf/expand');
17
- if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
18
- throw new Error('Invalid htf/hash function');
19
- }
20
- exports.validateOpts = validateOpts;
21
6
  function stringToBytes(str) {
22
7
  if (typeof str !== 'string') {
23
8
  throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
@@ -143,7 +128,15 @@ function isogenyMap(field, map) {
143
128
  }
144
129
  exports.isogenyMap = isogenyMap;
145
130
  function hashToCurve(Point, mapToCurve, def) {
146
- validateOpts(def);
131
+ (0, utils_js_1.validateObject)(def, {
132
+ DST: 'string',
133
+ p: 'bigint',
134
+ m: 'isSafeInteger',
135
+ k: 'isSafeInteger',
136
+ hash: 'hash',
137
+ });
138
+ if (def.expand !== 'xmd' && def.expand !== 'xof' && def.expand !== undefined)
139
+ throw new Error('Invalid htf/expand');
147
140
  if (typeof mapToCurve !== 'function')
148
141
  throw new Error('hashToCurve: mapToCurve() has not been defined');
149
142
  return {
@@ -42,7 +42,7 @@ export interface Field<T> {
42
42
  fromBytes(bytes: Uint8Array): T;
43
43
  cmov(a: T, b: T, c: boolean): T;
44
44
  }
45
- export declare function validateField<T>(field: Field<T>): object;
45
+ export declare function validateField<T>(field: Field<T>): Field<T>;
46
46
  export declare function FpPow<T>(f: Field<T>, num: T, power: bigint): T;
47
47
  export declare function FpInvertBatch<T>(f: Field<T>, nums: T[]): T[];
48
48
  export declare function FpDiv<T>(f: Field<T>, lhs: T, rhs: T | bigint): T;
@@ -121,7 +121,9 @@ function montgomery(curveDef) {
121
121
  // This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
122
122
  // fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
123
123
  const u = (0, utils_js_1.ensureBytes)(uEnc, montgomeryBytes);
124
- u[fieldLen - 1] &= 127; // 0b0111_1111
124
+ // u[fieldLen-1] crashes QuickJS (TypeError: out-of-bound numeric index)
125
+ if (fieldLen === montgomeryBytes)
126
+ u[fieldLen - 1] &= 127; // 0b0111_1111
125
127
  return (0, utils_js_1.bytesToNumberLE)(u);
126
128
  }
127
129
  function decodeScalar(n) {
@@ -25,6 +25,19 @@ export declare function bitLen(n: bigint): number;
25
25
  export declare const bitGet: (n: bigint, pos: number) => bigint;
26
26
  export declare const bitSet: (n: bigint, pos: number, value: boolean) => bigint;
27
27
  export declare const bitMask: (n: number) => bigint;
28
- declare type ValMap = Record<string, string>;
29
- export declare function validateObject(object: object, validators: ValMap, optValidators?: ValMap): object;
28
+ declare const validatorFns: {
29
+ readonly bigint: (val: any) => boolean;
30
+ readonly function: (val: any) => boolean;
31
+ readonly boolean: (val: any) => boolean;
32
+ readonly string: (val: any) => boolean;
33
+ readonly isSafeInteger: (val: any) => boolean;
34
+ readonly array: (val: any) => boolean;
35
+ readonly field: (val: any, object: any) => any;
36
+ readonly hash: (val: any) => boolean;
37
+ };
38
+ declare type Validator = keyof typeof validatorFns;
39
+ declare type ValMap<T extends Record<string, any>> = {
40
+ [K in keyof T]?: Validator;
41
+ };
42
+ export declare function validateObject<T extends Record<string, any>>(object: T, validators: ValMap<T>, optValidators?: ValMap<T>): T;
30
43
  export {};
@@ -27,7 +27,7 @@ function hexToNumber(hex) {
27
27
  if (typeof hex !== 'string')
28
28
  throw new Error('string expected, got ' + typeof hex);
29
29
  // Big Endian
30
- return BigInt(`0x${hex}`);
30
+ return BigInt(hex === '' ? '0' : `0x${hex}`);
31
31
  }
32
32
  exports.hexToNumber = hexToNumber;
33
33
  // Caching slows it down 2-3x
@@ -118,18 +118,18 @@ exports.bitSet = bitSet;
118
118
  // Not using ** operator with bigints for old engines.
119
119
  const bitMask = (n) => (_2n << BigInt(n - 1)) - _1n;
120
120
  exports.bitMask = bitMask;
121
+ const validatorFns = {
122
+ bigint: (val) => typeof val === 'bigint',
123
+ function: (val) => typeof val === 'function',
124
+ boolean: (val) => typeof val === 'boolean',
125
+ string: (val) => typeof val === 'string',
126
+ isSafeInteger: (val) => Number.isSafeInteger(val),
127
+ array: (val) => Array.isArray(val),
128
+ field: (val, object) => object.Fp.isValid(val),
129
+ hash: (val) => typeof val === 'function' && Number.isSafeInteger(val.outputLen),
130
+ };
131
+ // type Record<K extends string | number | symbol, T> = { [P in K]: T; }
121
132
  function validateObject(object, validators, optValidators = {}) {
122
- const validatorFns = {
123
- bigint: (val) => typeof val === 'bigint',
124
- function: (val) => typeof val === 'function',
125
- boolean: (val) => typeof val === 'boolean',
126
- string: (val) => typeof val === 'string',
127
- isSafeInteger: (val) => Number.isSafeInteger(val),
128
- array: (val) => Array.isArray(val),
129
- field: (val) => object.Fp.isValid(val),
130
- hash: (val) => typeof val === 'function' && Number.isSafeInteger(val.outputLen),
131
- };
132
- // type Key = keyof typeof validators;
133
133
  const checkField = (fieldName, type, isOptional) => {
134
134
  const checkVal = validatorFns[type];
135
135
  if (typeof checkVal !== 'function')
@@ -137,14 +137,22 @@ function validateObject(object, validators, optValidators = {}) {
137
137
  const val = object[fieldName];
138
138
  if (isOptional && val === undefined)
139
139
  return;
140
- if (!checkVal(val)) {
141
- throw new Error(`Invalid param ${fieldName}=${val} (${typeof val}), expected ${type}`);
140
+ if (!checkVal(val, object)) {
141
+ throw new Error(`Invalid param ${String(fieldName)}=${val} (${typeof val}), expected ${type}`);
142
142
  }
143
143
  };
144
- for (let [fieldName, type] of Object.entries(validators))
144
+ for (const [fieldName, type] of Object.entries(validators))
145
145
  checkField(fieldName, type, false);
146
- for (let [fieldName, type] of Object.entries(optValidators))
146
+ for (const [fieldName, type] of Object.entries(optValidators))
147
147
  checkField(fieldName, type, true);
148
148
  return object;
149
149
  }
150
150
  exports.validateObject = validateObject;
151
+ // validate type tests
152
+ // const o: { a: number; b: number; c: number } = { a: 1, b: 5, c: 6 };
153
+ // const z0 = validateObject(o, { a: 'isSafeInteger' }, { c: 'bigint' }); // Ok!
154
+ // // Should fail type-check
155
+ // const z1 = validateObject(o, { a: 'tmp' }, { c: 'zz' });
156
+ // const z2 = validateObject(o, { a: 'isSafeInteger' }, { c: 'zz' });
157
+ // const z3 = validateObject(o, { test: 'boolean', z: 'bug' });
158
+ // const z4 = validateObject(o, { a: 'boolean', z: 'bug' });
@@ -19,6 +19,7 @@ function validatePointOpts(curve) {
19
19
  wrapPrivateKey: 'boolean',
20
20
  isTorsionFree: 'function',
21
21
  clearCofactor: 'function',
22
+ allowInfinityPoint: 'boolean',
22
23
  });
23
24
  const { endo, Fp, a } = opts;
24
25
  if (endo) {
@@ -157,6 +158,8 @@ function weierstrassPoints(opts) {
157
158
  if (pz == null || !Fp.isValid(pz))
158
159
  throw new Error('z required');
159
160
  }
161
+ // Does not validate if the point is on-curve.
162
+ // Use fromHex instead, or call assertValidity() later.
160
163
  static fromAffine(p) {
161
164
  const { x, y } = p || {};
162
165
  if (!p || !Fp.isValid(x) || !Fp.isValid(y))
@@ -4,8 +4,17 @@ import { validateField, nLength } from './modular.js';
4
4
  import { validateObject } from './utils.js';
5
5
  const _0n = BigInt(0);
6
6
  const _1n = BigInt(1);
7
- // Elliptic curve multiplication of Point by scalar. Complicated and fragile. Uses wNAF method.
8
- // Windowed method is 10% faster, but takes 2x longer to generate & consumes 2x memory.
7
+ // Elliptic curve multiplication of Point by scalar. Fragile.
8
+ // Scalars should always be less than curve order: this should be checked inside of a curve itself.
9
+ // Creates precomputation tables for fast multiplication:
10
+ // - private scalar is split by fixed size windows of W bits
11
+ // - every window point is collected from window's table & added to accumulator
12
+ // - since windows are different, same point inside tables won't be accessed more than once per calc
13
+ // - each multiplication is 'Math.ceil(CURVE_ORDER / 𝑊) + 1' point additions (fixed for any scalar)
14
+ // - +1 window is neccessary for wNAF
15
+ // - wNAF reduces table size: 2x less memory + 2x faster generation, but 10% slower multiplication
16
+ // TODO: Research returning 2d JS array of windows, instead of a single window. This would allow
17
+ // windows to be in different memory locations
9
18
  export function wNAF(c, bits) {
10
19
  const constTimeNegate = (condition, item) => {
11
20
  const neg = item.negate();
@@ -33,8 +42,12 @@ export function wNAF(c, bits) {
33
42
  /**
34
43
  * Creates a wNAF precomputation window. Used for caching.
35
44
  * Default window size is set by `utils.precompute()` and is equal to 8.
36
- * Which means we are caching 65536 points: 256 points for every bit from 0 to 256.
37
- * @returns 65K precomputed points, depending on W
45
+ * Number of precomputed points depends on the curve size:
46
+ * 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where:
47
+ * - 𝑊 is the window size
48
+ * - 𝑛 is the bitlength of the curve order.
49
+ * For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.
50
+ * @returns precomputed point tables flattened to a single array
38
51
  */
39
52
  precomputeWindow(elm, W) {
40
53
  const { windows, windowSize } = opts(W);
@@ -54,14 +67,14 @@ export function wNAF(c, bits) {
54
67
  return points;
55
68
  },
56
69
  /**
57
- * Implements w-ary non-adjacent form for calculating ec multiplication.
70
+ * Implements ec multiplication using precomputed tables and w-ary non-adjacent form.
58
71
  * @param W window size
59
- * @param affinePoint optional 2d point to save cached precompute windows on it.
60
- * @param n bits
72
+ * @param precomputes precomputed tables
73
+ * @param n scalar (we don't check here, but should be less than curve order)
61
74
  * @returns real and fake (for const-time) points
62
75
  */
63
76
  wNAF(W, precomputes, n) {
64
- // TODO: maybe check that scalar is less than group order? wNAF will fail otherwise
77
+ // TODO: maybe check that scalar is less than group order? wNAF behavious is undefined otherwise
65
78
  // But need to carefully remove other checks before wNAF. ORDER == bits here
66
79
  const { windows, windowSize } = opts(W);
67
80
  let p = c.ZERO;
@@ -106,7 +106,30 @@ export function twistedEdwards(curveDef) {
106
106
  this._WINDOW_SIZE = windowSize;
107
107
  pointPrecomputes.delete(this);
108
108
  }
109
- assertValidity() { }
109
+ // Not required for fromHex(), which always creates valid points.
110
+ // Could be useful for fromAffine().
111
+ assertValidity() {
112
+ const { a, d } = CURVE;
113
+ if (this.is0())
114
+ throw new Error('bad point: ZERO'); // TODO: optimize, with vars below?
115
+ // Equation in affine coordinates: ax² + y² = 1 + dx²y²
116
+ // Equation in projective coordinates (X/Z, Y/Z, Z): (aX² + Y²)Z² = Z⁴ + dX²Y²
117
+ const { ex: X, ey: Y, ez: Z, et: T } = this;
118
+ const X2 = modP(X * X); // X²
119
+ const Y2 = modP(Y * Y); // Y²
120
+ const Z2 = modP(Z * Z); // Z²
121
+ const Z4 = modP(Z2 * Z2); // Z⁴
122
+ const aX2 = modP(X2 * a); // aX²
123
+ const left = modP(Z2 * modP(aX2 + Y2)); // (aX² + Y²)Z²
124
+ const right = modP(Z4 + modP(d * modP(X2 * Y2))); // Z⁴ + dX²Y²
125
+ if (left !== right)
126
+ throw new Error('bad point: equation left != right (1)');
127
+ // In Extended coordinates we also have T, which is x*y=T/Z: check X*Y == Z*T
128
+ const XY = modP(X * Y);
129
+ const ZT = modP(Z * T);
130
+ if (XY !== ZT)
131
+ throw new Error('bad point: equation left != right (2)');
132
+ }
110
133
  // Compare one point to another.
111
134
  equals(other) {
112
135
  isPoint(other);
@@ -1,19 +1,5 @@
1
1
  import { mod } from './modular.js';
2
- import { concatBytes, ensureBytes } from './utils.js';
3
- export function validateOpts(opts) {
4
- if (typeof opts.DST !== 'string')
5
- throw new Error('Invalid htf/DST');
6
- if (typeof opts.p !== 'bigint')
7
- throw new Error('Invalid htf/p');
8
- if (typeof opts.m !== 'number')
9
- throw new Error('Invalid htf/m');
10
- if (typeof opts.k !== 'number')
11
- throw new Error('Invalid htf/k');
12
- if (opts.expand !== 'xmd' && opts.expand !== 'xof' && opts.expand !== undefined)
13
- throw new Error('Invalid htf/expand');
14
- if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
15
- throw new Error('Invalid htf/hash function');
16
- }
2
+ import { concatBytes, ensureBytes, validateObject } from './utils.js';
17
3
  export function stringToBytes(str) {
18
4
  if (typeof str !== 'string') {
19
5
  throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
@@ -134,7 +120,15 @@ export function isogenyMap(field, map) {
134
120
  };
135
121
  }
136
122
  export function hashToCurve(Point, mapToCurve, def) {
137
- validateOpts(def);
123
+ validateObject(def, {
124
+ DST: 'string',
125
+ p: 'bigint',
126
+ m: 'isSafeInteger',
127
+ k: 'isSafeInteger',
128
+ hash: 'hash',
129
+ });
130
+ if (def.expand !== 'xmd' && def.expand !== 'xof' && def.expand !== undefined)
131
+ throw new Error('Invalid htf/expand');
138
132
  if (typeof mapToCurve !== 'function')
139
133
  throw new Error('hashToCurve: mapToCurve() has not been defined');
140
134
  return {
@@ -118,7 +118,9 @@ export function montgomery(curveDef) {
118
118
  // This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
119
119
  // fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
120
120
  const u = ensureBytes(uEnc, montgomeryBytes);
121
- u[fieldLen - 1] &= 127; // 0b0111_1111
121
+ // u[fieldLen-1] crashes QuickJS (TypeError: out-of-bound numeric index)
122
+ if (fieldLen === montgomeryBytes)
123
+ u[fieldLen - 1] &= 127; // 0b0111_1111
122
124
  return bytesToNumberLE(u);
123
125
  }
124
126
  function decodeScalar(n) {
@@ -22,7 +22,7 @@ export function hexToNumber(hex) {
22
22
  if (typeof hex !== 'string')
23
23
  throw new Error('string expected, got ' + typeof hex);
24
24
  // Big Endian
25
- return BigInt(`0x${hex}`);
25
+ return BigInt(hex === '' ? '0' : `0x${hex}`);
26
26
  }
27
27
  // Caching slows it down 2-3x
28
28
  export function hexToBytes(hex) {
@@ -99,18 +99,18 @@ export const bitSet = (n, pos, value) => n | ((value ? _1n : _0n) << BigInt(pos)
99
99
  // Return mask for N bits (Same as BigInt(`0b${Array(i).fill('1').join('')}`))
100
100
  // Not using ** operator with bigints for old engines.
101
101
  export const bitMask = (n) => (_2n << BigInt(n - 1)) - _1n;
102
+ const validatorFns = {
103
+ bigint: (val) => typeof val === 'bigint',
104
+ function: (val) => typeof val === 'function',
105
+ boolean: (val) => typeof val === 'boolean',
106
+ string: (val) => typeof val === 'string',
107
+ isSafeInteger: (val) => Number.isSafeInteger(val),
108
+ array: (val) => Array.isArray(val),
109
+ field: (val, object) => object.Fp.isValid(val),
110
+ hash: (val) => typeof val === 'function' && Number.isSafeInteger(val.outputLen),
111
+ };
112
+ // type Record<K extends string | number | symbol, T> = { [P in K]: T; }
102
113
  export function validateObject(object, validators, optValidators = {}) {
103
- const validatorFns = {
104
- bigint: (val) => typeof val === 'bigint',
105
- function: (val) => typeof val === 'function',
106
- boolean: (val) => typeof val === 'boolean',
107
- string: (val) => typeof val === 'string',
108
- isSafeInteger: (val) => Number.isSafeInteger(val),
109
- array: (val) => Array.isArray(val),
110
- field: (val) => object.Fp.isValid(val),
111
- hash: (val) => typeof val === 'function' && Number.isSafeInteger(val.outputLen),
112
- };
113
- // type Key = keyof typeof validators;
114
114
  const checkField = (fieldName, type, isOptional) => {
115
115
  const checkVal = validatorFns[type];
116
116
  if (typeof checkVal !== 'function')
@@ -118,13 +118,21 @@ export function validateObject(object, validators, optValidators = {}) {
118
118
  const val = object[fieldName];
119
119
  if (isOptional && val === undefined)
120
120
  return;
121
- if (!checkVal(val)) {
122
- throw new Error(`Invalid param ${fieldName}=${val} (${typeof val}), expected ${type}`);
121
+ if (!checkVal(val, object)) {
122
+ throw new Error(`Invalid param ${String(fieldName)}=${val} (${typeof val}), expected ${type}`);
123
123
  }
124
124
  };
125
- for (let [fieldName, type] of Object.entries(validators))
125
+ for (const [fieldName, type] of Object.entries(validators))
126
126
  checkField(fieldName, type, false);
127
- for (let [fieldName, type] of Object.entries(optValidators))
127
+ for (const [fieldName, type] of Object.entries(optValidators))
128
128
  checkField(fieldName, type, true);
129
129
  return object;
130
130
  }
131
+ // validate type tests
132
+ // const o: { a: number; b: number; c: number } = { a: 1, b: 5, c: 6 };
133
+ // const z0 = validateObject(o, { a: 'isSafeInteger' }, { c: 'bigint' }); // Ok!
134
+ // // Should fail type-check
135
+ // const z1 = validateObject(o, { a: 'tmp' }, { c: 'zz' });
136
+ // const z2 = validateObject(o, { a: 'isSafeInteger' }, { c: 'zz' });
137
+ // const z3 = validateObject(o, { test: 'boolean', z: 'bug' });
138
+ // const z4 = validateObject(o, { a: 'boolean', z: 'bug' });
@@ -16,6 +16,7 @@ function validatePointOpts(curve) {
16
16
  wrapPrivateKey: 'boolean',
17
17
  isTorsionFree: 'function',
18
18
  clearCofactor: 'function',
19
+ allowInfinityPoint: 'boolean',
19
20
  });
20
21
  const { endo, Fp, a } = opts;
21
22
  if (endo) {
@@ -154,6 +155,8 @@ export function weierstrassPoints(opts) {
154
155
  if (pz == null || !Fp.isValid(pz))
155
156
  throw new Error('z required');
156
157
  }
158
+ // Does not validate if the point is on-curve.
159
+ // Use fromHex instead, or call assertValidity() later.
157
160
  static fromAffine(p) {
158
161
  const { x, y } = p || {};
159
162
  if (!p || !Fp.isValid(x) || !Fp.isValid(y))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noble/curves",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Minimal, auditable JS implementation of elliptic curve cryptography",
5
5
  "files": [
6
6
  "lib"
@@ -21,19 +21,17 @@
21
21
  },
22
22
  "license": "MIT",
23
23
  "dependencies": {
24
- "@noble/hashes": "1.1.5"
24
+ "@noble/hashes": "1.2.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@rollup/plugin-node-resolve": "13.3.0",
28
27
  "@scure/base": "~1.1.1",
29
- "@scure/bip32": "~1.1.1",
30
- "@scure/bip39": "~1.1.0",
28
+ "@scure/bip32": "~1.1.5",
29
+ "@scure/bip39": "~1.1.1",
31
30
  "@types/node": "18.11.3",
32
31
  "fast-check": "3.0.0",
33
32
  "micro-bmark": "0.3.0",
34
33
  "micro-should": "0.4.0",
35
34
  "prettier": "2.8.3",
36
- "rollup": "2.75.5",
37
35
  "typescript": "4.7.3"
38
36
  },
39
37
  "main": "index.js",