This folder contains the different elliptic curve models currently supported by lambdaworks. For an overview of the curve models, their addition formulas and coordinate systems, see Hyperelliptic. The models currently supported are:
Each of the curve models can have one or more coordinate systems, such as homogeneous projective, Jacobian, XZ coordinates, etc. These are used for reasons of performance. It is possible to define an operation,
This part makes use of lambdaworks finite fields. If you are unfamiliar with it or underlying concepts, refer to the section on finite fields.
The following curves are currently supported:
- BLS12-377, a pairing-friendly elliptic curve (pairing implementation pending).
- BLS12-381, a pairing-friendly elliptic curve.
- BN-254, a pairing-friendly elliptic curve. Used on Ethereum.
-
Grumpkin, an elliptic curve that forms a two-cycle with BN-254. This means that the base field for Grumpkin (where the coordinates
$x,y$ live) is the scalar field of BN-254 (the field with order equal to the order of the group of the elliptic curve), and the scalar field for Grumpkin is the base field of BN-254. - Pallas, useful for recursive SNARKs when used with Vesta.
- Vesta, useful for recursive SNARKs when used with Pallas.
- Starknet's curve
- secp256k1: Bitcoin's curve. The implementation is not constant time, so it cannot be used to sign messages!
-
secq256k1: It has the same curve equation as secp256k1, a different generator and their order r and the modulus p are swapped. It uses
secp256k1_scalarfield
as a base field, which has modulus r. - secp256r1: Used for digital signatures, also known as: P-256 and prime256v1.
The following curves are currently supported:
- Ed448Goldilocks
- Bandersnatch
- TinyJubJub, only for learning purposes.
The following curves are currently supported:
- TinyJubJub, only for learning purposes.
In order to define your elliptic curve in lambdaworks, you need to implement some traits:
IsEllipticCurve
IsShortWeierstrass
IsEdwards
IsMontgomery
To create an elliptic curve in Short Weiestrass form, we have to implement the traits IsEllipticCurve
and IsShortWeierstrass
(If you want a twisted Edwards, use IsEdwards
. For Montgomery form, use IsMontgomery
). Below we show how the Pallas curve is defined:
#[derive(Clone, Debug)]
pub struct PallasCurve;
impl IsEllipticCurve for PallasCurve {
type BaseField = Pallas255PrimeField;
type PointRepresentation = ShortWeierstrassProjectivePoint<Self>;
fn generator() -> Self::PointRepresentation {
Self::PointRepresentation::new([
-FieldElement::<Self::BaseField>::one(),
FieldElement::<Self::BaseField>::from(2),
FieldElement::one(),
])
}
}
impl IsShortWeierstrass for PallasCurve {
fn a() -> FieldElement<Self::BaseField> {
FieldElement::from(0)
}
fn b() -> FieldElement<Self::BaseField> {
FieldElement::from(5)
}
}
Here, defining_equation
method, which allows us to check whether a given BaseField
is where the coordinates generator()
gives a point
To implement the IsShortWeierstrass
, you need to first implement IsEllipticCurve
. It has 3 methods, two of which need to be implemented for each curve fn a()
and fn b()
(the curve parameters) and fn defining_equation()
, which computes
All curves implement the trait FromAffine
, which lets us define points by providing the pair of values BaseField
of the curve. For example
let x = FE::from_hex_unchecked(
"bd1e740e6b1615ae4c508148ca0c53dbd43f7b2e206195ab638d7f45d51d6b5",
);
let y = FE::from_hex_unchecked(
"13aacd107ca10b7f8aab570da1183b91d7d86dd723eaa2306b0ef9c5355b91d8",
);
PallasCurve::create_point_from_affine(x, y).unwrap()
The function has to check whether the point is valid, and, if not, returns an error.
Each form and coordinate model has to implement the IsGroup
trait, which will give us all the necessary operations for the group. We need to provide expressions for:
-
fn neutral_element()
, the neutral element for the group operation. In the case of elliptic curves, this is the point at infinity. -
fn operate_with
, which defines the group operation; it takes two elements in the group and outputs a third one. -
fn neg
, which gives the inverse of the element. It also provides the methodfn operate_with_self
, which is used to indicate that repeteadly add one element against itself$n$ times. Here,$n$ should implement theIsUnsignedInteger
trait. In the case of elliptic curves, this provides the scalar multiplication,$n P$ , based on the double and add algorithm (square and multiply).
Operating is done in the following way:
// We get a point
let g = PallasCurve::generator();
let g2 = g.operate_with_self(2_u16);
let g3 = g.operate_with_other(&g2);
operate_with_self
takes as argument anything that implements the IsUnsignedInteger
trait. operate_with_other
takes as argument another point in the elliptic curve. When we operate this way, the to_affine
. For example,
let g = BLS12381Curve::generator();
let g2 = g.operate_with_self(2_u64);
// get x and y from affine coordinates
let g2_affine = g2.to_affine();
let x = g2_affine.x();
let y = g2_affine.y();
One common operation for different proof systems is the Mutiscalar Multiplication (MSM), which is given by a set of points operate_with_self
with each point and scalar and then add the results using operate_with
, but this is not efficient. lambdaworks provides an optimized MSM using Pippenger's algorithm. A naïve version is given here. Below we show how to use MSM in the context of a polynomial commitment scheme: the scalars are the coefficients of the polynomials and the points are provided by an SRS.
fn commit(&self, p: &Polynomial<FieldElement<F>>) -> Self::Commitment {
let coefficients: Vec<_> = p
.coefficients
.iter()
.map(|coefficient| coefficient.representative())
.collect();
msm(
&coefficients,
&self.srs.powers_main_group[..coefficients.len()],
)
.expect("`points` is sliced by `cs`'s length")
}
Pairings are an important calculation for BLS signatures and the KZG polynomial commitment scheme. These are functions mapping elements from groups of order
- Bilinearity
- Non-degeneracy
Not all elliptic curves have efficiently computable pairings. If the curve is pairing-friendly, we can implement the trait
IsPairing
. Examples of pairing-friendly curves are BLS12-381, BLS12-377, BN254. Curves such as Pallas, Vesta, secp256k1 are not pairing-friendly. For an explanation of pairings, see our blogpost.
The pairing function takes pairs of points compute_batch
. For example,
let p = BN254Curve::generator();
let q = BN254TwistCurve::generator();
let pairing_result = BN254AtePairing::compute_batch(&[(&p, &q)]).unwrap();
Elliptic curve points have to satisfy their defining equation; a pair
This allows us to reduce the amount of information we send to define a unique point on the curve. It suffices to specify
In many curves, the base field contains some spare bits (as is the case of BLS12-381 or BN254, but not secp256k1), which allows us to codify the extra bit into the free bits of the element. Depending on the number of spare bits, we could compress points in different ways.
Pairings and elliptic curve point compression and decompression are needed for BLS signatures and in EIP-4844.
- HyperElliptic - formulae for EC addition and doubling
- An introduction to mathematical cryptography
- Constantine
- BN-254 for the rest of us
- BLS12-381 for the rest of us
- High-speed implementation of the Optimal Ate Pairing over Barreto-Naehrig curves
- Computing the optimal pairing over the BN254 curve
- Pairings for beginners
- Pairing-friendly elliptic curves of prime order
- Need for speed: elliptic curves chapter
- What every developer needs to know about elliptic curves
- How we implemented the BN254 Ate pairing in lambdaworks
- Exploring elliptic curve pairings by Vitalik
- A survey of elliptic curves for proof systems
- Taxonomy of pairing-friendly elliptic curves