Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Add Gnark Bn254 precompile methods for fuzzing #30585

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
74 changes: 74 additions & 0 deletions crypto/bn256/gnark/g1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package bn256

import (
"math/big"

"github.com/consensys/gnark-crypto/ecc/bn254"
)

// G1 is the affine representation of a G1 group element.
//
// Since this code is used for precompiles, using Jacobian
// points are not beneficial because there are no intermediate
// points to allow us to save on inversions.
//
// Note: We also use this struct so that we can conform to the existing API
// that the precompiles want.
type G1 struct {
inner bn254.G1Affine
}

// Add adds `a` and `b` together storing the result in `g`
func (g *G1) Add(a, b *G1) {
// TODO(Decision to be made): There are three ways to
// TODO do this addition. Each with different performance
// TODO: characteristics.
//
// Option 1: This just calls a method in gnark
// g.inner.Add(&a.inner, &b.inner)

// Option 2: This calls multiple methods in gnark
// but is faster.
//
// var res bn254.G1Jac
// res.FromAffine(&a.inner)
// res.AddMixed(&b.inner)
// g.inner.FromJacobian(&res)

// Option 3: This calls a method that I created that
// we can upstream to gnark.
// This should be the fastest, I can write the same for G2
g.addAffine(a, b)
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
}

// ScalarMult computes the scalar multiplication between `a` and
// `scalar` storing the result in `g`
func (g *G1) ScalarMult(a *G1, scalar *big.Int) {
g.inner.ScalarMultiplication(&a.inner, scalar)
}

// Double adds `a` to itself, storing the result in `g`
func (g *G1) Double(a *G1) {
g.inner.Double(&a.inner)
}

// Unmarshal deserializes `buf` into `g`
//
// Note: whether the serialization is of a compressed
// or an uncompressed point, is encoding in the bytes.
//
// For our purpose, the point will always be serialized as uncompressed
// ie 64 bytes.
//
// This method checks whether the point is on the curve and
// in the subgroup.
func (g *G1) Unmarshal(buf []byte) (int, error) {
return g.inner.SetBytes(buf)
}

// Marshal serializes the point into a byte slice.
//
// Note: The point is serialized as uncompressed.
func (p *G1) Marshal() []byte {
return p.inner.Marshal()
}
73 changes: 73 additions & 0 deletions crypto/bn256/gnark/g1_aff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package bn256

import (
"github.com/consensys/gnark-crypto/ecc/bn254/fp"
)

// This is just the addition formula
// but given we know that we do not need Jacobian
// coordinates, we use the naive implementation.
//
// Ideally, we push this into gnark
Comment on lines +7 to +11
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned, I can remove this file and or upstream to gnark first -- added it as I noticed that most cryptography libraries are not well suited to the usecase where we do an operation on a point and then serialize straight after, most libraries asssume that you are going to make further computations on the result

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some rough benchmarks and it looked like this smooths out the variance between the worse cases

func (g *G1) addAffine(a_, b_ *G1) {

// Get the gnark specific points
var a = a_.inner
var b = b_.inner

// If a is 0, then return b
if a.IsInfinity() {
g.inner.Set(&b)
return
}

// If b is 0, then return a
if b.IsInfinity() {
g.inner.Set(&a)
return
}

// If a == -b, then return 0
g.inner.Neg(&b)
if a.Equal(&g.inner) {
g.inner.X.SetZero()
g.inner.Y.SetZero()
return
}

// Compute lambda based on whether we
// are doing a point addition or a point doubling
//
// Check if points are equal
var pointsAreEqual = a.Equal(&b)

var denominator fp.Element
var lambda fp.Element

// If a == b, then we need to compute lambda for double
// else we need to compute lambda for addition
if pointsAreEqual {
// Compute numerator
lambda.Square(&a.X)
fp.MulBy3(&lambda)

denominator.Add(&a.Y, &a.Y)
} else {
// Compute numerator
lambda.Sub(&b.Y, &a.Y)

denominator.Sub(&b.X, &a.X)
}
denominator.Inverse(&denominator)
lambda.Mul(&lambda, &denominator)

// Compute x_3 as lambda^2 - a_x - b_x
g.inner.X.Square(&lambda)
g.inner.X.Sub(&g.inner.X, &a.X)
g.inner.X.Sub(&g.inner.X, &b.X)

// Compute y as lambda * (a_x - x_3) - a_y
g.inner.Y.Sub(&a.X, &g.inner.X)
g.inner.Y.Mul(&g.inner.Y, &lambda)
g.inner.Y.Sub(&g.inner.Y, &a.Y)
}
48 changes: 48 additions & 0 deletions crypto/bn256/gnark/g2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package bn256

import (
"github.com/consensys/gnark-crypto/ecc/bn254"
)

// G2 is the affine representation of a G2 group element.
//
// Since this code is used for precompiles, using Jacobian
// points are not beneficial because there are no intermediate
// points.
//
// Note: We also use this struct so that we can conform to the existing API
// that the precompiles want.
type G2 struct {
inner bn254.G2Affine
}

// Add adds `a` and `b` together storing the result in `g`
func (g *G2) Add(a, b *G2) {
g.inner.Add(&a.inner, &b.inner)
}

// Double adds `a` to itself, storing the result in `g`
func (g *G2) Double(a *G2) {
g.inner.Double(&a.inner)
}

// Unmarshal deserializes `buf` into `g`
//
// Note: whether the serialization is of a compressed
// or an uncompressed point, is encoding in the bytes.
//
// For our purpose, the point will always be serialized as uncompressed
// ie 128 bytes.
//
// This method checks whether the point is on the curve and
// in the subgroup.
func (g *G2) Unmarshal(buf []byte) (int, error) {
return g.inner.SetBytes(buf)
}

// Marshal serializes the point into a byte slice.
//
// Note: The point is serialized as uncompressed.
func (g *G2) Marshal() []byte {
return g.inner.Marshal()
}
73 changes: 73 additions & 0 deletions crypto/bn256/gnark/pairing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package bn256

import (
"github.com/consensys/gnark-crypto/ecc/bn254"
)

// Computes the following relation: ∏ᵢ e(Pᵢ, Qᵢ) =? 1
//
// To explain why gnark returns a (bool, error):
//
// - If the function `e` does not return a result then internally
// an error is returned.
// - If `e` returns a result, then error will be nil,
// but if this value is not `1` then the boolean value will be false
//
// We therefore check for an error, and return false if its non-nil and
// then return the value of the boolean if not.
func PairingCheck(a_ []*G1, b_ []*G2) bool {
a := getInnerG1s(a_)
b := getInnerG2s(b_)

// Assume that len(a) == len(b)
//
// The pairing function will return
// false, if this is not the case.
size := len(a)

// Check if input is empty -- gnark will
// return false on an empty input, however
// the ossified behavior is to return true
// on an empty input, so we add this if statement.
if size == 0 {
return true
}

ok, err := bn254.PairingCheck(a, b)
if err != nil {
return false
}
return ok
}

// getInnerG1s gets the inner gnark G1 elements.
//
// These methods are used for two reasons:
//
// - We use a new type `G1`, so we need to convert from
// []*G1 to []*bn254.G1Affine
// - The gnark API accepts slices of values and not slices of
// pointers to values, so we need to return []bn254.G1Affine
// instead of []*bn254.G1Affine.
func getInnerG1s(pointerSlice []*G1) []bn254.G1Affine {
gnarkValues := make([]bn254.G1Affine, 0, len(pointerSlice))
for _, ptr := range pointerSlice {
if ptr != nil {
gnarkValues = append(gnarkValues, ptr.inner)
}
}
return gnarkValues
}

// getInnerG2s gets the inner gnark G2 elements.
//
// The rationale for this method is the same as getInnerG1s.
func getInnerG2s(pointerSlice []*G2) []bn254.G2Affine {
gnarkValues := make([]bn254.G2Affine, 0, len(pointerSlice))
for _, ptr := range pointerSlice {
if ptr != nil {
gnarkValues = append(gnarkValues, ptr.inner)
}
}
return gnarkValues
}