From ba121ff24c99684efebed54cc08fedec8aebb275 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 27 Dec 2022 02:16:45 +0000 Subject: [PATCH] README, lint --- README.md | 186 ++++++++++++++--------------- curve-definitions/README.md | 8 -- curve-definitions/src/bls12-381.ts | 29 +---- curve-definitions/src/ed25519.ts | 4 +- curve-definitions/src/secp256k1.ts | 6 +- 5 files changed, 100 insertions(+), 133 deletions(-) diff --git a/README.md b/README.md index 0a38864..2eeae50 100644 --- a/README.md +++ b/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 diff --git a/curve-definitions/README.md b/curve-definitions/README.md index cc4ffd2..aa0a476 100644 --- a/curve-definitions/README.md +++ b/curve-definitions/README.md @@ -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 diff --git a/curve-definitions/src/bls12-381.ts b/curve-definitions/src/bls12-381.ts index 1b4f39b..d58ae02 100644 --- a/curve-definitions/src/bls12-381.ts +++ b/curve-definitions/src/bls12-381.ts @@ -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 & 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 & 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 & 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) diff --git a/curve-definitions/src/ed25519.ts b/curve-definitions/src/ed25519.ts index 260518b..b33c13e 100644 --- a/curve-definitions/src/ed25519.ts +++ b/curve-definitions/src/ed25519.ts @@ -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'), diff --git a/curve-definitions/src/secp256k1.ts b/curve-definitions/src/secp256k1.ts index 388c843..803be78 100644 --- a/curve-definitions/src/secp256k1.ts +++ b/curve-definitions/src/secp256k1.ts @@ -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