Skip to content
← Writing

Elliptic Curves over Goldilocks

Building prime-order elliptic curves over a quintic extension of Goldilocks with 128-bit security and efficient hash-to-curve: EcGFp5 and EcMasFp5.

· 7 min read · Elliptic Curves, Goldilocks

The two elliptic curves implementation can be found here.

We aim to construct an elliptic curve satisfying the following criteria:

  • Prime order.
  • Efficient group operations.
  • Deterministc and efficient hash-to-curve encoding.
  • Security level of 128 bits.

Extension Field Selection

To achieve the target security level, we adopt an approach similar to EcGFp5. Specifically, we define the extension field Fp5=Fp[z]/(z5−3)\mathbb{F}_{p^5} = \mathbb{F}_p[z] / (z^5 - 3) over p=264−232+1p = 2^{64} - 2^{32} + 1

We select the polynomial f(z)=z5−3f(z) = z^5 - 3 because:

  • It has the minimal Hamming weight among irreducible polynomials.
  • It uses nonzero coefficients as close to ±1\pm 1 as possible.
  • A polynomial of the form zk−cz^k - c minimizes the computational cost of the Frobenius operator.

The following Sage script verifies that the previous claims about z5−3z^5 - 3 are true:

# Define Fp
p = 2**64 - 2**32 + 1
F = GF(p)

# Define Fp[z]
R.<z> = PolynomialRing(F)

# Polynomials of the form z⁵±c for c ∈ [0,2] are not irreducible
for i in range(3):
    assert(R(z^5+i).is_irreducible() == R(z^5-i).is_irreducible() == False)
    
# The polynomial z^5-3 is irreducible
assert(R(z^5-3).is_irreducible())

Quadratic Residuosity

To check whether an element a∈Fp5a \in \mathbb{F}_{p^{5}} is a quadratic residue, we use Euler’s criterion, raising aa to the power (p5−1)/2(p^{5}-1)/2.

This exponentiation can be optimized by noting that:

p5−1=(p4+p3+p2+p+1)⋅(p−1)p^5 - 1 = (p^4 + p^3 + p^2 + p + 1) \cdot (p-1)

which follows from the cyclotomic polynomial decomposition.

To compute aa to the power over p4+p3+p2+p+1p^4 + p^3 + p^2 + p + 1, we need to efficiently compute the first and second Frobenius operator. Moreover, notice that when this exponentation is done, we get an element b∈Fp5b \in \mathbb{F}_{p^5} such that bp−1=1b^{p-1} = 1 and this can only happen when b∈Fpb \in \mathbb{F}_p; so the second exponentiation is over Fp\mathbb{F}_p instead.

Exponentiation by (p−1)/2(p-1)/2 is done by observing that:

p−12=263−231,\frac{p-1}{2} = 2^{63} - 2^{31},

and using repeated squaring.

Frobenius Operator

As we have shown in the previous section, we must be able compute the first and second Frobenius operator over an element in Fp5\mathbb{F}_{p^{5}} efficiently. A naive implementation would take O(log⁡p)=O(64)O(\log p) = O(64) Fp5\mathbb{F}_{p^{5}}-operations, but we will do it much better.

Write aa as a0+a1z+a2z2+a3z3+a4z4a_0 + a_1z + a_2z^2 + a_3z^3 + a_4z^4, with ai∈Fpa_i \in \mathbb{F}_{p}. Then, we can compute the frobenius operators as follows:

ap=a0+a1γ1,1z+a2γ1,2z2+a3γ1,3z3+a4γ1,4z4,ap2=a0+a1γ2,1z+a2γ2,2z2+a3γ2,3z3+a4γ2,4z4,\begin{aligned} a^{p} = a_0 + a_1\gamma_{1,1}z + a_2\gamma_{1,2}z^2 + a_3\gamma_{1,3}z^3 + a_4\gamma_{1,4}z^4, \\ a^{p^2} = a_0 + a_1\gamma_{2,1}z + a_2\gamma_{2,2}z^2 + a_3\gamma_{2,3}z^3 + a_4\gamma_{2,4}z^4, \end{aligned}

with γ1,i=3ip−15\gamma_{1,i} = 3^{i\frac{p-1}{5}} and γ2,i=3ip2−15=γ1,i2\gamma_{2,i} = 3^{i\frac{p^2-1}{5}} = \gamma_{1,i}^2. Hence, assuming the precomputation of γ1,i,γ2,i\gamma_{1,i},\gamma_{2,i} for i∈[4]i \in [4], we can compute apa^p and ap2a^{p^2} by four multiplications each.

Expand to see the proof

Proof: On the one hand, using Fermat’s little theorem we have that ajpi=aja_j^{p^i} = a_j.

On the other hand, notice zp=(z5)p−15z=3p−15zz^p = (z^5)^{\frac{p-1}{5}}z = 3^{\frac{p-1}{5}}z and therefore (zi)p=γ1,i⋅zi(z^{i})^p = \gamma_{1,i} \cdot z^i, where γ1,i=3ip−15\gamma_{1,i} = 3^{i\frac{p-1}{5}}.

Using the previous observations, we have:

ap=(a0+a1z+a2z2+a3z3+a4z4)p=a0+a1zp+a2z2p+a3z3p+a4z4p=a0+a1γ1,1z+a2γ1,2z2+a3γ1,3z3+a4γ1,4z4,\begin{aligned} a^p &= (a_0 + a_1z + a_2z^2 + a_3z^3 + a_4z^4)^p = a_0 + a_1z^p + a_2z^{2p} + a_3z^{3p} + a_4z^{4p} \\ &= a_0 + a_1\gamma_{1,1}z + a_2\gamma_{1,2}z^2 + a_3\gamma_{1,3}z^3 + a_4\gamma_{1,4}z^4, \end{aligned}

which can be computed using a few multiplications.

Similarly, notice zp2=3p2−15z=(γ1,1)p+1z=γ1,12⋅zz^{p^2} = 3^{\frac{p^2-1}{5}}z = (\gamma_{1,1})^{p+1}z = \gamma_{1,1}^2 \cdot z and therefore (zi)p2=γ2,izi(z^{i})^{p^2} = \gamma_{2,i}z^i, where γ2,i=γ1,i2\gamma_{2,i} = \gamma_{1,i}^2.

TODO: Develop on γ1,i=3ip−15\gamma_{1,i} = 3^{i\frac{p-1}{5}} generating a subgroup of order 55.

Playing a little bit, we can refine exponentiation by p4+p3+p2+p+1p^4 + p^3 + p^2 + p + 1:

t0:=ap⋅ap2,t1:=t0p2,t2:=t0⋅t1,t3:=a⋅t2=a0⋅b0+3⋅(a1⋅b4+a2⋅b3+a3⋅b2+a4⋅b1),\begin{aligned} t_0 &:= a^{p} \cdot a^{p^2}, \\ t_1 &:= t_0^{p^2}, \\ t_2 &:= t_0 \cdot t_1, \\ t_3 &:= a \cdot t_2 = a_0 \cdot b_0 + 3 \cdot (a_1 \cdot b_4 + a_2 \cdot b_3 + a_3 \cdot b_2 + a_4 \cdot b_1), \end{aligned}

where b:=t2b := t_2 is viewed as a Fp5\mathbb{F}_{p^5}-element.

Square Roots

To compute the square root of an element b∈Fp5b \in \mathbb{F}_{p^{5}} we will use in our favour the fact that to check its quadratic residuosity we have to raise it to p4+p3+p2+p+1p^4 + p^3 + p^2 + p + 1.

After a bit of algebra, we get the following identity:

12+p4+p3+p2+p+12=(p+12)p3+(p+12)p+1,\frac{1}{2} + \frac{p^4 + p^3 + p^2 + p + 1}{2} = \left(\frac{p+1}{2}\right)p^3 + \left(\frac{p+1}{2}\right)p + 1,

so we can compute the squaring by:

  • Compute the square of b−(p4+p3+p2+p+1)b^{-(p^4 + p^3 + p^2 + p + 1)} by using any squaring algorithms over the base field (recall that when an element is raised to p4+p3+p2+p+1p^4 + p^3 + p^2 + p + 1 it gets mapped to the base field).
  • Compute the exponentiation of bb by the RHS of the identity.
  • Multiply both results.

EcGFp5

Definition

Found by Thomas Pornin, the EcGFp5 curve is defined by the equation:

E/Fp5:y2=x3+ax+bE/\mathbb{F}_{p^5} : y^2 = x^3 + ax + b

with constants:

a=6148914689804861439+263z,b=15713893096167979237+6148914689804861265z\begin{aligned} a &= 6148914689804861439 + 263z, \\ b &= 15713893096167979237 + 6148914689804861265z \end{aligned}

and order 2n2n for the 319319-bit prime:

n = 0x7ffffffd800000077ffffff1000000167fffffe6cfb80639e8885c39d724a09ce80fd996948bffe1

Since nn is odd, the points belonging to the subgroup of order nn are precisely the nn-torsion E[n]E[n] of the curve.

Hashing to the Curve

We follow the RFC 9380: Hashing to Elliptic Curves to get a deterministic mapping.

hash_to_curve(msg)

Input: msg, an arbitrary-length byte string.
Output: P, a point in G.

Steps:
1. u = hash_to_field(msg, 2)
2. Q0 = map_to_curve_simple_swu(u[0])
3. Q1 = map_to_curve_simple_swu(u[1])
4. R = Q0 + Q1
5. P = R + R # clear_cofactor(R)
6. return P

––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––

map_to_curve_simple_swu(u)

Input: u, an element in Fp5.
Output: P, a point in E.

Steps:
1. tv1 = inv0(Z^2 * u^4 + Z * u^2)
2.  x1 = (-B / A) * (1 + tv1)
3.  If tv1 == 0, set x1 = B / (Z * A)
4. gx1 = x1^3 + A * x1 + B
5.  x2 = Z * u^2 * x1
6. gx2 = x2^3 + A * x2 + B
7.  If is_square(gx1), set x = x1 and y = sqrt(gx1)
8.  Else set x = x2 and y = sqrt(gx2)
9.  If sgn0(u) != sgn0(y), set y = -y
10. return (x, y)

EcMasFp5

Definition

We choose the short Weierstrass equation:

y2=x3+Ax+By^2 = x^3 + Ax + B

because assuming that A∈Fp∗A \in \mathbb{F}_p^* and that the cost of addition, subtraction, multiplication and division over the field is the same (something true in SNARK settings), point addition costs 1010 field operations and point doubling costs 1212 field operations.

Setting A∈Fp∗A \in \mathbb{F}_p^* also restricts BB to be from the extension field, rather from the prime field; otherwise the curve could not be of primer order.

Since BB has no impact in group operations, we perform a linear search:

# Define Fp⁵
K.<z> = GF(p^5, modulus=z^5-3)

# Perform a linear search
def find_ec_over_gfp5(initial_a = 0, initial_c = 1, attempts = 50, pol_degree = K.degree(), verbose = True):
    A = initial_a
    c = initial_c
    if verbose:
        print("· Attempting with A =", A)
    while True:
        for i in range(1, pol_degree):
            B = c * z**i
            E = EllipticCurve(K, (A, B))
            if verbose:
                print("    -",E)
            if is_prime(E.order(algorithm="pari")):
                print(f"Elliptic curve found with A = {E.a4()} and B = {E.a6()}")
                return E
        else:
            c += 1
            # If no prime order was found, reset `c` and update `A`
            if c == attempts:
                c = initial_c
                A += 1
                if verbose:
                    print("· Attempting with A =", A)
            continue

        break
        
E = find_ec_over_gfp5()

The first elliptic curve found was:

E/Fp5:y2=x3+3x+8z4,E/\mathbb{F}_{p^5}: y^2 = x^3 + 3x + 8z^4,

with a 320320-bit prime order:

n = 0xfffffffb0000000effffffe20000002cffffffcc2c13f5f892042da0dfcde3fc8f4b2caf22360ee3

The name for this curve was chosen to be EcMasFp5 (Elliptic Curve for Maximum Aggregation Speed over Fp5\mathbb{F}_{p^5}).

Security of the Curve

Thanks to Robin Salen for help and discussion.

We follow the criteria in Safe Curves.

Summary

Curve prime order `n`: 0xfffffffb0000000effffffe20000002cffffffcc2c13f5f892042da0dfcde3fc8f4b2caf22360ee3 (320 bits)
Curve cofactor: 1
Curve embedding degree: n-1 (320 bits)
Curve CM discriminant `D`: -0x350910d76c19ff80472df1f236bda8f251edc59a20563b4f1bcb88ad47f9892d334755425f4369e43 (323 bits)
Curve security (Pollard-Rho): 159.83 bits

Twist prime order `nt`: 0x128ad28934008b50864257bbff0983af9688b4f04ff3f024f86f (205 bits)
Twist cofactor: 0xdce68ed7aef21329a0ac97d33d76f (116 bits)
Twist embedding degree: (nt-1)/15 (201 bits)
Twist CM discriminant: D (323 bits)
Twist security (Pollard-Rho): 101.93 bits

More details are given below.

Field

Since the extension field is the same as the one in EcGFp5, we inherit by default its security. In particular, this field selection approximately achieves a 142142-bit security level, well beyond the target one.

Embedding Degree

We use the following code to find the embedding degree ee:

def embedding_degree(q,n):  
    F = GF(n)  
    return F(q).multiplicative_order()

Finding that e=n−1e = n-1, as large as it can possibly be, avoiding MOV-type attacks.

CM Discriminant

The CM Discriminant D=t2−4qD = t^2-4q was found to be a 323323-bit integer:

D = -0x350910d76c19ff80472df1f236bda8f251edc59a20563b4f1bcb88ad47f9892d334755425f4369e43

Twist

The number q+1+tq+1+t factorizes as:

0x128ad28934008b50864257bbff0983af9688b4f04ff3f024f86f*0xdce68ed7aef21329a0ac97d33d76f

Hashing to the Curve

It is the same as the one for EcFp5 with the exception that this one does not need to clean the cofactor.

Resources

Break my circuit

You are the prover. The verifier doesn't run the program; it only checks the constraints. Each circuit here is missing one, which is the most common bug in real ZK systems. Find a witness that fools it.

circuit.circom

Public (verifier sees)

Witness (you choose)

Constraints · everything mod 97