noble-curves/README.md
Paul Miller 7e825520f1
README
2023-02-25 10:05:48 +01:00

30 KiB

noble-curves

Audited & minimal JS implementation of elliptic curve cryptography.

  • noble family, zero dependencies
  • Short Weierstrass, Edwards, Montgomery curves
  • ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement
  • #️⃣ hash to curve for encoding or hashing an arbitrary string to an elliptic curve point
  • 🧜‍♂️ Poseidon ZK-friendly hash
  • 🏎 Ultra-fast, hand-optimized for caveats of JS engines
  • 🔍 Unique tests ensure correctness with Wycheproof vectors and cryptofuzz differential fuzzing
  • 🔻 Tree-shaking-friendly: there is no entry point, which ensures small size of your app

Package consists of two parts:

  1. Abstract, zero-dependency EC algorithms
  2. Implementations, utilizing one dependency @noble/hashes, providing ready-to-use:
    • NIST curves secp256r1/P256, secp384r1/P384, secp521r1/P521
    • SECG curve secp256k1
    • ed25519/curve25519/x25519/ristretto255, edwards448/curve448/x448 RFC7748 / RFC8032 / ZIP215 stuff
    • pairing-friendly curves bls12-381, bn254

Check out Upgrading if you've previously used single-feature noble packages (secp256k1, ed25519). See Resources for articles and real-world software that uses curves.

This library belongs to noble crypto

noble-crypto — high-security, easily auditable set of contained cryptographic libraries and tools.

  • No dependencies, protection against supply chain attacks
  • Easily auditable TypeScript/JS code
  • Supported in all major browsers and stable node.js versions
  • All releases are signed with PGP keys
  • Check out homepage & all libraries: curves (secp256k1, ed25519), hashes

Usage

Use NPM for browser / node.js:

npm install @noble/curves

For Deno, use it with npm specifier. In browser, you could also include the single file from GitHub's releases page.

The library is tree-shaking-friendly and does not expose root entry point as import * from '@noble/curves'. Instead, you need to import specific primitives. This is done to ensure small size of your apps.

Implementations

Each curve can be used in the following way:

import { secp256k1 } from '@noble/curves/secp256k1'; // ECMAScript Modules (ESM) and Common.js
// import { secp256k1 } from 'npm:@noble/curves@1.2.0/secp256k1'; // Deno
const priv = secp256k1.utils.randomPrivateKey();
const pub = secp256k1.getPublicKey(priv);
const msg = new Uint8Array(32).fill(1);
const sig = secp256k1.sign(msg, priv);
secp256k1.verify(sig, msg, pub) === true;

const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c126236';
const pub2 = secp256k1.getPublicKey(privHex); // keys & other inputs can be Uint8Array-s or hex strings

All curves:

import { secp256k1, schnorr } from '@noble/curves/secp256k1';
import { ed25519, ed25519ph, ed25519ctx, x25519, RistrettoPoint } from '@noble/curves/ed25519';
import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
import { p256 } from '@noble/curves/p256';
import { p384 } from '@noble/curves/p384';
import { p521 } from '@noble/curves/p521';
import { pallas, vesta } from '@noble/curves/pasta';
import * as stark from '@noble/curves/stark';
import { bls12_381 } from '@noble/curves/bls12-381';
import { bn254 } from '@noble/curves/bn';
import { jubjub } from '@noble/curves/jubjub';

Weierstrass curves feature recovering public keys from signatures and ECDH key agreement:

// extraEntropy https://moderncrypto.org/mail-archive/curves/2017/000925.html
const sigImprovedSecurity = secp256k1.sign(msg, priv, { extraEntropy: true });
sig.recoverPublicKey(msg) === pub; // public key recovery
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
const shared = secp256k1.getSharedSecret(priv, someonesPub); // ECDH (elliptic curve diffie-hellman)

secp256k1 has schnorr signature implementation which follows BIP340:

import { schnorr } from '@noble/curves/secp256k1';
const priv = schnorr.utils.randomPrivateKey();
const pub = schnorr.getPublicKey(priv);
const msg = new TextEncoder().encode('hello');
const sig = schnorr.sign(msg, priv);
const isValid = schnorr.verify(sig, msg, pub);
console.log(isValid);

ed25519 module has ed25519ctx / ed25519ph variants, x25519 ECDH and ristretto255. It follows ZIP215 and can be used in consensus-critical applications:

import { ed25519 } from '@noble/curves/ed25519';

// Variants from RFC8032: with context, prehashed
import { ed25519ctx, ed25519ph } from '@noble/curves/ed25519';

// ECDH using curve25519 aka x25519
import { x25519 } from '@noble/curves/ed25519';
const priv = 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4';
const pub = 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c';
x25519.getSharedSecret(priv, pub) === x25519.scalarMult(priv, pub); // aliases
x25519.getPublicKey(priv) === x25519.scalarMultBase(priv);

// hash-to-curve
import { hashToCurve, encodeToCurve } from '@noble/curves/ed25519';

import { RistrettoPoint } from '@noble/curves/ed25519';
const rp = RistrettoPoint.fromHex(
  '6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919'
);
RistrettoPoint.hashToCurve('Ristretto is traditionally a short shot of espresso coffee');
// also has add(), equals(), multiply(), toRawBytes() methods

ed448 module is basically the same:

import { ed448, ed448ph, ed448ctx, x448 } from '@noble/curves/ed448';
import { hashToCurve, encodeToCurve } from '@noble/curves/ed448';

BLS12-381 pairing-friendly Barreto-Lynn-Scott elliptic curve construction allows to construct zk-SNARKs at the 128-bit security and use aggregated, batch-verifiable threshold signatures, using Boneh-Lynn-Shacham signature scheme.

import { bls12_381 as bls } from '@noble/curves/bls12-381';
const privateKey = '67d53f170b908cabb9eb326c3c337762d59289a8fec79f7bc9254b584b73265c';
const message = '64726e3da8';
const publicKey = bls.getPublicKey(privateKey);
const signature = bls.sign(message, privateKey);
const isValid = bls.verify(signature, message, publicKey);
console.log({ publicKey, signature, isValid });

// Sign 1 msg with 3 keys
const privateKeys = [
  '18f020b98eb798752a50ed0563b079c125b0db5dd0b1060d1c1b47d4a193e1e4',
  'ed69a8c50cf8c9836be3b67c7eeff416612d45ba39a5c099d48fa668bf558c9c',
  '16ae669f3be7a2121e17d0c68c05a8f3d6bef21ec0f2315f1d7aec12484e4cf5',
];
const messages = ['d2', '0d98', '05caf3'];
const publicKeys = privateKeys.map(bls.getPublicKey);
const signatures2 = privateKeys.map((p) => bls.sign(message, p));
const aggPubKey2 = bls.aggregatePublicKeys(publicKeys);
const aggSignature2 = bls.aggregateSignatures(signatures2);
const isValid2 = bls.verify(aggSignature2, message, aggPubKey2);
console.log({ signatures2, aggSignature2, isValid2 });

// Sign 3 msgs with 3 keys
const signatures3 = privateKeys.map((p, i) => bls.sign(messages[i], p));
const aggSignature3 = bls.aggregateSignatures(signatures3);
const isValid3 = bls.verifyBatch(aggSignature3, messages, publicKeys);
console.log({ publicKeys, signatures3, aggSignature3, isValid3 });
// bls.pairing(PointG1, PointG2) // pairings

// hash-to-curve examples can be seen below

Abstract API

Abstract API allows to define custom curves. All arithmetics is done with JS bigints over finite fields, which is defined from modular sub-module. For scalar multiplication, we use precomputed tables with w-ary non-adjacent form (wNAF). Precomputes are enabled for weierstrass and edwards BASE points of a curve. You could precompute any other point (e.g. for ECDH) using utils.precompute() method: check out examples.

There are following zero-dependency algorithms:

abstract/weierstrass: Short Weierstrass curve

import { weierstrass } from '@noble/curves/abstract/weierstrass';

Short Weierstrass curve's formula is y² = x³ + ax + b. weierstrass expects arguments a, b, field Fp, curve order n, cofactor h and coordinates Gx, Gy of generator point.

k generation is done deterministically, following RFC6979. For this you will need hmac & hash, which in our implementations is provided by noble-hashes. If you're using different hashing library, make sure to wrap it in the following interface:

type CHash = {
  (message: Uint8Array): Uint8Array;
  blockLen: number;
  outputLen: number;
  create(): any;
};

Weierstrass points:

  1. Exported as ProjectivePoint
  2. Represented in projective (homogeneous) coordinates: (x, y, z) ∋ (x=x/z, y=y/z)
  3. Use complete exception-free formulas for addition and doubling
  4. Can be decoded/encoded from/to Uint8Array / hex strings using ProjectivePoint.fromHex and ProjectivePoint#toRawBytes()
  5. Have assertValidity() which checks for being on-curve
  6. Have toAffine() and x / y getters which convert to 2d xy affine coordinates
// T is usually bigint, but can be something else like complex numbers in BLS curves
interface ProjPointType<T> extends Group<ProjPointType<T>> {
  readonly px: T;
  readonly py: T;
  readonly pz: T;
  multiply(scalar: bigint): ProjPointType<T>;
  multiplyUnsafe(scalar: bigint): ProjPointType<T>;
  multiplyAndAddUnsafe(Q: ProjPointType<T>, a: bigint, b: bigint): ProjPointType<T> | undefined;
  toAffine(iz?: T): AffinePoint<T>;
  isTorsionFree(): boolean;
  clearCofactor(): ProjPointType<T>;
  assertValidity(): void;
  hasEvenY(): boolean;
  toRawBytes(isCompressed?: boolean): Uint8Array;
  toHex(isCompressed?: boolean): string;
}
// Static methods for 3d XYZ points
interface ProjConstructor<T> extends GroupConstructor<ProjPointType<T>> {
  new (x: T, y: T, z: T): ProjPointType<T>;
  fromAffine(p: AffinePoint<T>): ProjPointType<T>;
  fromHex(hex: Hex): ProjPointType<T>;
  fromPrivateKey(privateKey: PrivKey): ProjPointType<T>;
}

ECDSA signatures are represented by Signature instances and can be described by the interface:

interface SignatureType {
  readonly r: bigint;
  readonly s: bigint;
  readonly recovery?: number;
  assertValidity(): void;
  addRecoveryBit(recovery: number): SignatureType;
  hasHighS(): boolean;
  normalizeS(): SignatureType;
  recoverPublicKey(msgHash: Hex): ProjPointType<bigint>;
  toCompactRawBytes(): Uint8Array;
  toCompactHex(): string;
  // DER-encoded
  toDERRawBytes(): Uint8Array;
  toDERHex(): string;
}
type SignatureConstructor = {
  new (r: bigint, s: bigint): SignatureType;
  fromCompact(hex: Hex): SignatureType;
  fromDER(hex: Hex): SignatureType;
};

Example implementing secq256k1 (NOT secp256k1) cycle of secp256k1 with Fp/N flipped.

import { weierstrass } from '@noble/curves/abstract/weierstrass';
import { Field } from '@noble/curves/abstract/modular'; // finite field, mod arithmetics done over it
import { sha256 } from '@noble/hashes/sha256'; // 3rd-party sha256() of type utils.CHash, with blockLen/outputLen
import { hmac } from '@noble/hashes/hmac'; // 3rd-party hmac() that will accept sha256()
import { concatBytes, randomBytes } from '@noble/hashes/utils'; // 3rd-party utilities
const secq256k1 = weierstrass({
  // secq256k1: cycle of secp256k1 with Fp/N flipped.
  a: 0n,
  b: 7n,
  Fp: Field(2n ** 256n - 432420386565659656852420866394968145599n),
  n: 2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n,
  Gx: 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
  Gy: 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
  hash: sha256,
  hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(sha256, key, concatBytes(...msgs)),
  randomBytes,
});

// All curves expose same generic interface.
const priv = secq256k1.utils.randomPrivateKey();
secq256k1.getPublicKey(priv); // Convert private key to public.
const sig = secq256k1.sign(msg, priv); // Sign msg with private key.
secq256k1.verify(sig, msg, priv); // Verify if sig is correct.

const Point = secq256k1.ProjectivePoint;
const point = Point.BASE; // Elliptic curve Point class and BASE point static var.
point.add(point).equals(point.double()); // add(), equals(), double() methods
point.subtract(point).equals(Point.ZERO); // subtract() method, ZERO static var
point.negate(); // Flips point over x/y coordinate.
point.multiply(31415n); // Multiplication of Point by scalar.

point.assertValidity(); // Checks for being on-curve
point.toAffine(); // Converts to 2d affine xy coordinates

secq256k1.CURVE.n;
secq256k1.CURVE.Fp.mod();
secq256k1.CURVE.hash();

// precomputes
const fast = secq256k1.utils.precompute(8, Point.fromHex(someonesPubKey));
fast.multiply(privKey); // much faster ECDH now

weierstrass() returns CurveFn:

type SignOpts = { lowS?: boolean; prehash?: boolean; extraEntropy: boolean | Uint8Array };
type CurveFn = {
  CURVE: ReturnType<typeof validateOpts>;
  getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
  getSharedSecret: (privateA: PrivKey, publicB: Hex, isCompressed?: boolean) => Uint8Array;
  sign: (msgHash: Hex, privKey: PrivKey, opts?: SignOpts) => SignatureType;
  verify: (
    signature: Hex | SignatureType,
    msgHash: Hex,
    publicKey: Hex,
    opts?: { lowS?: boolean; prehash?: boolean }
  ) => boolean;
  ProjectivePoint: ProjectivePointConstructor;
  Signature: SignatureConstructor;
  utils: {
    normPrivateKeyToScalar: (key: PrivKey) => bigint;
    isValidPrivateKey(key: PrivKey): boolean;
    randomPrivateKey: () => Uint8Array;
    precompute: (windowSize?: number, point?: ProjPointType<bigint>) => ProjPointType<bigint>;
  };
};

abstract/edwards: Twisted Edwards curve

Twisted Edwards curve's formula is ax² + y² = 1 + dx²y². You must specify a, d, field Fp, order n, cofactor h and coordinates Gx, Gy of generator point.

For EdDSA signatures, hash param required. adjustScalarBytes which instructs how to change private scalars could be specified.

Edwards points:

  1. Exported as ExtendedPoint
  2. Represented in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z)
  3. Use complete exception-free formulas for addition and doubling
  4. Can be decoded/encoded from/to Uint8Array / hex strings using ExtendedPoint.fromHex and ExtendedPoint#toRawBytes()
  5. Have assertValidity() which checks for being on-curve
  6. Have toAffine() and x / y getters which convert to 2d xy affine coordinates
  7. Have isTorsionFree(), clearCofactor() and isSmallOrder() utilities to handle torsions
interface ExtPointType extends Group<ExtPointType> {
  readonly ex: bigint;
  readonly ey: bigint;
  readonly ez: bigint;
  readonly et: bigint;
  assertValidity(): void;
  multiply(scalar: bigint): ExtPointType;
  multiplyUnsafe(scalar: bigint): ExtPointType;
  isSmallOrder(): boolean;
  isTorsionFree(): boolean;
  clearCofactor(): ExtPointType;
  toAffine(iz?: bigint): AffinePoint<bigint>;
}
// Static methods of Extended Point with coordinates in X, Y, Z, T
interface ExtPointConstructor extends GroupConstructor<ExtPointType> {
  new (x: bigint, y: bigint, z: bigint, t: bigint): ExtPointType;
  fromAffine(p: AffinePoint<bigint>): ExtPointType;
  fromHex(hex: Hex): ExtPointType;
  fromPrivateKey(privateKey: Hex): ExtPointType;
}

Example implementing edwards25519:

import { twistedEdwards } from '@noble/curves/abstract/edwards';
import { Field, div } from '@noble/curves/abstract/modular';
import { sha512 } from '@noble/hashes/sha512';

const Fp = Field(2n ** 255n - 19n);
const ed25519 = twistedEdwards({
  a: -1n,
  d: Fp.div(-121665n, 121666n), // -121665n/121666n mod p
  Fp,
  n: 2n ** 252n + 27742317777372353535851937790883648493n,
  h: 8n,
  Gx: 15112221349535400772501151409588531511454012693041857206046113283949847762202n,
  Gy: 46316835694926478169428394003475163141307993866256225615783033603165251855960n,
  hash: sha512,
  randomBytes,
  adjustScalarBytes(bytes) {
    // optional; but mandatory in ed25519
    bytes[0] &= 248;
    bytes[31] &= 127;
    bytes[31] |= 64;
    return bytes;
  },
} as const);

twistedEdwards() returns CurveFn of following type:

type CurveFn = {
  CURVE: ReturnType<typeof validateOpts>;
  getPublicKey: (privateKey: Hex) => Uint8Array;
  sign: (message: Hex, privateKey: Hex, context?: Hex) => Uint8Array;
  verify: (sig: SigType, message: Hex, publicKey: Hex, context?: Hex) => boolean;
  ExtendedPoint: ExtPointConstructor;
  utils: {
    randomPrivateKey: () => Uint8Array;
    getExtendedPublicKey: (key: PrivKey) => {
      head: Uint8Array;
      prefix: Uint8Array;
      scalar: bigint;
      point: PointType;
      pointBytes: Uint8Array;
    };
  };
};

abstract/montgomery: Montgomery curve

The module contains methods for x-only ECDH on Curve25519 / Curve448 from RFC7748. Proper Elliptic Curve Points are not implemented yet.

You must specify curve params Fp, a, Gu coordinate of u, montgomeryBits and nByteLength.

import { montgomery } from '@noble/curves/abstract/montgomery';

const x25519 = montgomery({
  Fp: Field(2n ** 255n - 19n),
  a: 486662n,
  Gu: 9n,
  montgomeryBits: 255,
  nByteLength: 32,
  // Optional param
  adjustScalarBytes(bytes) {
    bytes[0] &= 248;
    bytes[31] &= 127;
    bytes[31] |= 64;
    return bytes;
  },
});

abstract/hash-to-curve: Hashing strings to curve points

The module allows to hash arbitrary strings to elliptic curve points. Implements hash-to-curve v11.

Every curve has exported hashToCurve and encodeToCurve methods:

import { hashToCurve, encodeToCurve } from '@noble/curves/secp256k1';
import { randomBytes } from '@noble/hashes/utils';
hashToCurve('0102abcd');
console.log(hashToCurve(randomBytes()));
console.log(encodeToCurve(randomBytes()));


import { bls12_381 } from '@noble/curves/bls12-381';
bls12_381.G1.hashToCurve(randomBytes(), { DST: 'another' });
bls12_381.G2.hashToCurve(randomBytes(), { DST: 'custom' });

If you need low-level methods from spec:

expand_message_xmd (spec) produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.

function expand_message_xmd(
  msg: Uint8Array,
  DST: Uint8Array,
  lenInBytes: number,
  H: CHash
): Uint8Array;
function expand_message_xof(
  msg: Uint8Array,
  DST: Uint8Array,
  lenInBytes: number,
  k: number,
  H: CHash
): Uint8Array;

hash_to_field(msg, count, options) (spec) hashes arbitrary-length byte strings to a list of one or more elements of a finite field F. _ msg a byte string containing the message to hash _ count the number of elements of F to output _ options {DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H} _ Returns [u_0, ..., u_(count - 1)], a list of field elements.

function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][];

abstract/poseidon: Poseidon hash

Implements Poseidon ZK-friendly hash.

There are many poseidon variants with different constants. We don't provide them: you should construct them manually. The only variant provided resides in stark module: inspect it for proper usage.

import { poseidon } from '@noble/curves/abstract/poseidon';

type PoseidonOpts = {
  Fp: Field<bigint>;
  t: number;
  roundsFull: number;
  roundsPartial: number;
  sboxPower?: number;
  reversePartialPowIdx?: boolean; // Hack for stark
  mds: bigint[][];
  roundConstants: bigint[][];
};
const instance = poseidon(opts: PoseidonOpts);

abstract/bls

The module abstracts BLS (Barreto-Lynn-Scott) primitives. In theory you should be able to write BLS12-377, BLS24, and others with it.

abstract/modular: Modular arithmetics utilities

import * as mod from '@noble/curves/abstract/modular';
const fp = mod.Field(2n ** 255n - 19n); // Finite field over 2^255-19
fp.mul(591n, 932n); // multiplication
fp.pow(481n, 11024858120n); // exponentiation
fp.div(5n, 17n); // division: 5/17 mod 2^255-19 == 5 * invert(17)
fp.sqrt(21n); // square root

// Generic non-FP utils are also available
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.invertBatch([1n, 2n, 4n], 21n); // => [1n, 11n, 16n] in one inversion

Creating private keys from hashes

Suppose you have sha256(something) (e.g. from HMAC) and you want to make a private key from it. Even though p256 or secp256k1 may have 32-byte private keys, and sha256 output is also 32-byte, you can't just use it and reduce it modulo CURVE.n.

Doing so will make the result key biased.

To avoid the bias, we implement FIPS 186 B.4.1, which allows to take arbitrary byte array and produce valid scalars / private keys with bias being neglible.

Use hash-to-curve if you need hashing to public keys; the function in the module instead operates on private keys.

import { p256 } from '@noble/curves/p256';
import { sha256 } from '@noble/hashes/sha256';
import { hkdf } from '@noble/hashes/hkdf';
const someKey = new Uint8Array(32).fill(2); // Needs to actually be random, not .fill(2)
const derived = hkdf(sha256, someKey, undefined, 'application', 40); // 40 bytes
const validPrivateKey = mod.hashToPrivateScalar(derived, p256.CURVE.n);

abstract/utils: General utilities

import * as utils from '@noble/curves/abstract/utils';

utils.bytesToHex(Uint8Array.from([0xde, 0xad, 0xbe, 0xef]));
utils.hexToBytes('deadbeef');
utils.hexToNumber();
utils.bytesToNumberBE(Uint8Array.from([0xde, 0xad, 0xbe, 0xef]));
utils.bytesToNumberLE(Uint8Array.from([0xde, 0xad, 0xbe, 0xef]));
utils.numberToBytesBE(123n, 32);
utils.numberToBytesLE(123n, 64);
utils.numberToHexUnpadded(123n);
utils.concatBytes(Uint8Array.from([0xde, 0xad]), Uint8Array.from([0xbe, 0xef]));
utils.nLength(255n);
utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));

Security

The library had no prior security audit. The library has been fuzzed by Guido Vranken's cryptofuzz: you can run the fuzzer by yourself to check it.

Timing attack considerations: we are using non-CT bigints. However, JIT-compiler and Garbage Collector make "constant time" extremely hard to achieve in a scripting language. Which means any other JS library can't have constant-timeness. Even statically typed Rust, a language without GC, makes it harder to achieve constant-time for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones. Use low-level libraries & languages. Nonetheless we're targetting algorithmic constant time.

We consider infrastructure attacks like rogue NPM modules very important; that's why it's crucial to minimize the amount of 3rd-party dependencies & native bindings. If your app uses 500 dependencies, any dep could get hacked and you'll be downloading malware with every npm install. Our goal is to minimize this attack vector. As for devDependencies used by the library:

  • @scure base, bip32, bip39 (used in tests), micro-bmark (benchmark), micro-should (testing) are developed by us and follow the same practices such as: minimal library size, auditability, signed releases
  • prettier (linter), fast-check (property-based testing), typescript versions are locked and rarely updated. Every update is checked with npm-diff. The packages are big, which makes it hard to audit their source code thoroughly and fully.
  • They are only used if you clone the git repo and want to add some feature to it. End-users won't use them.

Speed

Benchmark results on Apple M2 with node v19:

secp256k1
init x 58 ops/sec @ 17ms/op
getPublicKey x 5,640 ops/sec @ 177μs/op
sign x 3,909 ops/sec @ 255μs/op
verify x 780 ops/sec @ 1ms/op
getSharedSecret x 465 ops/sec @ 2ms/op
recoverPublicKey x 740 ops/sec @ 1ms/op
schnorr.sign x 597 ops/sec @ 1ms/op
schnorr.verify x 775 ops/sec @ 1ms/op

P256
init x 31 ops/sec @ 31ms/op
getPublicKey x 5,607 ops/sec @ 178μs/op
sign x 3,930 ops/sec @ 254μs/op
verify x 540 ops/sec @ 1ms/op

P384
init x 15 ops/sec @ 63ms/op
getPublicKey x 2,622 ops/sec @ 381μs/op
sign x 1,913 ops/sec @ 522μs/op
verify x 222 ops/sec @ 4ms/op

P521
init x 8 ops/sec @ 119ms/op
getPublicKey x 1,371 ops/sec @ 729μs/op
sign x 1,090 ops/sec @ 917μs/op
verify x 118 ops/sec @ 8ms/op

ed25519
init x 47 ops/sec @ 20ms/op
getPublicKey x 9,414 ops/sec @ 106μs/op
sign x 4,516 ops/sec @ 221μs/op
verify x 912 ops/sec @ 1ms/op

ed448
init x 17 ops/sec @ 56ms/op
getPublicKey x 3,363 ops/sec @ 297μs/op
sign x 1,615 ops/sec @ 619μs/op
verify x 319 ops/sec @ 3ms/op

stark
init x 35 ops/sec @ 28ms/op
pedersen x 884 ops/sec @ 1ms/op
poseidon x 8,598 ops/sec @ 116μs/op
verify x 528 ops/sec @ 1ms/op

ecdh
├─x25519 x 1,337 ops/sec @ 747μs/op
├─secp256k1 x 461 ops/sec @ 2ms/op
├─P256 x 441 ops/sec @ 2ms/op
├─P384 x 179 ops/sec @ 5ms/op
├─P521 x 93 ops/sec @ 10ms/op
└─x448 x 496 ops/sec @ 2ms/op

bls12-381
init x 32 ops/sec @ 30ms/op
getPublicKey 1-bit x 858 ops/sec @ 1ms/op
getPublicKey x 858 ops/sec @ 1ms/op
sign x 49 ops/sec @ 20ms/op
verify x 34 ops/sec @ 28ms/op
pairing x 94 ops/sec @ 10ms/op
aggregatePublicKeys/8 x 116 ops/sec @ 8ms/op
aggregatePublicKeys/32 x 31 ops/sec @ 31ms/op
aggregatePublicKeys/128 x 7 ops/sec @ 125ms/op
aggregateSignatures/8 x 45 ops/sec @ 22ms/op
aggregateSignatures/32 x 11 ops/sec @ 84ms/op
aggregateSignatures/128 x 3 ops/sec @ 332ms/opp

Resources

Article about some of library's features: Learning fast elliptic-curve cryptography. Elliptic curve calculator: paulmillr.com/ecc

Upgrading

If you're coming from single-feature noble packages, the following changes need to be kept in mind:

  • 2d affine (x, y) points have been removed to reduce complexity and improve speed
  • Removed number support as a type for private keys, bigint is still supported
  • mod, invert are no longer present in utils: use @noble/curves/abstract/modular

Upgrading from @noble/secp256k1 1.7:

  • Compressed (33-byte) public keys are now returned by default, instead of uncompressed
  • Methods are now synchronous. Setting secp.utils.hmacSha256 is no longer required
  • sign()
    • der, recovered options were removed
    • canonical was renamed to lowS
    • Return type is now { r: bigint, s: bigint, recovery: number } instance of Signature
  • verify()
    • strict was renamed to lowS
  • recoverPublicKey(): moved to sig instance Signature#recoverPublicKey(msgHash)
  • Point was removed: use ProjectivePoint in xyz coordinates
  • utils: Many methods were removed, others were moved to schnorr namespace

Upgrading from @noble/ed25519 1.7:

  • Methods are now synchronous. Setting secp.utils.hmacSha256 is no longer required
  • ed25519ph, ed25519ctx
  • Point was removed: use ExtendedPoint in xyzt coordinates
  • Signature was removed
  • getSharedSecret was removed: use separate x25519 sub-module
  • bigint is no longer allowed in getPublicKey, sign, verify. Reason: ed25519 is LE, can lead to bugs

Contributing & testing

  1. Clone the repository
  2. npm install to install build dependencies like TypeScript
  3. npm run build to compile TypeScript code
  4. npm run test will execute all main tests

License

The MIT License (MIT)

Copyright (c) 2022 Paul Miller (https://paulmillr.com)

See LICENSE file.