An optimized elliptic curve library for Noir.
noir_bigcurve provides elliptic curve arithmetic over arbitrary prime fields using the Weierstrass form:
y^2 = x^3 + a*x + b (mod p)
It builds on noir-bignum for arbitrary-precision field arithmetic.
BigCurve instances are parameterized by a struct implementing BigCurveParams.
This library implements elliptic curve operations over arbitrary-precision prime fields.
- Uses noir-bignum for field arithmetic
- Minimizes modular reductions for efficiency
- Supports both constrained and unconstrained execution paths
- Provides runtime lookup tables for faster scalar multiplication
Predefined curves are included, and tools are provided to define custom curves.
use ::bigcurve::BLS12_377;See:
src/lib.nrfor exported curvessrc/curves/for parameter definitions
If your curve is not included:
- Define or import a parameter set (see noir-bignum)
- Define the corresponding big number type
- Implement
BigCurveParams
Examples are available in src/curves/.
- Operators like
+,-,==are constrained and expensive - Best practice:
- Apply constraints once using
evaluate_linear_expression
- Apply constraints once using
This significantly reduces gate count.
BigCurve does not automatically constrain that point coordinates are valid field elements or that points lie on the curve. If your circuit receives curve points as inputs or returns them as outputs, you must manually validate them. Without these checks, a malicious prover can supply invalid values that break the soundness of your circuit.
1. validate_in_field — Call this on the x and y coordinates (which are BigNum values) to constrain that they are valid elements of the base field (i.e. less than the field modulus). Without this, a prover can use unreduced values that are congruent modulo p but not canonical, leading to inconsistent behavior.
2. validate_on_curve — Call this on the point to constrain that (x, y) satisfies the curve equation y^2 = x^3 + ax + b. Without this, a prover can supply arbitrary (x, y) pairs that are not on the curve.
let point: BLS12_377 = /* ... from circuit input ... */;
point.x.validate_in_field();
point.y.validate_in_field();
point.validate_on_curve();Points produced by hash_to_curve or one already include these checks and do not need manual validation.
use ::bigcurve::BigCurve;
use ::bigcurve::{BLS12_377, BLS12_377Scalar};
fn main() {
let one = BLS12_377::one();
let two = one.mul(BLS12_377Scalar::from(2));
let three = one.mul(BLS12_377Scalar::from(3));
assert((one + two) == three);
}Represents a point on an elliptic curve over a field MOD.
Represents scalars used in point multiplication.
zerofrom(Field)into(Field)from_bignuminto_bignum
+,-,==neg,add,submul(ScalarField)
x,ya,bis_infinity
onepoint_at_infinityfrom_coordinateshash_to_curve
validate_on_curve
Efficiently computes expressions of the form:
sum_i(a_i * P_i) + sum_j(Q_j)
This allows batching operations and applying constraints once.
fn evaluate_linear_expression<
let NScalarSlices: u32,
let NMuls: u32,
let NAdds: u32
>(
mul_points: [Self; NMuls],
mul_scalars: [ScalarField<NScalarSlices>; NMuls],
add_points: [Self; NAdds],
) -> Self;For example, for the earlier example we wanted to calculate S = a * G + b * Q + P + R. The parameters then become:
NMuls= 2. The products area * Gandb * QNAdds= 2. The linear terms arePandR
use ::bigcurve::BigCurve;
use ::bigcurve::{BLS12_377, BLS12_377Scalar};
use ::bignum::BigNum;
use ::bignum::BLS12_377_Fr;
fn main(s1: [u8; 4], s2: [u8; 3], s3: [u8; 4], s4: [u8; 4]) -> pub BLS12_377 {
let G = BLS12_377::hash_to_curve(s1);
let a = BLS12_377Scalar::from_bignum(BLS12_377_Fr::derive_from_seed(s1));
let Q = BLS12_377::hash_to_curve(s2);
let b = BLS12_377Scalar::from_bignum(BLS12_377_Fr::derive_from_seed(s2));
let P = BLS12_377::hash_to_curve(s3);
let R = BLS12_377::hash_to_curve(s4);
BLS12_377::evaluate_linear_expression([G, Q], [a, b], [P, R])
}See src/tests/bigcurve_test.nr for more examples.
There are predefined parameter sets located in src/curves of this library.
This library includes the following field presets:
| Curve | Base Field Instance | Scalar Field Instance |
|---|---|---|
| BLS12-377 | BLS12_377_Fq |
BLS12_377_Fr |
| BLS12-381 | BLS12_381_Fq |
BLS12_381_Fr |
| BN254 | BN254_Fq |
(Scalar field is the native field in Noir) |
| MNT4-753 | MNT4_753_Fq |
MNT4_753_Fr |
| MNT6-753 | MNT6_753_Fq |
MNT6_753_Fr |
| Pallas | Pallas_Fr |
Pallas_Fq |
| Secp256k1 | Secp256k1_Fq |
Secp256k1_Fr |
| Secp256r1 | Secp256r1_Fq |
Secp256r1_Fr |
| Secp384r1 | Secp384r1_Fq |
Secp384r1_Fr |
| Vesta | Vesta_Fq |
Vesta_Fr |
| Feature requests and/or pull requests welcome for missing fields you need. |
Q: What's up with the Jacobian points and the transcript objects? A: To minimize witness generation time (currently the bottleneck due to Brillig VM) we evaluate ECC operations over Jacobian coordinates in an unconstrained function, in order to efficiently batch-compute the modular inverses required to constrain ECC operations over Affine coordinates (which is more constraint-efficient)
- Add method to check point is in prime-order subgroup for curves with a cofactor
- Parametrize and test with a degree-2 extension field instead of
BigNum - Add support for curve endomorphisms where applicable (if base field and scalar field both contain cube roots of unity, we can reduce the number of point doublings required for an MSM in half)