@noble/curves 0.6.1 → 0.6.2

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
@@ -329,47 +329,54 @@ The module allows to hash arbitrary strings to elliptic curve points.
329
329
 
330
330
  - `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
331
 
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
- ```
332
+ ```ts
333
+ function expand_message_xmd(
334
+ msg: Uint8Array,
335
+ DST: Uint8Array,
336
+ lenInBytes: number,
337
+ H: CHash
338
+ ): Uint8Array;
339
+ function expand_message_xof(
340
+ msg: Uint8Array,
341
+ DST: Uint8Array,
342
+ lenInBytes: number,
343
+ k: number,
344
+ H: CHash
345
+ ): Uint8Array;
346
+ ```
340
347
 
341
348
  - `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
- ```
349
+ hashes arbitrary-length byte strings to a list of one or more elements of a finite field F.
350
+ _ `msg` a byte string containing the message to hash
351
+ _ `count` the number of elements of F to output
352
+ _ `options` `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`
353
+ _ Returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
354
+
355
+ ```ts
356
+ function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][];
357
+ type htfOpts = {
358
+ // DST: a domain separation tag
359
+ // defined in section 2.2.5
360
+ DST: string;
361
+ // p: the characteristic of F
362
+ // where F is a finite field of characteristic p and order q = p^m
363
+ p: bigint;
364
+ // m: the extension degree of F, m >= 1
365
+ // where F is a finite field of characteristic p and order q = p^m
366
+ m: number;
367
+ // k: the target security level for the suite in bits
368
+ // defined in section 5.1
369
+ k: number;
370
+ // option to use a message that has already been processed by
371
+ // expand_message_xmd
372
+ expand?: 'xmd' | 'xof';
373
+ // Hash functions for: expand_message_xmd is appropriate for use with a
374
+ // wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
375
+ // BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
376
+ // TODO: verify that hash is shake if expand==='xof' via types
377
+ hash: CHash;
378
+ };
379
+ ```
373
380
 
374
381
  ### abstract/poseidon: Poseidon hash
375
382
 
@@ -516,11 +523,11 @@ Upgrading from @noble/secp256k1 1.7:
516
523
  - Compressed (33-byte) public keys are now returned by default, instead of uncompressed
517
524
  - Methods are now synchronous. Setting `secp.utils.hmacSha256` is no longer required
518
525
  - `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`
526
+ - `der`, `recovered` options were removed
527
+ - `canonical` was renamed to `lowS`
528
+ - Return type is now `{ r: bigint, s: bigint, recovery: number }` instance of `Signature`
522
529
  - `verify()`
523
- - `strict` was renamed to `lowS`
530
+ - `strict` was renamed to `lowS`
524
531
  - `recoverPublicKey()`: moved to sig instance `Signature#recoverPublicKey(msgHash)`
525
532
  - `Point` was removed: use `ProjectivePoint` in xyz coordinates
526
533
  - `utils`: Many methods were removed, others were moved to `schnorr` namespace
@@ -532,6 +539,7 @@ Upgrading from @noble/ed25519 1.7:
532
539
  - `Point` was removed: use `ExtendedPoint` in xyzt coordinates
533
540
  - `Signature` was removed
534
541
  - `getSharedSecret` was removed: use separate x25519 sub-module
542
+ - `bigint` is no longer allowed in `getPublicKey`, `sign`, `verify`. Reason: ed25519 is LE, can lead to bugs
535
543
 
536
544
  ## Contributing & testing
537
545
 
@@ -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);
@@ -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))
@@ -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);
@@ -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.2",
4
4
  "description": "Minimal, auditable JS implementation of elliptic curve cryptography",
5
5
  "files": [
6
6
  "lib"