README, lint

This commit is contained in:
Paul Miller 2022-12-27 02:16:45 +00:00
parent 0277c01efd
commit ba121ff24c
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
5 changed files with 100 additions and 133 deletions

186
README.md

@ -10,11 +10,16 @@ Minimal, zero-dependency JS implementation of elliptic curve cryptography.
- 🔻 Helps JS bundlers with lack of entry point, ensures small size of your app
- 🔍 Unique tests ensure correctness. Wycheproof vectors included
To keep the package minimal, no curve definitions are provided out-of-box. Use `micro-curve-definitions` module:
No curve definitions are provided out-of-box. Use separate package `micro-curve-definitions`:
- It provides P192, P224, P256, P384, P521, secp256k1, stark, bn254, pasta (pallas/vesta), ed25519, ed448 & bls12-381 curves
- Main reason for separate package is the fact hashing library (like @noble/hashes) is required for full functionality
- We may reconsider merging packages in future, when a stable version would be ready
- It provides:
- NIST curves secp192r1/P192, secp224r1/P224, secp256r1/P256, secp384r1/P384, secp521r1/P521
- SECG curve secp256k1
- bls12-381, bn254 pairing-friendly curves
- ed25519/curve25519/x25519/ristretto, edwards448/curve448/x448 RFC7748 / RFC8032 / ZIP215 stuff
- It allows to keep the main library minimal, zero-dependency.
m-c-d depends on a hashing library `@noble/hashes`
- Packages may be merged later, once a stable version is ready
The goal for the near future is to update previous packages
([secp256k1](https://github.com/paulmillr/noble-secp256k1),
@ -45,7 +50,8 @@ Use NPM in node.js / browser, or include single file from
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.
```ts
import { weierstrass } from '@noble/curves/weierstrass'; // Short Weierstrass curve
import { Fp } from '@noble/curves/modular';
import { weierstrass } from '@noble/curves/weierstrass';
import { sha256 } from '@noble/hashes/sha256';
import { hmac } from '@noble/hashes/hmac';
import { concatBytes, randomBytes } from '@noble/hashes/utils';
@ -53,8 +59,8 @@ import { concatBytes, randomBytes } from '@noble/hashes/utils';
const secp256k1 = weierstrass({
a: 0n,
b: 7n,
P: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn,
n: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n,
Fp: Fp(2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n),
n: 2n ** 256n - 432420386565659656852420866394968145599n,
Gx: 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
Gy: 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
hash: sha256,
@ -65,10 +71,10 @@ const key = secp256k1.utils.randomPrivateKey();
const pub = secp256k1.getPublicKey(key);
const msg = randomBytes(32);
const sig = secp256k1.sign(msg, key);
secp256k1.verify(sig, msg, pub); // true
sig.recoverPublicKey(msg); // == pub
const someonesPubkey = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
const shared = secp256k1.getSharedSecret(key, someonesPubkey);
secp256k1.verify(sig, msg, pub) === true;
sig.recoverPublicKey(msg) === pub;
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
const shared = secp256k1.getSharedSecret(key, someonesPub);
```
## API
@ -86,9 +92,9 @@ const shared = secp256k1.getSharedSecret(key, someonesPubkey);
* All curves expose same generic interface:
* `getPublicKey()`, `sign()`, `verify()` functions
* `Point` conforming to `Group` interface with add/multiply/double/negate/add/equals methods
* `CURVE` object with curve variables like `Gx`, `Gy`, `P` (field), `n` (order)
* `CURVE` object with curve variables like `Gx`, `Gy`, `Fp` (field), `n` (order)
* `utils` object with `randomPrivateKey()`, `mod()`, `invert()` methods (`mod CURVE.P`)
* All arithmetics is done with JS bigints over finite fields
* All arithmetics is done with JS bigints over finite fields, which is defined from `modular` sub-module
* Many features require hashing, which is not provided. `@noble/hashes` can be used for this purpose.
Any other library must conform to the CHash interface:
```ts
@ -99,19 +105,19 @@ const shared = secp256k1.getSharedSecret(key, someonesPubkey);
```
* w-ary non-adjacent form (wNAF) method with constant-time adjustments is used for point multiplication.
It is possible to enable precomputes for edwards & weierstrass curves.
Precomputes are calculated once (takes ~20-40ms), after that most `G` multiplications
- for example, `getPublicKey()`, `sign()` and similar methods - would be much faster.
Use `curve.utils.precompute()`
* Special params that tune performance can be optionally provided. For example:
* `sqrtMod` square root calculation, used for point decompression
Precomputes are calculated once (takes ~20-40ms), after that most `G` base point multiplications:
for example, `getPublicKey()`, `sign()` and similar methods - would be much faster.
Use `curve.utils.precompute()` to adjust precomputation window size
* You could use optional special params to tune performance:
* `Fp({sqrt})` square root calculation, used for point decompression
* `endo` endomorphism options for Koblitz curves
### edwards: Twisted Edwards curve
Twisted Edwards curve's formula is: ax² + y² = 1 + dx²y².
* You must specify curve params `a`, `d`, field `P`, order `n`, cofactor `h`, and coordinates `Gx`, `Gy` of generator point.
* For EdDSA signatures, params `hash` is also required. `adjustScalarBytes` which instructs how to change private scalars could be specified.
* You must specify curve params `a`, `d`, field `Fp`, order `n`, cofactor `h` and coordinates `Gx`, `Gy` of generator point
* For EdDSA signatures, params `hash` is also required. `adjustScalarBytes` which instructs how to change private scalars could be specified
```typescript
import { twistedEdwards } from '@noble/curves/edwards'; // Twisted Edwards curve
@ -128,14 +134,18 @@ const ed25519 = twistedEdwards({
Gy: 46316835694926478169428394003475163141307993866256225615783033603165251855960n,
hash: sha512,
randomBytes,
adjustScalarBytes(bytes) { // optional
adjustScalarBytes(bytes) { // optional in general, mandatory in ed25519
bytes[0] &= 248;
bytes[31] &= 127;
bytes[31] |= 64;
return bytes;
},
} as const);
ed25519.getPublicKey(ed25519.utils.randomPrivateKey());
const key = ed25519.utils.randomPrivateKey();
const pub = ed25519.getPublicKey(key);
const msg = new TextEncoder().encode('hello world'); // strings not accepted, must be Uint8Array
const sig = ed25519.sign(msg, key);
ed25519.verify(sig, msg, pub) === true;
```
`twistedEdwards()` returns `CurveFn` of following type:
@ -195,23 +205,23 @@ const x25519 = montgomery({
Short Weierstrass curve's formula is: y² = x³ + ax + b. Uses deterministic ECDSA from RFC6979. You can also specify `extraEntropy` in `sign()`.
* You must specify curve params: `a`, `b`; field `P`; curve order `n`; coordinates `Gx`, `Gy` of generator point
* You must specify curve params: `a`, `b`, field `Fp`, order `n`, cofactor `h` and coordinates `Gx`, `Gy` of generator point
* For ECDSA, you must specify `hash`, `hmac`. It is also possible to recover keys from signatures
* For ECDH, use `getSharedSecret(privKeyA, pubKeyB)`
* Optional params are `lowS` (default value), `sqrtMod` (square root chain) and `endo` (endomorphism)
* Optional params are `lowS` (default value) and `endo` (endomorphism)
```typescript
import { Fp } from '@noble/curves/modular';
import { weierstrass } from '@noble/curves/weierstrass'; // Short Weierstrass curve
import { sha256 } from '@noble/hashes/sha256';
import { hmac } from '@noble/hashes/hmac';
import { concatBytes, randomBytes } from '@noble/hashes/utils';
const secp256k1 = weierstrass({
// Required params
a: 0n,
b: 7n,
P: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn,
n: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n,
Fp: Fp(2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n),
n: 2n ** 256n - 432420386565659656852420866394968145599n,
Gx: 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
Gy: 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
hash: sha256,
@ -219,19 +229,14 @@ const secp256k1 = weierstrass({
randomBytes,
// Optional params
// Cofactor
h: BigInt(1),
// Allow only low-S signatures by default in sign() and verify()
lowS: true,
// More efficient curve-specific implementation of square root
sqrtMod(y: bigint) { return sqrt(y); },
// Endomorphism options
endo: {
h: 1n, // Cofactor
lowS: true, // Allow only low-S signatures by default in sign() and verify()
endo: { // Endomorphism options for Koblitz curve
// Beta param
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
beta: 0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501een,
// Split scalar k into k1, k2
splitScalar: (k: bigint) => {
return { k1neg: true, k1: 512n, k2neg: false, k2: 448n };
// return { k1neg: true, k1: 512n, k2neg: false, k2: 448n };
},
},
});
@ -262,8 +267,8 @@ export type CurveFn = {
ProjectivePoint: ProjectivePointConstructor;
Signature: SignatureConstructor;
utils: {
mod: (a: bigint, b?: bigint) => bigint;
invert: (number: bigint, modulo?: bigint) => bigint;
mod: (a: bigint) => bigint;
invert: (number: bigint) => bigint;
isValidPrivateKey(privateKey: PrivKey): boolean;
hashToPrivateKey: (hash: Hex) => Uint8Array;
randomPrivateKey: () => Uint8Array;
@ -276,12 +281,15 @@ export type CurveFn = {
Modular arithmetics utilities.
```typescript
import * as mod from '@noble/curves/modular';
mod.mod(21n, 10n); // 21 mod 10 == 1n; fixed version of 21 % 10
mod.invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse
mod.div(5n, 17n, 10n); // 5/17 mod 10 == 5 * invert(17) mod 10; division
mod.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion
mod.sqrt(21n, 73n); // sqrt(21) mod 73; square root
import { mod, invert, div, invertBatch, sqrt, Fp } from '@noble/curves/modular';
mod(21n, 10n); // 21 mod 10 == 1n; fixed version of 21 % 10
invert(17n, 10n); // invert(17) mod 10; modular multiplicative inverse
div(5n, 17n, 10n); // 5/17 mod 10 == 5 * invert(17) mod 10; division
invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion
sqrt(21n, 73n); // √21 mod 73; square root
const fp = Fp(2n ** 255n - 19n); // Finite field over 2^255-19
fp.mul(591n, 932n);
fp.pow(481n, 11024858120n);
```
### utils
@ -316,59 +324,43 @@ We consider infrastructure attacks like rogue NPM modules very important; that's
Benchmark results on Apple M2 with node v18.10:
```
==== secp256k1 ====
- getPublicKey1 (samples: 10000)
noble_old x 8,131 ops/sec @ 122μs/op
secp256k1 x 7,374 ops/sec @ 135μs/op
- getPublicKey255 (samples: 10000)
noble_old x 7,894 ops/sec @ 126μs/op
secp256k1 x 7,327 ops/sec @ 136μs/op
- sign (samples: 5000)
noble_old x 5,243 ops/sec @ 190μs/op
secp256k1 x 4,834 ops/sec @ 206μs/op
- getSharedSecret (samples: 1000)
noble_old x 653 ops/sec @ 1ms/op
secp256k1 x 634 ops/sec @ 1ms/op
- verify (samples: 1000)
secp256k1_old x 1,038 ops/sec @ 962μs/op
secp256k1 x 1,009 ops/sec @ 990μs/op
==== ed25519 ====
- getPublicKey (samples: 10000)
old x 8,632 ops/sec @ 115μs/op
noble x 8,390 ops/sec @ 119μs/op
- sign (samples: 5000)
old x 4,376 ops/sec @ 228μs/op
noble x 4,233 ops/sec @ 236μs/op
- verify (samples: 1000)
old x 865 ops/sec @ 1ms/op
noble x 860 ops/sec @ 1ms/op
==== ed448 ====
- getPublicKey (samples: 5000)
noble x 3,224 ops/sec @ 310μs/op
- sign (samples: 2500)
noble x 1,561 ops/sec @ 640μs/op
- verify (samples: 500)
noble x 313 ops/sec @ 3ms/op
==== nist ====
- getPublicKey (samples: 2500)
P256 x 7,993 ops/sec @ 125μs/op
P384 x 3,819 ops/sec @ 261μs/op
P521 x 2,074 ops/sec @ 481μs/op
- sign (samples: 1000)
P256 x 5,327 ops/sec @ 187μs/op
P384 x 2,728 ops/sec @ 366μs/op
P521 x 1,594 ops/sec @ 626μs/op
- verify (samples: 250)
P256 x 806 ops/sec @ 1ms/op
P384 x 353 ops/sec @ 2ms/op
P521 x 171 ops/sec @ 5ms/op
getPublicKey
secp256k1 x 5,241 ops/sec @ 190μs/op
P256 x 7,993 ops/sec @ 125μs/op
P384 x 3,819 ops/sec @ 261μs/op
P521 x 2,074 ops/sec @ 481μs/op
ed25519 x 8,390 ops/sec @ 119μs/op
ed448 x 3,224 ops/sec @ 310μs/op
sign
secp256k1 x 3,934 ops/sec @ 254μs/op
P256 x 5,327 ops/sec @ 187μs/op
P384 x 2,728 ops/sec @ 366μs/op
P521 x 1,594 ops/sec @ 626μs/op
ed25519 x 4,233 ops/sec @ 236μs/op
ed448 x 1,561 ops/sec @ 640μs/op
verify
secp256k1 x 731 ops/sec @ 1ms/op
P256 x 806 ops/sec @ 1ms/op
P384 x 353 ops/sec @ 2ms/op
P521 x 171 ops/sec @ 5ms/op
ed25519 x 860 ops/sec @ 1ms/op
ed448 x 313 ops/sec @ 3ms/op
getSharedSecret
secp256k1 x 445 ops/sec @ 2ms/op
recoverPublicKey
secp256k1 x 732 ops/sec @ 1ms/op
==== bls12-381 ====
getPublicKey x 817 ops/sec @ 1ms/op
sign x 50 ops/sec @ 19ms/op
verify x 34 ops/sec @ 28ms/op
pairing x 89 ops/sec @ 11ms/op
==== stark ====
- pedersen (samples: 500)
old x 85 ops/sec @ 11ms/op
noble x 1,216 ops/sec @ 822μs/op
- verify (samples: 500)
old x 302 ops/sec @ 3ms/op
noble x 698 ops/sec @ 1ms/op
pedersen
old x 85 ops/sec @ 11ms/op
noble x 1,216 ops/sec @ 822μs/op
verify
old x 302 ops/sec @ 3ms/op
noble x 698 ops/sec @ 1ms/op
```
## Contributing & testing

@ -2,14 +2,6 @@
Elliptic curves implementations. `@noble/curves` is zero-dependency library for internal arithmetics.
`micro-curve-definitions` is the actual implementations. Current functionality:
- NIST curves: P192, P224, P256, P384, P521 (ECDSA)
- secp256k1 (ECDSA, without Schnorr)
- stark curve
- bls12-381
- bn254
## Usage
```sh

@ -243,9 +243,7 @@ const Fp6Multiply = ({ c0, c1, c2 }: Fp6, rhs: Fp6 | bigint) => {
// t0 + (c1 + c2) * (r1 * r2) - (T1 + T2) * (u + 1)
c0: Fp2.add(
t0,
Fp2.mulByNonresidue(
Fp2.sub(Fp2.mul(Fp2.add(c1, c2), Fp2.add(r1, r2)), Fp2.add(t1, t2))
)
Fp2.mulByNonresidue(Fp2.sub(Fp2.mul(Fp2.add(c1, c2), Fp2.add(r1, r2)), Fp2.add(t1, t2)))
),
// (c0 + c1) * (r0 + r1) - (T0 + T1) + T2 * (u + 1)
c1: Fp2.add(
@ -318,10 +316,7 @@ const Fp6: mod.Field<Fp6> & Fp6Utils = {
let t2 = Fp2.sub(Fp2.square(c1), Fp2.mul(c0, c2)); // c1² - c0 * c2
// 1/(((c2 * T1 + c1 * T2) * v) + c0 * T0)
let t4 = Fp2.invert(
Fp2.add(
Fp2.mulByNonresidue(Fp2.add(Fp2.mul(c2, t1), Fp2.mul(c1, t2))),
Fp2.mul(c0, t0)
)
Fp2.add(Fp2.mulByNonresidue(Fp2.add(Fp2.mul(c2, t1), Fp2.mul(c1, t2))), Fp2.mul(c0, t0))
);
return { c0: Fp2.mul(t4, t0), c1: Fp2.mul(t4, t1), c2: Fp2.mul(t4, t2) };
},
@ -568,10 +563,7 @@ const Fp12: mod.Field<Fp12> & Fp12Utils = {
return {
c0: Fp6.add(Fp6.mulByNonresidue(t1), t0), // T1 * v + T0
// (c1 + c0) * [o0, o1+o4] - T0 - T1
c1: Fp6.sub(
Fp6.sub(Fp6.multiplyBy01(Fp6.add(c1, c0), o0, Fp2.add(o1, o4)), t0),
t1
),
c1: Fp6.sub(Fp6.sub(Fp6.multiplyBy01(Fp6.add(c1, c0), o0, Fp2.add(o1, o4)), t0), t1),
};
},
multiplyByFp2: ({ c0, c1 }, rhs: Fp2): Fp12 => ({
@ -624,20 +616,14 @@ const Fp12: mod.Field<Fp12> & Fp12Utils = {
const t3 = Fp12.mul(Fp12.conjugate(Fp12._cyclotomicSquare(t1)), t2);
const t4 = Fp12.conjugate(Fp12._cyclotomicExp(t3, x));
const t5 = Fp12.conjugate(Fp12._cyclotomicExp(t4, x));
const t6 = Fp12.mul(
Fp12.conjugate(Fp12._cyclotomicExp(t5, x)),
Fp12._cyclotomicSquare(t2)
);
const t6 = Fp12.mul(Fp12.conjugate(Fp12._cyclotomicExp(t5, x)), Fp12._cyclotomicSquare(t2));
const t7 = Fp12.conjugate(Fp12._cyclotomicExp(t6, x));
const t2_t5_pow_q2 = Fp12.frobeniusMap(Fp12.mul(t2, t5), 2);
const t4_t1_pow_q3 = Fp12.frobeniusMap(Fp12.mul(t4, t1), 3);
const t6_t1c_pow_q1 = Fp12.frobeniusMap(Fp12.mul(t6, Fp12.conjugate(t1)), 1);
const t7_t3c_t1 = Fp12.mul(Fp12.mul(t7, Fp12.conjugate(t3)), t1);
// (t2 * t5)^(q²) * (t4 * t1)^(q³) * (t6 * t1.conj)^(q^1) * t7 * t3.conj * t1
return Fp12.mul(
Fp12.mul(Fp12.mul(t2_t5_pow_q2, t4_t1_pow_q3), t6_t1c_pow_q1),
t7_t3c_t1
);
return Fp12.mul(Fp12.mul(Fp12.mul(t2_t5_pow_q2, t4_t1_pow_q3), t6_t1c_pow_q1), t7_t3c_t1);
},
};
const FP12_FROBENIUS_COEFFICIENTS = [
@ -897,10 +883,7 @@ function map_to_curve_simple_swu_9mod16(t: bigint[] | Fp2): [Fp2, Fp2] {
let v = Fp2.pow(denominator, 3n);
// u = N³ + a * N * D² + b * D³
let u = Fp2.add(
Fp2.add(
Fp2.pow(numerator, 3n),
Fp2.mul(Fp2.mul(iso_3_a, numerator), Fp2.pow(denominator, 2n))
),
Fp2.add(Fp2.pow(numerator, 3n), Fp2.mul(Fp2.mul(iso_3_a, numerator), Fp2.pow(denominator, 2n))),
Fp2.mul(iso_3_b, v)
);
// Attempt y = sqrt(u / v)

@ -3,7 +3,7 @@ import { sha512 } from '@noble/hashes/sha512';
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
import { twistedEdwards, ExtendedPointType } from '@noble/curves/edwards';
import { montgomery } from '@noble/curves/montgomery';
import { mod, pow2, isNegativeLE, Fp as FpFn } from '@noble/curves/modular';
import { mod, pow2, isNegativeLE, Fp } from '@noble/curves/modular';
import {
ensureBytes,
equalBytes,
@ -98,7 +98,7 @@ const ED25519_DEF = {
// Negative number is P - number, and division is invert(number, P)
d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'),
// Finite field 𝔽p over which we'll do calculations; 2n ** 255n - 19n
Fp: FpFn(ED25519_P),
Fp: Fp(ED25519_P),
// Subgroup order: how many points ed25519 has
// 2n ** 252n + 27742317777372353535851937790883648493n;
n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'),

@ -1,6 +1,6 @@
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import { sha256 } from '@noble/hashes/sha256';
import { Fp as FpFn, mod, pow2 } from '@noble/curves/modular';
import { Fp, mod, pow2 } from '@noble/curves/modular';
import { createCurve } from './_shortw_utils.js';
import { PointType } from '@noble/curves/weierstrass';
import {
@ -58,7 +58,7 @@ function sqrtMod(y: bigint): bigint {
return pow2(t2, _2n, P);
}
const Fp = FpFn(secp256k1P, undefined, undefined, { sqrt: sqrtMod });
const fp = Fp(secp256k1P, undefined, undefined, { sqrt: sqrtMod });
export const secp256k1 = createCurve(
{
@ -68,7 +68,7 @@ export const secp256k1 = createCurve(
b: BigInt(7),
// Field over which we'll do calculations;
// 2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n
Fp,
Fp: fp,
// Curve order, total count of valid points in the field
n: secp256k1N,
// Base point (x, y) aka generator point