Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions ecc/secp256r1/cardano.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package secp256r1

// Cardano solver for the depressed cubic x³ − 3x + c = 0 over secp256r1 Fp.
// Requires q ≡ 3 mod 4 (for Fp2 sqrt) and q ≡ 4 mod 9 (for Fp cbrt).

import (
"math/big"

"github.com/consensys/gnark-crypto/ecc/secp256r1/fp"
fp2 "github.com/consensys/gnark-crypto/ecc/secp256r1/internal/fptower"
)

var omegaFq fp.Element // primitive cube root of unity in Fp

func init() {
q := fp.Modulus()
exp := new(big.Int).Sub(q, big.NewInt(1))
exp.Div(exp, big.NewInt(3))
var one fp.Element
one.SetOne()
for i := int64(2); ; i++ {
var g, w fp.Element
g.SetInt64(i)
w.Exp(g, exp)
if !w.Equal(&one) {
omegaFq = w
break
}
}
}

// CardanoRoots returns all roots in Fp of x³ − 3x + c = 0
// using Cardano's formula.
func CardanoRoots(c fp.Element) []fp.Element {
var a fp.Element
a.SetInt64(-3)

var zero fp.Element

// Δ = −4a³ − 27c²
var a3, neg4a3, k27c2, delta fp.Element
a3.Square(&a).Mul(&a3, &a)
neg4a3.Mul(&a3, new(fp.Element).SetInt64(4)).Neg(&neg4a3)
k27c2.Square(&c).Mul(&k27c2, new(fp.Element).SetInt64(27))
delta.Sub(&neg4a3, &k27c2)

// disc_D = c²/4 + a³/27
var inv4, inv27, discD fp.Element
inv4.SetInt64(4)
inv4.Inverse(&inv4)
inv27.SetInt64(27)
inv27.Inverse(&inv27)
discD.Square(&c).Mul(&discD, &inv4)
var a3over27 fp.Element
a3over27.Mul(&a3, &inv27)
discD.Add(&discD, &a3over27)
Comment thread
yelhousni marked this conversation as resolved.

// −c/2
var inv2, negCHalf fp.Element
inv2.SetInt64(2)
inv2.Inverse(&inv2)
negCHalf.Mul(&c, &inv2).Neg(&negCHalf)

om := omegaFq
var om2 fp.Element
om2.Square(&om)
var one fp.Element
one.SetOne()
zetas := [3]fp.Element{one, om, om2}

// Case 1: Δ = 0 (repeated root)
if delta.Equal(&zero) {
var invA, r0, r1 fp.Element
invA.Inverse(&a)
r0.Mul(&c, &invA).Mul(&r0, new(fp.Element).SetInt64(3))
var twoA fp.Element
twoA.Double(&a)
r1.Inverse(&twoA).Mul(&r1, &c).Mul(&r1, new(fp.Element).SetInt64(3)).Neg(&r1)
return []fp.Element{r0, r1}
}

// Case 2: Δ non-square → one real root via Fp2
if delta.Legendre() == -1 {
var discDE2, D fp2.E2
discDE2.A0 = discD
D.Sqrt(&discDE2)

w := fp2.E2{A0: negCHalf, A1: D.A1}
if w.IsZero() {
w.A1.Neg(&D.A1)
}

var u fp2.E2
if u.Cbrt(&w) == nil {
return []fp.Element{}
}

for _, zeta := range zetas {
var cand fp2.E2
cand.MulByElement(&u, &zeta)
var inv fp2.E2
inv.Inverse(&cand)
var rRe, rIm fp.Element
rRe.Add(&cand.A0, &inv.A0)
rIm.Add(&cand.A1, &inv.A1)
if rIm.Equal(&zero) {
return []fp.Element{rRe}
}
Comment thread
yelhousni marked this conversation as resolved.
}
return []fp.Element{}
}

// Case 3: Δ square → 0 or 3 roots in Fp
var DFq, wFq fp.Element
DFq.Sqrt(&discD)
wFq.Add(&negCHalf, &DFq)
if wFq.Equal(&zero) {
wFq.Sub(&negCHalf, &DFq)
}

var uFq fp.Element
if uFq.Cbrt(&wFq) == nil {
return []fp.Element{}
}

var invU, r0, r1, r2, t1, t2 fp.Element
invU.Inverse(&uFq)
r0.Add(&uFq, &invU)
t1.Mul(&om, &uFq)
t2.Mul(&om2, &invU)
r1.Add(&t1, &t2)
t1.Mul(&om2, &uFq)
t2.Mul(&om, &invU)
r2.Add(&t1, &t2)
return []fp.Element{r0, r1, r2}
}
102 changes: 102 additions & 0 deletions ecc/secp256r1/cardano_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package secp256r1

import (
"math/big"
"testing"

"github.com/consensys/gnark-crypto/ecc/secp256r1/fp"
"github.com/consensys/gnark-crypto/ecc/secp256r1/fr"
"github.com/leanovate/gopter"
"github.com/leanovate/gopter/prop"
)

func TestCardanoRoots(t *testing.T) {
t.Parallel()
parameters := gopter.DefaultTestParameters()
if testing.Short() {
parameters.MinSuccessfulTests = nbFuzzShort
} else {
parameters.MinSuccessfulTests = nbFuzz
}

properties := gopter.NewProperties(parameters)

properties.Property("[SECP256R1] CardanoRoots should return valid roots of x³ − 3x + c = 0", prop.ForAll(
func(c fp.Element) bool {
roots := CardanoRoots(c)
var three fp.Element
three.SetInt64(3)
for _, r := range roots {
// verify r³ − 3r + c = 0
var r3, threex, lhs fp.Element
r3.Square(&r).Mul(&r3, &r)
threex.Mul(&three, &r)
lhs.Sub(&r3, &threex).Add(&lhs, &c)
if !lhs.IsZero() {
return false
}
}
return true
},
GenFp(),
))

properties.Property("[SECP256R1] CardanoRoots from curve points should find at least one root matching x", prop.ForAll(
func(s fr.Element) bool {
// generate a real curve point by scalar multiplication
var sBig big.Int
s.BigInt(&sBig)
var p G1Jac
p.ScalarMultiplication(&g1Gen, &sBig)
var pAff G1Affine
pAff.FromJacobian(&p)

// c = b − y² so x³ − 3x + c = 0 must have pAff.X as a root
var b fp.Element
b.SetString("41058363725152142129326129780047268409114441015993725554835256314039467401291")
var y2, c fp.Element
y2.Square(&pAff.Y)
c.Sub(&b, &y2)

roots := CardanoRoots(c)
if len(roots) == 0 {
return false // must find at least one root
}
Comment thread
cursor[bot] marked this conversation as resolved.
// verify at least one root matches the known x
found := false
for _, r := range roots {
if r.Equal(&pAff.X) {
found = true
break
}
}
return found
},
GenFr(),
))

properties.Property("[SECP256R1] CardanoRoots with c=0 should return roots of x³ − 3x = 0", prop.ForAll(
func(_ fp.Element) bool {
var c fp.Element // zero
roots := CardanoRoots(c)
if len(roots) == 0 {
return false
}
var three fp.Element
three.SetInt64(3)
for _, r := range roots {
var r3, threex, lhs fp.Element
r3.Square(&r).Mul(&r3, &r)
threex.Mul(&three, &r)
lhs.Sub(&r3, &threex)
if !lhs.IsZero() {
return false
}
}
return true
},
GenFp(),
))

properties.TestingRun(t, gopter.ConsoleReporter(false))
}
Loading
Loading