Skip to content
Open
Show file tree
Hide file tree
Changes from 13 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
5 changes: 5 additions & 0 deletions std/algebra/native/fields_kb8/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright 2020-2026 Consensys Software Inc.
// Licensed under the Apache License, Version 2.0. See the LICENSE file for details.

// Package fields_kb8 implements KoalaBear-native Fp^8 arithmetic for kb8-based gadgets.
package fields_kb8
165 changes: 165 additions & 0 deletions std/algebra/native/fields_kb8/e2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package fields_kb8

import (
"github.com/consensys/gnark-crypto/field/koalabear"
"github.com/consensys/gnark-crypto/field/koalabear/extensions"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/internal/frontendtype"
)

var uSquare = koalabear.NewElement(3)

type E2 struct {
A0, A1 frontend.Variable
}

func (e *E2) SetZero() *E2 {
e.A0 = 0
e.A1 = 0
return e
}

func (e *E2) SetOne() *E2 {
e.A0 = 1
e.A1 = 0
return e
}

func (e *E2) IsZero(api frontend.API) frontend.Variable {
return api.And(api.IsZero(e.A0), api.IsZero(e.A1))
}

func (e *E2) assign(e1 []frontend.Variable) {
e.A0 = e1[0]
e.A1 = e1[1]
}

func (e *E2) Neg(api frontend.API, e1 E2) *E2 {
e.A0 = api.Neg(e1.A0)
e.A1 = api.Neg(e1.A1)
return e
}

func (e *E2) Add(api frontend.API, e1, e2 E2) *E2 {
e.A0 = api.Add(e1.A0, e2.A0)
e.A1 = api.Add(e1.A1, e2.A1)
return e
}

func (e *E2) Double(api frontend.API, e1 E2) *E2 {
e.A0 = api.Mul(e1.A0, 2)
e.A1 = api.Mul(e1.A1, 2)
return e
}

func (e *E2) Sub(api frontend.API, e1, e2 E2) *E2 {
e.A0 = api.Sub(e1.A0, e2.A0)
e.A1 = api.Sub(e1.A1, e2.A1)
return e
}

func (e *E2) Mul(api frontend.API, e1, e2 E2) *E2 {
if ft, ok := api.Compiler().(frontendtype.FrontendTyper); ok {
switch ft.FrontendType() {
case frontendtype.R1CS:
return e.mulKaratsuba(api, e1, e2)
case frontendtype.SCS:
return e.mulSchoolbook(api, e1, e2)
}
}
return e.mulKaratsuba(api, e1, e2)
}

// mulKaratsuba uses Karatsuba: 3M+5A = 8 gates. Cheaper in R1CS where M >> A.
func (e *E2) mulKaratsuba(api frontend.API, e1, e2 E2) *E2 {
l1 := api.Add(e1.A0, e1.A1)
l2 := api.Add(e2.A0, e2.A1)
u := api.Mul(l1, l2)
ac := api.Mul(e1.A0, e2.A0)
bd := api.Mul(e1.A1, e2.A1)
e.A1 = api.Sub(u, api.Add(ac, bd))
e.A0 = api.Add(ac, api.Mul(bd, uSquare))
return e
}

// mulSchoolbook uses schoolbook: 4M+2A = 6 gates. Cheaper in Plonk where M = A = 1 gate.
func (e *E2) mulSchoolbook(api frontend.API, e1, e2 E2) *E2 {
a0b0 := api.Mul(e1.A0, e2.A0)
a1b1 := api.Mul(e1.A1, e2.A1)
a0b1 := api.Mul(e1.A0, e2.A1)
a1b0 := api.Mul(e1.A1, e2.A0)
e.A0 = api.Add(a0b0, api.Mul(uSquare, a1b1))
e.A1 = api.Add(a0b1, a1b0)
return e
}

func (e *E2) Square(api frontend.API, x E2) *E2 {
if ft, ok := api.Compiler().(frontendtype.FrontendTyper); ok {
switch ft.FrontendType() {
case frontendtype.R1CS:
return e.squareKaratsuba(api, x)
case frontendtype.SCS:
return e.squareSchoolbook(api, x)
}
}
return e.squareKaratsuba(api, x)
}

// squareKaratsuba uses Algorithm 22 from https://eprint.iacr.org/2010/354.pdf
// adapted to u^2 = 3: 2M + 3A + 3 const-muls. Cheaper in R1CS where M >> A.
func (e *E2) squareKaratsuba(api frontend.API, x E2) *E2 {
c0 := api.Add(x.A0, x.A1)
c2 := api.Mul(x.A1, uSquare)
c2 = api.Add(c2, x.A0)

c0 = api.Mul(c0, c2)
c2 = api.Mul(x.A0, x.A1)
c2 = api.Mul(c2, 2)
e.A1 = c2
c2 = api.Mul(c2, 2)
e.A0 = api.Sub(c0, c2)
return e
}

// squareSchoolbook uses 3M + 1A + 2 const-muls. Cheaper in Plonk where M = A = 1 gate.
func (e *E2) squareSchoolbook(api frontend.API, x E2) *E2 {
a2 := api.Mul(x.A0, x.A0)
b2 := api.Mul(x.A1, x.A1)
ab := api.Mul(x.A0, x.A1)
e.A0 = api.Add(a2, api.Mul(b2, uSquare))
e.A1 = api.Mul(ab, 2)
return e
}

func (e *E2) MulByFp(api frontend.API, e1 E2, c interface{}) *E2 {
e.A0 = api.Mul(e1.A0, c)
e.A1 = api.Mul(e1.A1, c)
return e
}

func (e *E2) MulByNonResidue(api frontend.API, e1 E2) *E2 {
x := e1.A0
e.A0 = api.Mul(e1.A1, uSquare)
e.A1 = x
return e
}

func (e *E2) AssertIsEqual(api frontend.API, other E2) {
api.AssertIsEqual(e.A0, other.A0)
api.AssertIsEqual(e.A1, other.A1)
}

func (e *E2) IsEqual(api frontend.API, other E2) frontend.Variable {
return api.And(api.IsZero(api.Sub(e.A0, other.A0)), api.IsZero(api.Sub(e.A1, other.A1)))
}

func (e *E2) Select(api frontend.API, b frontend.Variable, r1, r2 E2) *E2 {
e.A0 = api.Select(b, r1.A0, r2.A0)
e.A1 = api.Select(b, r1.A1, r2.A1)
return e
}

func (e *E2) Assign(a *extensions.E2) {
e.A0 = a.A0
e.A1 = a.A1
}
91 changes: 91 additions & 0 deletions std/algebra/native/fields_kb8/e2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package fields_kb8

import (
"testing"

"github.com/consensys/gnark-crypto/field/koalabear/extensions"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/test"
)

type e2Add struct{ A, B, C E2 }

func (c *e2Add) Define(api frontend.API) error {
var e E2
e.Add(api, c.A, c.B)
e.AssertIsEqual(api, c.C)
return nil
}
func TestAddE2(t *testing.T) {
assert := test.NewAssert(t)
var a, b, c extensions.E2
a.SetRandom()
b.SetRandom()
c.Add(&a, &b)
var w e2Add
w.A.Assign(&a)
w.B.Assign(&b)
w.C.Assign(&c)
assert.CheckCircuit(&e2Add{}, test.WithValidAssignment(&w), test.WithoutCurveChecks(), test.WithSmallfieldCheck())
}

type e2Sub struct{ A, B, C E2 }

func (c *e2Sub) Define(api frontend.API) error {
var e E2
e.Sub(api, c.A, c.B)
e.AssertIsEqual(api, c.C)
return nil
}
func TestSubE2(t *testing.T) {
assert := test.NewAssert(t)
var a, b, c extensions.E2
a.SetRandom()
b.SetRandom()
c.Sub(&a, &b)
var w e2Sub
w.A.Assign(&a)
w.B.Assign(&b)
w.C.Assign(&c)
assert.CheckCircuit(&e2Sub{}, test.WithValidAssignment(&w), test.WithoutCurveChecks(), test.WithSmallfieldCheck())
}

type e2Mul struct{ A, B, C E2 }

func (c *e2Mul) Define(api frontend.API) error {
var e E2
e.Mul(api, c.A, c.B)
e.AssertIsEqual(api, c.C)
return nil
}
func TestMulE2(t *testing.T) {
assert := test.NewAssert(t)
var a, b, c extensions.E2
a.SetRandom()
b.SetRandom()
c.Mul(&a, &b)
var w e2Mul
w.A.Assign(&a)
w.B.Assign(&b)
w.C.Assign(&c)
assert.CheckCircuit(&e2Mul{}, test.WithValidAssignment(&w), test.WithoutCurveChecks(), test.WithSmallfieldCheck())
}

type e2Square struct{ A, C E2 }

func (c *e2Square) Define(api frontend.API) error {
var e E2
e.Square(api, c.A)
e.AssertIsEqual(api, c.C)
return nil
}
func TestSquareE2(t *testing.T) {
assert := test.NewAssert(t)
var a, c extensions.E2
a.SetRandom()
c.Square(&a)
var w e2Square
w.A.Assign(&a)
w.C.Assign(&c)
assert.CheckCircuit(&e2Square{}, test.WithValidAssignment(&w), test.WithoutCurveChecks(), test.WithSmallfieldCheck())
}
Loading
Loading