diff --git a/crypto/bn256/gnark/g1.go b/crypto/bn256/gnark/g1.go new file mode 100644 index 0000000000..2db24e5b16 --- /dev/null +++ b/crypto/bn256/gnark/g1.go @@ -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) +} + +// 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() +} diff --git a/crypto/bn256/gnark/g1_aff.go b/crypto/bn256/gnark/g1_aff.go new file mode 100644 index 0000000000..d92eb80009 --- /dev/null +++ b/crypto/bn256/gnark/g1_aff.go @@ -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 +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) +} diff --git a/crypto/bn256/gnark/g2.go b/crypto/bn256/gnark/g2.go new file mode 100644 index 0000000000..86b1a8d747 --- /dev/null +++ b/crypto/bn256/gnark/g2.go @@ -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() +} diff --git a/crypto/bn256/gnark/pairing.go b/crypto/bn256/gnark/pairing.go new file mode 100644 index 0000000000..a9e049111d --- /dev/null +++ b/crypto/bn256/gnark/pairing.go @@ -0,0 +1,64 @@ +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_) + + // Check if input is empty + if len(a) == 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 +}