GithubHelp home page GithubHelp logo

paulmillr / noble-curves Goto Github PK

View Code? Open in Web Editor NEW
590.0 11.0 54.0 10.63 MB

Audited & minimal JS implementation of elliptic curve cryptography.

Home Page: https://paulmillr.com/noble

License: MIT License

TypeScript 50.43% JavaScript 49.47% Go 0.10%
crypto cryptography ecdsa ed25519 ed448 eddsa elliptic-curve-cryptography elliptic-curves javascript nist

noble-curves's Introduction

noble-curves

Audited & minimal JS implementation of elliptic curve cryptography.

  • πŸ”’ Audited by independent security firms
  • πŸ”» Tree-shaking-friendly: use only what's necessary, other code won't be included
  • 🏎 Ultra-fast, hand-optimized for caveats of JS engines
  • πŸ” Unique tests ensure correctness: property-based, cross-library and Wycheproof vectors, fuzzing
  • ➰ Short Weierstrass, Edwards, Montgomery curves
  • ✍️ ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement, hashing to curves
  • πŸ”– SUF-CMA, SBS (non-repudiation), ZIP215 (consensus friendliness) features for ed25519
  • πŸ§œβ€β™‚οΈ Poseidon ZK-friendly hash
  • πŸͺΆ 178KB for everything, 25KB for single-curve build

For discussions, questions and support, visit GitHub Discussions section of the repository.

This library belongs to noble cryptography

noble cryptography β€” high-security, easily auditable set of contained cryptographic libraries and tools.

Usage

npm install @noble/curves

We support all major platforms and runtimes. For Deno, ensure to use npm specifier. For React Native, you may need a polyfill for getRandomValues. A standalone file noble-curves.js is also available.

// import * from '@noble/curves'; // Error: use sub-imports, to ensure small app size
import { secp256k1 } from '@noble/curves/secp256k1'; // ESM and Common.js
// import { secp256k1 } from 'npm:@noble/[email protected]/secp256k1'; // Deno

Implementations

Implementations use noble-hashes. If you want to use a different hashing library, abstract API doesn't depend on them.

ECDSA signature scheme

Generic example that works for all curves, shown for secp256k1:

import { secp256k1 } from '@noble/curves/secp256k1';
const priv = secp256k1.utils.randomPrivateKey();
const pub = secp256k1.getPublicKey(priv);
const msg = new Uint8Array(32).fill(1); // message hash (not message) in ecdsa
const sig = secp256k1.sign(msg, priv); // `{prehash: true}` option is available
const isValid = secp256k1.verify(sig, msg, pub) === true;

// hex strings are also supported besides Uint8Arrays:
const privHex = '46c930bc7bb4db7f55da20798697421b98c4175a52c630294d75a84b9c126236';
const pub2 = secp256k1.getPublicKey(privHex);

We support P256 (secp256r1), P384 (secp384r1), P521 (secp521r1).

ECDSA public key recovery & extra entropy

// let sig = secp256k1.Signature.fromCompact(sigHex); // or .fromDER(sigDERHex)
// sig = sig.addRecoveryBit(bit); // bit is not serialized into compact / der format
sig.recoverPublicKey(msg).toRawBytes(); // === pub; // public key recovery

// extraEntropy https://moderncrypto.org/mail-archive/curves/2017/000925.html
const sigImprovedSecurity = secp256k1.sign(msg, priv, { extraEntropy: true });

ECDH: Elliptic Curve Diffie-Hellman

// 1. The output includes parity byte. Strip it using shared.slice(1)
// 2. The output is not hashed. More secure way is sha256(shared) or hkdf(shared)
const someonesPub = secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
const shared = secp256k1.getSharedSecret(priv, someonesPub);

Schnorr signatures over secp256k1 (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);

ed25519, X25519, ristretto255

import { ed25519 } from '@noble/curves/ed25519';
const priv = ed25519.utils.randomPrivateKey();
const pub = ed25519.getPublicKey(priv);
const msg = new TextEncoder().encode('hello');
const sig = ed25519.sign(msg, priv);
ed25519.verify(sig, msg, pub); // Default mode: follows ZIP215
ed25519.verify(sig, msg, pub, { zip215: false }); // RFC8032 / FIPS 186-5

Default verify behavior follows ZIP215 and can be used in consensus-critical applications. It has SUF-CMA (strong unforgeability under chosen message attacks). zip215: false option switches verification criteria to strict RFC8032 / FIPS 186-5 and additionally provides non-repudiation with SBS.

X25519 follows RFC7748.

// 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);
x25519.getPublicKey(x25519.utils.randomPrivateKey());

// ed25519 => x25519 conversion
import { edwardsToMontgomeryPub, edwardsToMontgomeryPriv } from '@noble/curves/ed25519';
edwardsToMontgomeryPub(ed25519.getPublicKey(ed25519.utils.randomPrivateKey()));
edwardsToMontgomeryPriv(ed25519.utils.randomPrivateKey());

ristretto255 follows irtf draft.

// hash-to-curve, ristretto255
import { utf8ToBytes } from '@noble/hashes/utils';
import { sha512 } from '@noble/hashes/sha512';
import {
  hashToCurve,
  encodeToCurve,
  RistrettoPoint,
  hashToRistretto255,
} from '@noble/curves/ed25519';

const msg = utf8ToBytes('Ristretto is traditionally a short shot of espresso coffee');
hashToCurve(msg);

const rp = RistrettoPoint.fromHex(
  '6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919'
);
RistrettoPoint.BASE.multiply(2n).add(rp).subtract(RistrettoPoint.BASE).toRawBytes();
RistrettoPoint.ZERO.equals(dp) === false;
// pre-hashed hash-to-curve
RistrettoPoint.hashToCurve(sha512(msg));
// full hash-to-curve including domain separation tag
hashToRistretto255(msg, { DST: 'ristretto255_XMD:SHA-512_R255MAP_RO_' });

ed448, X448, decaf448

import { ed448 } from '@noble/curves/ed448';
const priv = ed448.utils.randomPrivateKey();
const pub = ed448.getPublicKey(priv);
const msg = new TextEncoder().encode('whatsup');
const sig = ed448.sign(msg, priv);
ed448.verify(sig, msg, pub);

// Variants from RFC8032: prehashed
import { ed448ph } from '@noble/curves/ed448';

ECDH using Curve448 aka X448, follows RFC7748.

import { x448 } from '@noble/curves/ed448';
x448.getSharedSecret(priv, pub) === x448.scalarMult(priv, pub); // aliases
x448.getPublicKey(priv) === x448.scalarMultBase(priv);

// ed448 => x448 conversion
import { edwardsToMontgomeryPub } from '@noble/curves/ed448';
edwardsToMontgomeryPub(ed448.getPublicKey(ed448.utils.randomPrivateKey()));

decaf448 follows irtf draft.

import { utf8ToBytes } from '@noble/hashes/utils';
import { shake256 } from '@noble/hashes/sha3';
import { hashToCurve, encodeToCurve, DecafPoint, hashToDecaf448 } from '@noble/curves/ed448';

const msg = utf8ToBytes('Ristretto is traditionally a short shot of espresso coffee');
hashToCurve(msg);

const dp = DecafPoint.fromHex(
  'c898eb4f87f97c564c6fd61fc7e49689314a1f818ec85eeb3bd5514ac816d38778f69ef347a89fca817e66defdedce178c7cc709b2116e75'
);
DecafPoint.BASE.multiply(2n).add(dp).subtract(DecafPoint.BASE).toRawBytes();
DecafPoint.ZERO.equals(dp) === false;
// pre-hashed hash-to-curve
DecafPoint.hashToCurve(shake256(msg, { dkLen: 112 }));
// full hash-to-curve including domain separation tag
hashToDecaf448(msg, { DST: 'decaf448_XOF:SHAKE256_D448MAP_RO_' });

Same RFC7748 / RFC8032 / IRTF draft are followed.

bls12-381

See abstract/bls.

All available imports

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 { bls12_381 } from '@noble/curves/bls12-381';
import { bn254 } from '@noble/curves/bn254'; // also known as alt_bn128
import { jubjub } from '@noble/curves/jubjub';
import { bytesToHex, hexToBytes, concatBytes, utf8ToBytes } from '@noble/curves/abstract/utils';

Accessing a curve's variables

import { secp256k1 } from '@noble/curves/secp256k1';
// Every curve has `CURVE` object that contains its parameters, field, and others
console.log(secp256k1.CURVE.p); // field modulus
console.log(secp256k1.CURVE.n); // curve order
console.log(secp256k1.CURVE.a, secp256k1.CURVE.b); // equation params
console.log(secp256k1.CURVE.Gx, secp256k1.CURVE.Gy); // base point coordinates

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.

weierstrass: Short Weierstrass curve

import { weierstrass } from '@noble/curves/abstract/weierstrass';
import { Field } from '@noble/curves/abstract/modular'; // finite field for mod arithmetics
import { sha256 } from '@noble/hashes/sha256'; // 3rd-party sha256() of type utils.CHash
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.
  // https://personaelabs.org/posts/spartan-ecdsa
  // https://zcash.github.io/halo2/background/curves.html#cycles-of-curves
  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,
});

// Replace weierstrass() with weierstrassPoints() if you don't need ECDSA, hash, hmac, randomBytes

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;
};

// example
function sha256(message: Uint8Array) { return _internal_lowlvl(message) }
sha256.outputLen = 32; // 32 bytes of output for sha2-256

Message hash is expected instead of message itself:

  • sign(msgHash, privKey) is default behavior, assuming you pre-hash msg with sha2, or other hash
  • sign(msg, privKey, {prehash: true}) option can be used if you want to pass the message itself

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
// `weierstrassPoints()` returns `CURVE` and `ProjectivePoint`
// `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>;
  };
};

// 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;
  get x(): bigint;
  get y(): bigint;
  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;
};

More examples:

// 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.
const sig2 = secq256k1.sign(msg, priv, { prehash: true }); // hash(msg)
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.p;
secq256k1.CURVE.Fp.mod();
secq256k1.CURVE.hash();

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

edwards: Twisted Edwards curve

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

const Fp = Field(2n ** 255n - 19n);
const ed25519 = twistedEdwards({
  a: Fp.create(-1n),
  d: Fp.div(-121665n, 121666n), // -121665n/121666n mod p
  Fp: 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);

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.

We support non-repudiation, which help in following scenarios:

  • Contract Signing: if A signed an agreement with B using key that allows repudiation, it can later claim that it signed a different contract
  • E-voting: malicious voters may pick keys that allow repudiation in order to deny results
  • Blockchains: transaction of amount X might also be valid for a different amount Y

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
// `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;
    };
  };
};

interface ExtPointType extends Group<ExtPointType> {
  readonly ex: bigint;
  readonly ey: bigint;
  readonly ez: bigint;
  readonly et: bigint;
  get x(): bigint;
  get y(): bigint;
  assertValidity(): void;
  multiply(scalar: bigint): ExtPointType;
  multiplyUnsafe(scalar: bigint): ExtPointType;
  isSmallOrder(): boolean;
  isTorsionFree(): boolean;
  clearCofactor(): ExtPointType;
  toAffine(iz?: bigint): AffinePoint<bigint>;
  toRawBytes(isCompressed?: boolean): Uint8Array;
  toHex(isCompressed?: boolean): string;
}
// 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;
}

montgomery: Montgomery curve

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

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

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.

bls: Barreto-Lynn-Scott curves

The module abstracts BLS (Barreto-Lynn-Scott) pairing-friendly elliptic curve construction. They allow to construct zk-SNARKs and use aggregated, batch-verifiable threshold signatures, using Boneh-Lynn-Shacham signature scheme.

The module doesn't expose CURVE property: use G1.CURVE, G2.CURVE instead. Only BLS12-381 is implemented currently. Defining BLS12-377 and BLS24 should be straightforward.

Main methods and properties are:

  • getPublicKey(privateKey)
  • sign(message, privateKey)
  • verify(signature, message, publicKey)
  • aggregatePublicKeys(publicKeys)
  • aggregateSignatures(signatures)
  • G1 and G2 curves containing CURVE and ProjectivePoint
  • Signature property with fromHex, toHex methods
  • fields containing Fp, Fp2, Fp6, Fp12, Fr

The default BLS uses short public keys (with public keys in G1 and signatures in G2). Short signatures (public keys in G2 and signatures in G1) is also supported, using:

  • getPublicKeyForShortSignatures(privateKey)
  • signShortSignature(message, privateKey)
  • verifyShortSignature(signature, message, publicKey)
  • aggregateShortSignatures(signatures)
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 });

// Use custom DST, e.g. for Ethereum consensus layer
const htfEthereum = {DST: 'BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_'};
const signatureEth = bls.sign(message, privateKey, htfEthereum);
const isValidEth = bls.verify(signature, message, publicKey, htfEthereum);
console.log({ signatureEth, isValidEth });

// 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 });

// Pairings, with and without final exponentiation
bls.pairing(PointG1, PointG2);
bls.pairing(PointG1, PointG2, false);
bls.fields.Fp12.finalExponentiate(bls.fields.Fp12.mul(PointG1, PointG2));

// Others
bls.G1.ProjectivePoint.BASE, bls.G2.ProjectivePoint.BASE
bls.fields.Fp, bls.fields.Fp2, bls.fields.Fp12, bls.fields.Fr
bls.params.x, bls.params.r, bls.params.G1b, bls.params.G2b

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

hash-to-curve: Hashing strings to curve points

The module allows to hash arbitrary strings to elliptic curve points. Implements RFC 9380.

Every curve has exported hashToCurve and encodeToCurve methods. You should always prefer hashToCurve for security:

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' });

Low-level methods from the 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 // For CHash see abstract/weierstrass docs section
): Uint8Array;
// produces a uniformly random byte string using an extendable-output function (XOF) H.
function expand_message_xof(
  msg: Uint8Array,
  DST: Uint8Array,
  lenInBytes: number,
  k: number,
  H: CHash
): Uint8Array;
// Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F
function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][];

/**
 * * `DST` is a domain separation tag, defined in section 2.2.5
 * * `p` characteristic of F, where F is a finite field of characteristic p and order q = p^m
 * * `m` is extension degree (1 for prime fields)
 * * `k` is the target security target in bits (e.g. 128), from section 5.1
 * * `expand` is `xmd` (SHA2, SHA3, BLAKE) or `xof` (SHAKE, BLAKE-XOF)
 * * `hash` conforming to `utils.CHash` interface, with `outputLen` / `blockLen` props
 */
type UnicodeOrBytes = string | Uint8Array;
type Opts = {
  DST: UnicodeOrBytes;
  p: bigint;
  m: number;
  k: number;
  expand?: 'xmd' | 'xof';
  hash: CHash;
};

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. Check out micro-starknet package for a proper example.

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

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

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

Field operations are not constant-time: they are using JS bigints, see security. The fact is mostly irrelevant, but the important method to keep in mind is pow, which may leak exponent bits, when used naΓ―vely.

mod.Field is always field over prime. Non-prime fields aren't supported for now. We don't test for prime-ness for speed and because algorithms are probabilistic anyway. Initializing a non-prime field could make your app suspectible to DoS (infilite loop) on Tonelli-Shanks square root calculation.

Unlike mod.invert, mod.invertBatch won't throw on 0: make sure to throw an error yourself.

Creating private keys from hashes

You can't simply make a 32-byte private key from a 32-byte hash. Doing so will make the key biased.

To make the bias negligible, we follow FIPS 186-5 A.2 and RFC 9380. This means, for 32-byte key, we would need 48-byte hash to get 2^-128 bias, which matches curve security level.

hashToPrivateScalar() that hashes to private key was created for this purpose. Use abstract/hash-to-curve if you need to hash to public key.

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

utils: Useful utilities

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

utils.bytesToHex(Uint8Array.from([0xde, 0xad, 0xbe, 0xef]));
utils.hexToBytes('deadbeef');
utils.numberToHexUnpadded(123n);
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.concatBytes(Uint8Array.from([0xde, 0xad]), Uint8Array.from([0xbe, 0xef]));
utils.nLength(255n);
utils.equalBytes(Uint8Array.from([0xde]), Uint8Array.from([0xde]));

Security

The library has been independently audited:

It is tested against property-based, cross-library and Wycheproof vectors, and has fuzzing by Guido Vranken's cryptofuzz.

If you see anything unusual: investigate and report.

Constant-timeness

JIT-compiler and Garbage Collector make "constant time" extremely hard to achieve timing attack resistance 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.

Supply chain security

  • Commits are signed with PGP keys, to prevent forgery. Make sure to verify commit signatures.
  • Releases are transparent and built on GitHub CI. Make sure to verify provenance logs
  • Rare releasing is followed to ensure less re-audit need for end-users
  • Dependencies are minimized and locked-down:
    • If your app has 500 dependencies, any dep could get hacked and you'll be downloading malware with every install. We make sure to use as few dependencies as possible
    • We prevent automatic dependency updates by locking-down version ranges. Every update is checked with npm-diff
    • One dependency noble-hashes is used, by the same author, to provide hashing functionality
  • Dev Dependencies are only used if you want to contribute to the repo. They are disabled for end-users:
    • scure-base, scure-bip32, scure-bip39, micro-bmark and micro-should are developed by the same author and follow identical security practices
    • prettier (linter), fast-check (property-based testing) and typescript are used for code quality, vector generation and ts compilation. The packages are big, which makes it hard to audit their source code thoroughly and fully

Randomness

We're deferring to built-in crypto.getRandomValues which is considered cryptographically secure (CSPRNG).

In the past, browsers had bugs that made it weak: it may happen again. Implementing a userspace CSPRNG to get resilient to the weakness is even worse: there is no reliable userspace source of quality entropy.

Speed

Benchmark results on Apple M2 with node v20:

secp256k1
init x 68 ops/sec @ 14ms/op
getPublicKey x 6,750 ops/sec @ 148ΞΌs/op
sign x 5,206 ops/sec @ 192ΞΌs/op
verify x 880 ops/sec @ 1ms/op
getSharedSecret x 536 ops/sec @ 1ms/op
recoverPublicKey x 852 ops/sec @ 1ms/op
schnorr.sign x 685 ops/sec @ 1ms/op
schnorr.verify x 908 ops/sec @ 1ms/op

p256
init x 38 ops/sec @ 26ms/op
getPublicKey x 6,530 ops/sec @ 153ΞΌs/op
sign x 5,074 ops/sec @ 197ΞΌs/op
verify x 626 ops/sec @ 1ms/op

p384
init x 17 ops/sec @ 57ms/op
getPublicKey x 2,883 ops/sec @ 346ΞΌs/op
sign x 2,358 ops/sec @ 424ΞΌs/op
verify x 245 ops/sec @ 4ms/op

p521
init x 9 ops/sec @ 109ms/op
getPublicKey x 1,516 ops/sec @ 659ΞΌs/op
sign x 1,271 ops/sec @ 786ΞΌs/op
verify x 123 ops/sec @ 8ms/op

ed25519
init x 54 ops/sec @ 18ms/op
getPublicKey x 10,269 ops/sec @ 97ΞΌs/op
sign x 5,110 ops/sec @ 195ΞΌs/op
verify x 1,049 ops/sec @ 952ΞΌs/op

ed448
init x 19 ops/sec @ 51ms/op
getPublicKey x 3,775 ops/sec @ 264ΞΌs/op
sign x 1,771 ops/sec @ 564ΞΌs/op
verify x 351 ops/sec @ 2ms/op

ecdh
β”œβ”€x25519 x 1,466 ops/sec @ 682ΞΌs/op
β”œβ”€secp256k1 x 539 ops/sec @ 1ms/op
β”œβ”€p256 x 511 ops/sec @ 1ms/op
β”œβ”€p384 x 199 ops/sec @ 5ms/op
β”œβ”€p521 x 103 ops/sec @ 9ms/op
└─x448 x 548 ops/sec @ 1ms/op

bls12-381
init x 36 ops/sec @ 27ms/op
getPublicKey 1-bit x 973 ops/sec @ 1ms/op
getPublicKey x 970 ops/sec @ 1ms/op
sign x 55 ops/sec @ 17ms/op
verify x 39 ops/sec @ 25ms/op
pairing x 106 ops/sec @ 9ms/op
aggregatePublicKeys/8 x 129 ops/sec @ 7ms/op
aggregatePublicKeys/32 x 34 ops/sec @ 28ms/op
aggregatePublicKeys/128 x 8 ops/sec @ 112ms/op
aggregatePublicKeys/512 x 2 ops/sec @ 446ms/op
aggregatePublicKeys/2048 x 0 ops/sec @ 1778ms/op
aggregateSignatures/8 x 50 ops/sec @ 19ms/op
aggregateSignatures/32 x 13 ops/sec @ 74ms/op
aggregateSignatures/128 x 3 ops/sec @ 296ms/op
aggregateSignatures/512 x 0 ops/sec @ 1180ms/op
aggregateSignatures/2048 x 0 ops/sec @ 4715ms/op

hash-to-curve
hash_to_field x 91,600 ops/sec @ 10ΞΌs/op
secp256k1 x 2,373 ops/sec @ 421ΞΌs/op
p256 x 4,310 ops/sec @ 231ΞΌs/op
p384 x 1,664 ops/sec @ 600ΞΌs/op
p521 x 807 ops/sec @ 1ms/op
ed25519 x 3,088 ops/sec @ 323ΞΌs/op
ed448 x 1,247 ops/sec @ 801ΞΌs/op

Upgrading

Previously, the library was split into single-feature packages noble-secp256k1, noble-ed25519 and noble-bls12-381.

Curves continue their original work. The single-feature packages changed their direction towards providing minimal 4kb implementations of cryptography, which means they have less features.

Upgrading from noble-secp256k1 2.0 or noble-ed25519 2.0: no changes, libraries are compatible.

Upgrading from noble-secp256k1 1.7:

  • getPublicKey
    • now produce 33-byte compressed signatures by default
    • to use old behavior, which produced 65-byte uncompressed keys, set argument isCompressed to false: getPublicKey(priv, false)
  • sign
    • is now sync
    • now returns Signature instance with { r, s, recovery } properties
    • canonical option was renamed to lowS
    • recovered option has been removed because recovery bit is always returned now
    • der option has been removed. There are 2 options:
      1. Use compact encoding: fromCompact, toCompactRawBytes, toCompactHex. Compact encoding is simply a concatenation of 32-byte r and 32-byte s.
      2. If you must use DER encoding, switch to noble-curves (see above).
  • verify
    • is now sync
    • strict option was renamed to lowS
  • getSharedSecret
    • now produce 33-byte compressed signatures by default
    • to use old behavior, which produced 65-byte uncompressed keys, set argument isCompressed to false: getSharedSecret(a, b, false)
  • recoverPublicKey(msg, sig, rec) was changed to sig.recoverPublicKey(msg)
  • number type for private keys have been removed: use bigint instead
  • Point (2d xy) has been changed to ProjectivePoint (3d xyz)
  • utils were split into utils (same api as in noble-curves) and etc (hmacSha256Sync and others)

Upgrading from @noble/ed25519 1.7:

  • Methods are now sync by default
  • bigint is no longer allowed in getPublicKey, sign, verify. Reason: ed25519 is LE, can lead to bugs
  • Point (2d xy) has been changed to ExtendedPoint (xyzt)
  • Signature was removed: just use raw bytes or hex now
  • utils were split into utils (same api as in noble-curves) and etc (sha512Sync and others)
  • getSharedSecret was moved to x25519 module
  • toX25519 has been moved to edwardsToMontgomeryPub and edwardsToMontgomeryPriv methods

Upgrading from @noble/bls12-381:

  • Methods and classes were renamed:
    • PointG1 -> G1.Point, PointG2 -> G2.Point
    • PointG2.fromSignature -> Signature.decode, PointG2.toSignature -> Signature.encode
  • Fp2 ORDER was corrected

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

Resources

Check out paulmillr.com/noble for useful resources, articles, documentation and demos related to the library.

License

The MIT License (MIT)

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

See LICENSE file.

noble-curves's People

Contributors

ardislu avatar arobsn avatar carleeto avatar dhrubabasu avatar legobeat avatar mahnunchik avatar mirceanis avatar paulmillr avatar randombit avatar secure12 avatar steveluscher avatar stknob avatar sublimator avatar xrchz avatar yhc125 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

noble-curves's Issues

BLS threshold m-of-n signatures

It's said in readme that BLS can be used well for threshold signatures, with link to article that describes how it works cryptographically.
However I couldn't find how to do it using noble-curves. Is it possible currently?

Bls point addition algorithm correctness

Disclaimer: This might not be an issue with the library, however, I have no other explanation atm.

I have implemented the point addition for BLS curves on my own using an algorithm from hyperelliptic (https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html, both 98 and 07 algo) and tried to match the result (Point.equals) with the algorithm used by Point and the result was not the same point. I then went through history and reused the previous implementation of Point.add, and it also failed.

Is this a bug or is it expected?

Starknet : get complete pubKey from X part

Hello,
In Starknet network, the public key stored in the accounts is a reduced part of the complete pubKey. Only the X part is stored. To be able to perform a signature verification, Starknet is launching a Python code to recover the complete (X+Y) pubKey .
I would like, with your Typescript library, to recover the complete pubKey from the X part, or at least the Y coordinate from the X part.
You said me that this code can be used :

fromBytes(bytes: Uint8Array) {

I made some extraction of this code. I would like to perform a test, with a signature verification, but the compiler do not like this :

import { starkCurve } from '../utils/ec';

const message = ["0x53463473467", "0x879678967", "0x67896789678"];
const msgHash = starkCurve.hashChain(message) as string;
const sign = starkCurve.sign(msgHash, privateKey);

const verif = starkCurve.verify(sign, msgHash, pubKey3)
  • variable sign is SignatureType.

  • verify function expect signature: Hex

and the ts compiler do not accept this type mismatch.

How to proceed?

Add bitcoin as a topic to this repository

Hello,

This repository is listed on the Awesome Bitcoin list, which is a collection of useful Bitcoin projects. However, it seems that the 'bitcoin' topic is missing from this repository's topics.

Adding the 'bitcoin' topic will help users discover your project more easily and recognize its relevance to the Bitcoin ecosystem. To add the topic, please follow these steps:

  1. Navigate to the main page of the repository.
  2. Click on the gear icon next to "About" on the right side of the page.
  3. In the "Topics" section, type 'bitcoin' and press Enter.
  4. Click "Save changes."

Thank you for your attention and for contributing to the Bitcoin community!

hash-to-curve: hash_to_field expand cannot be undefined

Note that in bls12-381 Scalar implements HashToField. So this is the method to test, not the utility method from_okm.

should(`produce correct scalars (${SCALAR_VECTORS.length} vectors)`, () => {
const options = {
p: bls.CURVE.r,
m: 1,
expand: undefined,
};
for (let vector of SCALAR_VECTORS) {
const [okmAscii, expectedHex] = vector;
const expected = BigInt('0x' + expectedHex);
const okm = utf8ToBytes(okmAscii);
const scalars = hash_to_field(okm, 1, Object.assign({}, bls.CURVE.htfDefaults, options));
deepStrictEqual(scalars[0][0], expected);

Do you need TextEncoder?

First of all, great work with your library you've done an amazing job.

I'm using it from a pure v8 runtime, and the only issue I have is the use of TextEncoder. I have patched your library to:

Replace the line:
https://github.com/paulmillr/noble-curves/blob/79dd7d342636f421d36acbf3dbe2014ad7ba7ece/src/abstract/utils.ts#LL118C8-L118C8

With:

let uint8Array = new Uint8Array(str.length);
for (var i = 0; i < str.length; i++) {
    let charCode = str.charCodeAt(i);
    if (charCode > 127) {
        throw new Error('Not ascii. Text must be in ascii encoding');
    }
    uint8Array[i] = charCode;
}
return uint8Array;

And I haven't had any issues. I would assume my use-case is not really supported, but I guess if you wanted to you could create a method asciiToUint8Array which you use internally which AFAICT is all you need. That said, TextEncoder seems like a pretty reasonable dependency, so makes sense if you want to leave it how it is.

DST should be array of bytes.

Regarding the parameters of hash_to_field:

DST: string, 
p: bigint, 
m: number, 
k: number, 
expand: 'xmd' | 'xof',
hash: H,

DST must be an array of bytes.
There should be a way to validate that H is conforming with the expander, so it avoid to call expander = "xof" and hash = "sha-256" for example.

* @param msg a byte string containing the message to hash
* @param count the number of elements of F to output
* @param 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.
*/
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
const { p, k, m, hash, expand, DST: _DST } = options;
isBytes(msg);
isNum(count);
if (typeof _DST !== 'string') throw new Error('DST must be valid');
const log2p = p.toString(2).length;
const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above
const len_in_bytes = count * m * L;
const DST = utf8ToBytes(_DST);
const pseudo_random_bytes =
expand === 'xmd'
? expand_message_xmd(msg, DST, len_in_bytes, hash)
: expand === 'xof'
? expand_message_xof(msg, DST, len_in_bytes, k, hash)
: msg;

How to use Schnorr over BN254 + Grumpkin

Hello,
For context, I am working with the Noir lang and the Aztec protocol, which uses bn254 and Grumpkin curves, defined here.

How can I set up pairing on bn254? I am looking into how to set up a weierstrass instance of bn254 with a G2 pairing group so I can do aggregation - will this lib allow for that?

(amazing work btw, keep it up!)

Problem importing in a TS project

While trying to import stark curve (or any other) from micro-curve-definitions package. Seems like the project isn't build properly as there is no build directory and also no entry file (index.js).

Cannot find module 'micro-curve-definitions' or its corresponding type declarations. TS throws this error even after properly installing the package

Screenshot 2022-12-15 at 13 10 01

This is what the package looks like in node_modules

CommonJs for micro-curve-definitions

The micro-curve-definitions module is published with only esm files. It would be beneficial to also include cjs builds so both formats can be supported the same way it is done for @noble/curves.

hashToPrivateScalar Error: hex string is invalid: unpadded 63

        let caip10 = `eip155:1:${App.ADDR}`
        let domain = "domain.eth"
        let msg = `Generate Deterministic IPNS Keys for '${domain}'\n\nSigned By: ${caip10}`;
        let sig = await App.SIGNER.signMessage(msg);
        let password = "pass12#$"
        let inputKey = sha256(
            hexToBytes(
                sig.toLowerCase().startsWith('0x') ? sig.slice(2) : sig
            ) 
        )
        let info = `${caip10}:${domain}`
        let salt = sha256(`${info}:${password ? password : ''}:${sig.slice(-64)}`)
        let hashKey = hkdf(sha256, inputKey, salt, info, 42)
        let privKey = hashToPrivateScalar(hashKey, ed25519.CURVE.n).toString(16)
        let pubKey = await ed25519.getPublicKey(privKey)

utils.js:86 Uncaught (in promise) Error: private key must be valid hex string, got "d06c76f87d42fe0e683b454318543ba25463b34a4c17594f32c05a3051ae608". Cause: Error: hex string is invalid: unpadded 63

bls_12_381 CURVE.G2.fromBytes mutates input

This is a minor inconvenience and unexpected. All of the APIs seem to not mutate the input, but the CURVE.G2.fromBytes does, so running it twice on the same input fails.

Problem importing in a TS project - 2

This is similar to #2 with a different import error

node_modules/micro-curve-definitions/lib/index.d.ts' is not a module.ts(2306)

Edit: Can be fixed using import { keccak, pedersen } from 'micro-curve-definitions/lib/stark'; but then it causes the following build error

✘ [ERROR] Could not resolve "@noble/curves/weierstrass"

    node_modules/micro-curve-definitions/lib/stark.js:6:28:
      6 β”‚ import { weierstrass } from '@noble/curves/weierstrass';
        β•΅                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~

  The path "./weierstrass" is not exported by package "@noble/curves":

    node_modules/micro-curve-definitions/node_modules/@noble/curves/package.json:34:13:
      34 β”‚   "exports": {
         β•΅              ^

  You can mark the path "@noble/curves/weierstrass" as external to exclude it from the bundle, which
  will remove this error.

Ability to multiply Point by CURVE.n

I am working on implementing the SPAKE2+ draft using @noble/curves and am running into a little trouble when implementing their Algorithm used for Point Generation. The reference implementation performs a check on generated point (p != ec.zero_elem() and p * p.l() == ec.zero_elem()). In order to reproduce their results I have had to resort to some pretty ugly hacks (creating a wnaf instance for each curve, wrapping/unwrapping ProjectivePoint or ExtendedPoint for the various curves, and calling wnaf.unsafeLadder directly). RFC 8032's example python implementation handles this operation without objection.

Is there a better way of doing this that I am not seeing? Are the authors of SPAKE2+ doing something odd?

The specification does not require additional entropy length

RFC6979 3.6: additional k' (optional).
A use case may be a protocol that requires a non-deterministic signature algorithm on a system that does not have access to a high-quality random source. It suffices that the additional data k' is non-repeating (e.g., a signature counter or a monotonic clock) to ensure "random-looking" signatures are indistinguishable, in a cryptographic way, from plain (EC)DSA signatures. In [SP800-90A] terminology, k' is the "additional input" that can be set as a parameter when generating pseudorandom bits. This variant can be thought of as a "strengthening" of the randomness of the source of the additional data k'.

https://datatracker.ietf.org/doc/html/rfc6979#section-3.6

According to the specification there is no requirements for k' length.

Expected behaviour: extraEntropy checked to be a bytes.

Actual behaviour: it checked to be a 32 bytes length.

if (ent != null) {
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
const e = ent === true ? randomBytes(Fp.BYTES) : ent; // generate random bytes OR pass as-is
seedArgs.push(ensureBytes('extraEntropy', e, Fp.BYTES)); // check for being of size BYTES
}

Verifying HashToCurve results

How can I verify that my HashToCurve results are correct as per these tests here: https://github.com/paulmillr/noble-curves/blob/main/test/hash-to-curve/edwards25519_XMD%3ASHA-512_ELL2_RO_.json ?

This is the code I've implemented:

import { hashToCurve, encodeToCurve } from '@noble/curves/ed25519';
import { numberToBytesLE, bytesToHex } from '@noble/curves/abstract/utils';

let str = "";
let encoder = new TextEncoder(); 
let arr = encoder.encode(str);

var x = numberToBytesLE(hashToCurve(arr).toAffine().y);
var y = bytesToHex(x);
console.log(y); //0301ab58b84523561c1e1db48a76bcfe74c92f9c32856f7dbb683ffbfd513a5f

I've tried finding the value of y in the test pages linked above but couldn't seem to find it. I'm sure that I'm comparing the wrong values, so any help would be great.

v2: recoverPublicKey should return bytes

I tried to run the example from the README to recover the public key from a signature
sig.recoverPublicKey(msg) === pub; // public key recovery
which results actually as false.

I saw in the test the secp256k1.getPublicKey(priv); gets wrapped into a Point object
const publicKey = Point.fromHex(secp.getPublicKey(privateKey)).toHex(false);
which isn't that expected using the library.

I would expect to get an Uint8Array back when calling sig.recoverPublicKey(msg)

Test code:

const { secp256k1 } = require("@noble/curves/secp256k1");

const priv = secp256k1.utils.randomPrivateKey();
const pub = secp256k1.getPublicKey(priv);
console.log(`pub: ${pub}`);
const msg = new Uint8Array(32).fill(1);
const sig = secp256k1.sign(msg, priv);
const isValid = secp256k1.verify(sig, msg, pub) === true;
console.log(`isValid: ${isValid}`);
const recoverPub = sig.recoverPublicKey(msg);
console.log(`recoverPub: ${recoverPub}`);
const isRecoverValid = sig.recoverPublicKey(msg) === pub;
console.log(`isRecoverValid: ${isRecoverValid}`);

cannot use `finalExponentiate` with type safety

In https://github.com/drand/drand-client we use the bls12-381 curve for verifying signatures created by our network.
We have a custom scheme that creates signatures on G1 rather than G2, and thus far have been performing our own pairing operations as demonstrated here.

With the newest curves version, it appears that finalExponentiate is on the type Fp12Utils here which is available on the CurveType type, which gets passed into the bls constructor here.
Unfortunately, as defines its own fields param, we can't use the finalExponentiate function directly, without casting to any. Other types we could cast it to are private, but even then casting feels a little ugly.

Is there another suggested way to use finalExponentiate directly or would it be possible to expose it?

Thanks for all your hard work! Your libraries have saved us a lot of pain!

Feature: Allow more than 32 bytes to be passed as private key (seed) to Edwards curves

Allow more than 32 bytes to be passed as private key (seed) to Edwards curves.

For the compatability with elliptic implementation it would be helpful to be able to pass more then 32 bytes (64 bytes in my case) as a secret.

Edwards curves use a hash of the secret, so more bytes don't reduce security.

key = ensureBytes('private key', key, len);

Preposed solution:

function getExtendedPublicKey(key: Hex, noCheck: boolean = false) {
    const len = nByteLength;
    if (!noCheck) {
      key = ensureBytes('private key', key, len);
    }

Insight on Default Value Strategy for `auxRand` in Schnorr Signing

I am currently using the noble library to maintain a Bitcoin package, @bitcoinerlab/secp256k1, that aligns with the interface of tiny-secp256k1 in the bitcoinjs-lib ecosystem. This package is ultimately dependent on the bitcoin-core/secp256k1 library.

Recently, an issue was reported in our package related to the inconsistency of Schnorr signatures when auxRand is not passed.

Upon investigation, I noted a discrepancy in the strategy used to handle auxRand when it's not passed. In noble-curves and noble-secp256k1 1.7, auxRand defaults to a random value if it's not passed, as seen in this line of code.

However, the bitcoin-core/secp256k1 library handles this scenario differently, opting for a deterministic approach when auxRand is not provided.

I understand this is not a bug in noble-curves, and the chosen strategy is both safe and valid. However, given these differences, I'm finding it challenging to use noble to accurately emulate the behaviour of bitcoin-core/secp256k1 when auxRand isn't provided (without resorting to forking noble-curves).

Due to the hashing, I believe it's not practically feasible to precompute an auxRand that would result in the same deterministic behaviour as in bitcoin-core/secp256k1.

I would appreciate your insights and guidance on this issue. Is there a workaround or a potential modification I can implement to align the handling of auxRand more closely with the bitcoin-core/secp256k1 approach?

Thank you for your time!

Remove utf8ToBytes definition

You already have the utf8ToBytes function in noble-hashes which this project depends on, so you might as well just use it and not redefine it here. Actually the project already is a bit of a mix between calling nobel-curves.utf8ToBytes and nobel-hashes.utf8ToBytes (which isn't a big deal, as they do the same thing, but a bit weird?).

Anyway, not a big deal but I think it's cleaner and selfishly it allows me to only have to patch 1 library instead of 2 (since I'm running in an environment without TextEncoder)

toDERHex incompatible with other libs

Hi,

I am trying to use this module in the browser, and we have a backend which runs golang with "go-libp2p/core/crypto/key.go" using secp256k1.

The problem is when I sign a message using noble-curves and export the sig using toDERHex, in libp2p (they are using DER) the verification fails without error.

Same private key is used btw

Lack of method toRawBytes in interface ExtPointType

It seems that there are no way to call toRawBytes in typescript without casting it to <any>.

const point = ed25519.ExtendedPoint.fromAffine(...);
const hex = Buffer.from((<any>point).toRawBytes()).toString('hex');

recoverPublicKey compatibility

I am creating a signature (for an ethereum transaction) using the secp256K1 curve from this library. Checking the recovered public key from the hash and signature using this library itself always gives me the same public key I expect. All good so far,

But using the ethers library (v6.3.0) to create a signed transaction with the signature created by this library only occasionally gives the same public key. It also gives other public keys in a seemingly non-deterministic fashion (when I set extraEntropy to true). I see that the latest ethers library is still dependent on the old version "@noble/secp256k1": "1.7.1" for the public key recovery.

Are the new and old version of secp256k1 incompatible from this perspective?

ed25519 edwardsToMontgomery does not produce correct output

The function edwardsToMontgomery doesn't seem to be correctly implemented.
There is a formula stated in the TSDoc (1+y)/(1-y) but the implementation uses a different formula (y-1)/(y+1)).

Indeed, when comparing outputs to what would be the equivalent code from @stablelib/ed25519 we get different results.

Here's an example test:

import { ed25519, edwardsToMontgomery } from '@noble/curves/ed25519'
import { hexToBytes, bytesToHex } from '@noble/hashes/utils'
import { deepStrictEqual } from 'assert'
import { should } from 'micro-should'

should('edwardsToMontgomery should produce correct output', () => {
  const edSecret = hexToBytes('77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a');
  const edPublic = ed25519.getPublicKey(edSecret);
  const xPublic = edwardsToMontgomery(edPublic);
  deepStrictEqual(
    bytesToHex(xPublic),
    'ed7749b4d989f6957f3bfde6c56767e988e21c9f8784d91d610011cd553f9b06'
  );
});

Test vectors were computed using @stablelib/ed25519:

import { generateKeyPairFromSeed, convertPublicKeyToX25519 } from '@stablelib/ed25519'

const edSeed = hexToBytes('77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a')
const edKeyPair = generateKeyPairFromSeed(edSeed)
const xPub = convertPublicKeyToX25519(edKeyPair.publicKey)
console.log(bytesToHex(xPub))

PS. I already have a fix and will make a PR soon.

Bundling related issues

@noble/curves does not work correctly in react-native. However, @noble/hashes does. I believe the reason this is the case is due to an issue around the export maps in each project.

In @noble/curves it's the case that the export map includes the lib folder, whereas in @noble/hashes, they are within the root of the library.

I have also ran into this issue with my own libraries, and have been forced to ensure the package.json lives along side the built assets when the library has more advanced export maps.

I would suggest that @noble/curves follows the same build set up as @noble/hashes.

Support groestl

Groestl was a sha-3 candidate: https://github.com/Groestlcoin/groestl-hash-js

Reasons we want groestl added on noble-hashes:

  1. We have our own bip32 fork. Recently you send a PR and we no longer can merge the changes from upstream without getting on noble-hashes first.
  2. We have our own wif fork. Recently you send a PR and we no longer will be able to merge the changes from upstream without getting on noble-hashes first.
  3. We have our own bs58check fork. Recently you send a PR and we no longer can merge the changes from upstream.
  4. We have our own bitcoinjs-lib fork. Recently you send a PR and we no longer can merge the changes from upstream.

Signature export

Signature: SignatureConstructor;

Why are we exporting Signature of type SignatureConstructor and not Signature class?

class Signature implements SignatureType {

This breaks the type inference (when being explicit) using sign method in stark.ts because it returns SignatureType which needs to be imported from weierstrass.ts. I think Signature Class should be exported from weierstrass.ts instead of SignatureConstructor

Package version in dependent modules

Modules dependent on @noble/curves uses ~ as version matcher. It produces many version of @noble/curves in dependencies tree:

β”œβ”€β”€ @noble/[email protected]
β”œβ”€β”¬ @scure/[email protected]
β”‚ └── @noble/[email protected]
└─┬ @scure/[email protected]
  └── @noble/[email protected]

https://github.com/paulmillr/scure-btc-signer/blob/e9fdfbc1fe8ce43efa384e1f433f72c21ce25562/package.json#L17-L20

I think that ^ can be used for own modules to reduce total bundle size.

Issue is also related to @noble/hashes and @scure/base

changelog next ver

bls12-381: changed API

  • CURVE is no longer exposed, it was an internal property. Use G1.CURVE, G2.CURVE
  • Fields have been moved into fields: {Fp, Fp2, Fp6, Fp12, Fr} property
  • See README for new usage

ed25519, ed448: changed API

  • context is now an option in sign and verify
  • zip215 is a new verify option that allows to conform to RFC8032 when false. For true it will instead match ZIP215.
  • Added edwardsToMontgomery function

weierstrass: improved DER decoding. Validate curve creation
Updated Wycheproof vectors to v0.9
hash-to-curve: restrict expand to xmd and xof

Additional EdDSA Ed25519 Verification Checks

Hi Paul very much enjoying @noble/curves for lots of my elliptic curve needs. Most recently I've been reviewing/implementing EdDSA signatures based on Ed25519.

The 2020 paper Taming the many EdDSAs, explains that with some extra validity checks (beyond those in RFC8032) that EdDSA signatures based on Ed25519 can be proven:

  • EUF-CMA (existential unforgeability under chosen message attacks)
  • SUF-CMA (strong unforgeability under chosen message attacks) "but also cannot find a new signature on an old message"
  • Binding signature (BS) "A binding signature makes it impossible for the signer to claim later that it has signed a different message"
  • Strongly Binding signature (SBS) "require(s) a signature to not only be binding to the message but also be binding to the public key."

They authors test a number of Ed25519 libraries as of 2020 and find them missing many of the required checks. They give their short set of test vectors at https://github.com/novifinancial/ed25519-speccheck.

When I run their test vectors against @noble/curves Ed25519 signature verification I get the results shown below. Note that all signatures should be invalid (false), the first column are the results of the recommended checks, the second is what noble/curves currently catches as invalid, the others are looking for specific known bad key and R values.

Case 0 validation value: false, noble check: true, bad key: true, bad R: true
Case 1 validation value: false, noble check: true, bad key: true, bad R: false
Case 2 validation value: false, noble check: true, bad key: false, bad R: true
Case 3 validation value: true, noble check: true, bad key: false, bad R: false
Case 4 validation value: true, noble check: true, bad key: false, bad R: false
Case 5 validation value: true, noble check: true, bad key: false, bad R: false
Case 6 validation value: false, noble check: false, bad key: false, bad R: false
Case 7 validation value: false, noble check: false, bad key: false, bad R: false
Case 8 validation value: false, noble check: true, bad key: false, bad R: true
Case 9 validation value: false, noble check: false, bad key: false, bad R: true
Case 10 validation value: false, noble check: true, bad key: true, bad R: false
Case 11 validation value: false, noble check: true, bad key: true, bad R: false

In an updated presentation to NIST the authors mentioned that a recent version of Bouncy Castle (Java crypto library) has been updated (2023).

Can we get these additional verification/validation checks for Ed25519 EdDSA signatures incorporated into noble/curves? Would you like assistance with this?

Best Regards Greg

References

Signature

Hello, is signature compressed ? Comparing with Java BLS implementation, receiving different signatures with the same input.

Encrypt/Decrypt Functionality?

First, I want to mention this repository is great! It helped improve the performance of sign/verify using p256 compared to another library we were using.

I wanted to ask if you have any plans to implement encrypt and decrypt methods?

We are using ecies-25519 for encrypt/decrypt with ed25519 and the performance is good. However for p256 and secp256k1 the performance is quite a bit slower in the libraries we are using and are looking at alternatives.

Error: BigInt value to a number

TypeError: Cannot convert a BigInt value to a number at Math.pow (<anonymous>) at SWUFpSqrtRatio (weierstrass.js:1360:29) at mapToCurveSimpleSWU (weierstrass.js:1476:19) at eval (secp256k1.js:393:55) at ./node_modules/_@[email protected]@@noble/curves/secp256k1.js (chunk-vendors.js:15327:1) at __webpack_require__ (app.js:854:30) at fn (app.js:151:20) at eval (index.js:3096:24) at ./node_modules/_@[email protected]@@mysten/sui.js/dist/index.js (chunk-vendors.js:15243:1) at __webpack_require__ (app.js:854:30) a

I have tried many ways, but no solution

node -v: v16.16.0
"webpack": "^4.44.2",

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.