From 840a8676a06ca0f6e7a56f9226c7df8cb66bb694 Mon Sep 17 00:00:00 2001 From: Weiji Guo Date: Mon, 8 Jan 2024 21:14:28 +0800 Subject: [PATCH 001/105] added circuit implementation of ExpandMsgXmd --- std/hash/tofield/doc.go | 3 + std/hash/tofield/expand.go | 102 +++++++++++++++++++++ std/hash/tofield/expand_test.go | 158 ++++++++++++++++++++++++++++++++ 3 files changed, 263 insertions(+) create mode 100644 std/hash/tofield/doc.go create mode 100644 std/hash/tofield/expand.go create mode 100644 std/hash/tofield/expand_test.go diff --git a/std/hash/tofield/doc.go b/std/hash/tofield/doc.go new file mode 100644 index 0000000000..b1612201a7 --- /dev/null +++ b/std/hash/tofield/doc.go @@ -0,0 +1,3 @@ +// Package tofield provides ZKP circuits for expanding messages to field elements, according to +// RFC9380 (section 5.3.1). +package tofield diff --git a/std/hash/tofield/expand.go b/std/hash/tofield/expand.go new file mode 100644 index 0000000000..f6d3e07e1a --- /dev/null +++ b/std/hash/tofield/expand.go @@ -0,0 +1,102 @@ +package tofield + +import ( + "errors" + + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/hash/sha2" + "github.com/consensys/gnark/std/math/uints" +) + +const ( + block_size = 64 +) + +// ExpandMsgXmd expands msg to a slice of lenInBytes bytes according to RFC9380 (section 5.3.1) +// Spec: https://datatracker.ietf.org/doc/html/rfc9380#name-expand_message_xmd (hashutils.go) +// Implementation was adapted from gnark-crypto/field/hash.ExpandMsgXmd. +func ExpandMsgXmd(api frontend.API, msg []uints.U8, dst []byte, lenInBytes int) ([]uints.U8, error) { + h, e := sha2.New(api) + if e != nil { + return nil, e + } + + ell := (lenInBytes + h.Size() - 1) / h.Size() // ceil(len_in_bytes / b_in_bytes) + if ell > 255 { + return nil, errors.New("invalid lenInBytes") + } + if len(dst) > 255 { + return nil, errors.New("invalid domain size (>255 bytes)") + } + sizeDomain := uint8(len(dst)) + + dst_prime := make([]uints.U8, len(dst)+1) + copy(dst_prime, uints.NewU8Array(dst)) + dst_prime[len(dst)] = uints.NewU8(uint8(sizeDomain)) + + Z_pad_raw := make([]uint8, block_size) + Z_pad := uints.NewU8Array(Z_pad_raw) + h.Write(Z_pad) + h.Write(msg) + h.Write([]uints.U8{uints.NewU8(uint8(lenInBytes >> 8)), uints.NewU8(uint8(lenInBytes)), uints.NewU8(0)}) + h.Write(dst_prime) + b0 := h.Sum() + + h, e = sha2.New(api) + if e != nil { + return nil, e + } + h.Write(b0) + h.Write([]uints.U8{uints.NewU8(1)}) + h.Write(dst_prime) + b1 := h.Sum() + + res := make([]uints.U8, lenInBytes) + copy(res[:h.Size()], b1) + + for i := 2; i <= ell; i++ { + h, e = sha2.New(api) + if e != nil { + return nil, e + } + + // b_i = H(strxor(b₀, b_(i - 1)) ∥ I2OSP(i, 1) ∥ DST_prime) + strxor := make([]uints.U8, h.Size()) + for j := 0; j < h.Size(); j++ { + strxor[j], e = xor(api, b0[j], b1[j]) + if e != nil { + return res, e + } + } + h.Write(strxor) + h.Write([]uints.U8{uints.NewU8(uint8(i))}) + h.Write(dst_prime) + b1 = h.Sum() + copy(res[h.Size()*(i-1):min(h.Size()*i, len(res))], b1) + } + + return res, nil +} + +func xor(api frontend.API, a, b uints.U8) (uints.U8, error) { + aBits := api.ToBinary(a.Val, 8) + bBits := api.ToBinary(b.Val, 8) + cBits := make([]frontend.Variable, 8) + + for i := 0; i < 8; i++ { + cBits[i] = api.Xor(aBits[i], bBits[i]) + } + + uapi, err := uints.New[uints.U32](api) + if err != nil { + return uints.NewU8(255), err + } + return uapi.ByteValueOf(api.FromBinary(cBits...)), nil +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/std/hash/tofield/expand_test.go b/std/hash/tofield/expand_test.go new file mode 100644 index 0000000000..1df5271c59 --- /dev/null +++ b/std/hash/tofield/expand_test.go @@ -0,0 +1,158 @@ +package tofield + +import ( + "encoding/hex" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/uints" + "github.com/consensys/gnark/test" +) + +type expandMsgXmdCircuit struct { + Msg []uints.U8 + Dst []uint8 + Len int + Expected []uints.U8 +} + +type expandMsgXmdTestCase struct { + msg string + lenInBytes int + uniformBytesHex string +} + +func (c *expandMsgXmdCircuit) Define(api frontend.API) error { + uapi, err := uints.New[uints.U32](api) + if err != nil { + return err + } + expanded, err := ExpandMsgXmd(api, c.Msg, c.Dst, c.Len) + if err != nil { + return err + } + + for i := 0; i < c.Len; i++ { + uapi.ByteAssertEq(expanded[i], c.Expected[i]) + } + + return nil +} + +// adapted from gnark-crypto/field/hash/hashutils_test.go +func TestExpandMsgXmd(t *testing.T) { + //name := "expand_message_xmd" + dst := "QUUX-V01-CS02-with-expander-SHA256-128" + //hash := "SHA256" + //k := 128 + + testCases := []expandMsgXmdTestCase{ + { + "", + 0x20, + "68a985b87eb6b46952128911f2a4412bbc302a9d759667f87f7a21d803f07235", + }, + + { + "abc", + 0x20, + "d8ccab23b5985ccea865c6c97b6e5b8350e794e603b4b97902f53a8a0d605615", + }, + + { + "abcdef0123456789", + 0x20, + "eff31487c770a893cfb36f912fbfcbff40d5661771ca4b2cb4eafe524333f5c1", + }, + + { + "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + 0x20, + "b23a1d2b4d97b2ef7785562a7e8bac7eed54ed6e97e29aa51bfe3f12ddad1ff9", + }, + + { + "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + 0x20, + "4623227bcc01293b8c130bf771da8c298dede7383243dc0993d2d94823958c4c", + }, + { + "", + 0x80, + "af84c27ccfd45d41914fdff5df25293e221afc53d8ad2ac06d5e3e29485dadbee0d121587713a3e0dd4d5e69e93eb7cd4f5df4cd103e188cf60cb02edc3edf18eda8576c412b18ffb658e3dd6ec849469b979d444cf7b26911a08e63cf31f9dcc541708d3491184472c2c29bb749d4286b004ceb5ee6b9a7fa5b646c993f0ced", + }, + { + "", + 0x20, + "68a985b87eb6b46952128911f2a4412bbc302a9d759667f87f7a21d803f07235", + }, + { + "abc", + 0x80, + "abba86a6129e366fc877aab32fc4ffc70120d8996c88aee2fe4b32d6c7b6437a647e6c3163d40b76a73cf6a5674ef1d890f95b664ee0afa5359a5c4e07985635bbecbac65d747d3d2da7ec2b8221b17b0ca9dc8a1ac1c07ea6a1e60583e2cb00058e77b7b72a298425cd1b941ad4ec65e8afc50303a22c0f99b0509b4c895f40", + }, + { + "abcdef0123456789", + 0x80, + "ef904a29bffc4cf9ee82832451c946ac3c8f8058ae97d8d629831a74c6572bd9ebd0df635cd1f208e2038e760c4994984ce73f0d55ea9f22af83ba4734569d4bc95e18350f740c07eef653cbb9f87910d833751825f0ebefa1abe5420bb52be14cf489b37fe1a72f7de2d10be453b2c9d9eb20c7e3f6edc5a60629178d9478df", + }, + { + "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + 0x80, + "80be107d0884f0d881bb460322f0443d38bd222db8bd0b0a5312a6fedb49c1bbd88fd75d8b9a09486c60123dfa1d73c1cc3169761b17476d3c6b7cbbd727acd0e2c942f4dd96ae3da5de368d26b32286e32de7e5a8cb2949f866a0b80c58116b29fa7fabb3ea7d520ee603e0c25bcaf0b9a5e92ec6a1fe4e0391d1cdbce8c68a", + }, + { + "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + 0x80, + "546aff5444b5b79aa6148bd81728704c32decb73a3ba76e9e75885cad9def1d06d6792f8a7d12794e90efed817d96920d728896a4510864370c207f99bd4a608ea121700ef01ed879745ee3e4ceef777eda6d9e5e38b90c86ea6fb0b36504ba4a45d22e86f6db5dd43d98a294bebb9125d5b794e9d2a81181066eb954966a487", + }, + //test cases not in the standard + { + "", + 0x30, + "3808e9bb0ade2df3aa6f1b459eb5058a78142f439213ddac0c97dcab92ae5a8408d86b32bbcc87de686182cbdf65901f", + }, + { + "abc", + 0x30, + "2b877f5f0dfd881405426c6b87b39205ef53a548b0e4d567fc007cb37c6fa1f3b19f42871efefca518ac950c27ac4e28", + }, + { + "abcdef0123456789", + 0x30, + "226da1780b06e59723714f80da9a63648aebcfc1f08e0db87b5b4d16b108da118214c1450b0e86f9cefeb44903fd3aba", + }, + { + "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + 0x30, + "12b23ae2e888f442fd6d0d85d90a0d7ed5337d38113e89cdc7c22db91bd0abaec1023e9a8f0ef583a111104e2f8a0637", + }, + { + "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + 0x30, + "1aaee90016547a85ab4dc55e4f78a364c2e239c0e58b05753453c63e6e818334005e90d9ce8f047bddab9fbb315f8722", + }, + } + + for _, testCase := range testCases { + uniformBytes := make([]uint8, len(testCase.uniformBytesHex)>>1) + hex.Decode(uniformBytes, []uint8(testCase.uniformBytesHex)) + witness := expandMsgXmdCircuit{ + Msg: uints.NewU8Array([]uint8(testCase.msg)), + Dst: []uint8(dst), + Len: testCase.lenInBytes, + Expected: uints.NewU8Array(uniformBytes), + } + circuit := expandMsgXmdCircuit{ + Msg: uints.NewU8Array(make([]uint8, len(testCase.msg))), + Dst: []uint8(dst), + Len: testCase.lenInBytes, + Expected: uints.NewU8Array(uniformBytes), + } + err := test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) + if err != nil { + t.Fatal(err) + } + } +} From 7fb1b95ec23297a54045cb46fd53cfbd3e37d484 Mon Sep 17 00:00:00 2001 From: Weiji Guo Date: Sat, 27 Jan 2024 15:56:58 +0800 Subject: [PATCH 002/105] implemented HashToG2 for bls12-381 --- std/algebra/emulated/sw_bls12381/g2.go | 58 +++ std/algebra/emulated/sw_bls12381/g2_test.go | 71 ++++ .../emulated/sw_bls12381/hash_to_g2.go | 371 ++++++++++++++++++ .../emulated/sw_bls12381/hash_to_g2_test.go | 167 ++++++++ std/algebra/emulated/sw_bls12381/hints.go | 44 +++ std/hints.go | 2 + 6 files changed, 713 insertions(+) create mode 100644 std/algebra/emulated/sw_bls12381/hash_to_g2.go create mode 100644 std/algebra/emulated/sw_bls12381/hash_to_g2_test.go create mode 100644 std/algebra/emulated/sw_bls12381/hints.go diff --git a/std/algebra/emulated/sw_bls12381/g2.go b/std/algebra/emulated/sw_bls12381/g2.go index c607c50693..91daab066d 100644 --- a/std/algebra/emulated/sw_bls12381/g2.go +++ b/std/algebra/emulated/sw_bls12381/g2.go @@ -13,6 +13,7 @@ type G2 struct { *fields_bls12381.Ext2 u1, w *emulated.Element[BaseField] v *fields_bls12381.E2 + api frontend.API } type g2AffP struct { @@ -50,6 +51,7 @@ func NewG2(api frontend.API) *G2 { w: &w, u1: &u1, v: &v, + api: api, } } @@ -96,6 +98,18 @@ func (g2 *G2) psi(q *G2Affine) *G2Affine { } } +func (g2 *G2) psi2(q *G2Affine) *G2Affine { + x := g2.Ext2.MulByElement(&q.P.X, g2.w) + y := g2.Ext2.Neg(&q.P.Y) + + return &G2Affine{ + P: g2AffP{ + X: *x, + Y: *y, + }, + } +} + func (g2 *G2) scalarMulBySeed(q *G2Affine) *G2Affine { z := g2.triple(q) @@ -136,6 +150,50 @@ func (g2 G2) add(p, q *G2Affine) *G2Affine { } } +// The add() function is not complete. If p == q, then λ is 0, while it should be (3p.x²)/2*p.y according to double() +// Moreover, the AssertIsEqual() check will fail within the DivUnchecked() call as the div hint will return 0. +// +// Provide addUnified to handle this situation at the cost of additional constraints. Useful when not certain if p != q. +// Note, ClearCofactor (covered in TestClearCofactorTestSolve of hash_to_g2_test.go) might fail without this function. +func (g2 G2) addUnified(p, q *G2Affine) *G2Affine { + // if p != q, compute λ = (q.y-p.y)/(q.x-p.x) + qypy := g2.Ext2.Sub(&q.P.Y, &p.P.Y) + qxpx := g2.Ext2.Sub(&q.P.X, &p.P.X) + + // else if p == q, compute λ = (3p.x²)/2*p.y + xx3a := g2.Square(&p.P.X) + xx3a = g2.MulByConstElement(xx3a, big.NewInt(3)) + y2 := g2.Double(&p.P.Y) + + z := g2.Ext2.IsZero(qxpx) + deltaX := g2.Select(z, y2, qxpx) + deltaY := g2.Select(z, xx3a, qypy) + λ := g2.Ext2.DivUnchecked(deltaY, deltaX) + + // if p == -q, result should zero + zeroConst := g2.Zero() + z1 := g2.Ext2.Add(&q.P.Y, &p.P.Y) + z2 := g2.Ext2.IsZero(z1) + z3 := g2.api.And(z, z2) + + // xr = λ²-p.x-q.x + λλ := g2.Ext2.Square(λ) + qxpx = g2.Ext2.Add(&p.P.X, &q.P.X) + xr := g2.Ext2.Sub(λλ, qxpx) + + // yr = λ(p.x-r.x) - p.y + pxrx := g2.Ext2.Sub(&p.P.X, xr) + λpxrx := g2.Ext2.Mul(λ, pxrx) + yr := g2.Ext2.Sub(λpxrx, &p.P.Y) + + return &G2Affine{ + P: g2AffP{ + X: *g2.Select(z3, zeroConst, xr), + Y: *g2.Select(z3, zeroConst, yr), + }, + } +} + func (g2 G2) neg(p *G2Affine) *G2Affine { xr := &p.P.X yr := g2.Ext2.Neg(&p.P.Y) diff --git a/std/algebra/emulated/sw_bls12381/g2_test.go b/std/algebra/emulated/sw_bls12381/g2_test.go index 9d4a90d0e4..467925bea4 100644 --- a/std/algebra/emulated/sw_bls12381/g2_test.go +++ b/std/algebra/emulated/sw_bls12381/g2_test.go @@ -37,6 +37,77 @@ func TestAddG2TestSolve(t *testing.T) { assert.NoError(err) } +func TestAddG2FailureCaseTestSolve(t *testing.T) { + assert := test.NewAssert(t) + _, in1 := randomG1G2Affines() + var res bls12381.G2Affine + res.Double(&in1) + witness := addG2Circuit{ + In1: NewG2Affine(in1), + In2: NewG2Affine(in1), + Res: NewG2Affine(res), + } + err := test.IsSolved(&addG2Circuit{}, &witness, ecc.BN254.ScalarField()) + // the add() function cannot handle identical inputs + assert.Error(err) +} + +type addG2UnifiedCircuit struct { + In1, In2 G2Affine + Res G2Affine +} + +func (c *addG2UnifiedCircuit) Define(api frontend.API) error { + g2 := NewG2(api) + res := g2.addUnified(&c.In1, &c.In2) + g2.AssertIsEqual(res, &c.Res) + return nil +} + +func TestAddG2UnifiedTestSolveAdd(t *testing.T) { + assert := test.NewAssert(t) + _, in1 := randomG1G2Affines() + _, in2 := randomG1G2Affines() + var res bls12381.G2Affine + res.Add(&in1, &in2) + witness := addG2UnifiedCircuit{ + In1: NewG2Affine(in1), + In2: NewG2Affine(in2), + Res: NewG2Affine(res), + } + err := test.IsSolved(&addG2UnifiedCircuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} + +func TestAddG2UnifiedTestSolveDbl(t *testing.T) { + assert := test.NewAssert(t) + _, in1 := randomG1G2Affines() + var res bls12381.G2Affine + res.Double(&in1) + witness := addG2UnifiedCircuit{ + In1: NewG2Affine(in1), + In2: NewG2Affine(in1), + Res: NewG2Affine(res), + } + err := test.IsSolved(&addG2UnifiedCircuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} + +func TestAddG2UnifiedTestSolveNeg(t *testing.T) { + assert := test.NewAssert(t) + _, in1 := randomG1G2Affines() + var in2, res bls12381.G2Affine + in2.Neg(&in1) + res.Add(&in1, &in2) + witness := addG2UnifiedCircuit{ + In1: NewG2Affine(in1), + In2: NewG2Affine(in2), + Res: NewG2Affine(res), + } + err := test.IsSolved(&addG2UnifiedCircuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} + type doubleG2Circuit struct { In1 G2Affine Res G2Affine diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2.go b/std/algebra/emulated/sw_bls12381/hash_to_g2.go new file mode 100644 index 0000000000..10266f3839 --- /dev/null +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2.go @@ -0,0 +1,371 @@ +package sw_bls12381 + +import ( + "math/big" + "slices" + + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/emulated/fields_bls12381" + "github.com/consensys/gnark/std/hash/tofield" + "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/math/emulated/emparams" + "github.com/consensys/gnark/std/math/uints" +) + +const ( + security_level = 128 + len_per_base_element = 64 +) + +func HashToG2(api frontend.API, msg []uints.U8, dst []byte) (*G2Affine, error) { + fp, e := emulated.NewField[emulated.BLS12381Fp](api) + if e != nil { + return &G2Affine{}, e + } + ext2 := fields_bls12381.NewExt2(api) + mapper := newMapper(api, ext2, fp) + g2 := NewG2(api) + + // Steps: + // 1. u = hash_to_field(msg, 2) + // 2. Q0 = map_to_curve(u[0]) + // 3. Q1 = map_to_curve(u[1]) + // 4. R = Q0 + Q1 # Point addition + // 5. P = clear_cofactor(R) + // 6. return P + lenPerBaseElement := len_per_base_element + lenInBytes := lenPerBaseElement * 4 + uniformBytes, e := tofield.ExpandMsgXmd(api, msg, dst, lenInBytes) + if e != nil { + return &G2Affine{}, e + } + + ele1 := bytesToElement(api, fp, uniformBytes[:lenPerBaseElement]) + ele2 := bytesToElement(api, fp, uniformBytes[lenPerBaseElement:lenPerBaseElement*2]) + ele3 := bytesToElement(api, fp, uniformBytes[lenPerBaseElement*2:lenPerBaseElement*3]) + ele4 := bytesToElement(api, fp, uniformBytes[lenPerBaseElement*3:]) + + // we will still do iso_map before point addition, as we do not have point addition in E' (yet) + Q0 := mapper.mapToCurve(fields_bls12381.E2{A0: *ele1, A1: *ele2}) + Q1 := mapper.mapToCurve(fields_bls12381.E2{A0: *ele3, A1: *ele4}) + Q0 = mapper.isogeny(&Q0.P.X, &Q0.P.Y) + Q1 = mapper.isogeny(&Q1.P.X, &Q1.P.Y) + + R := g2.addUnified(Q0, Q1) + + return clearCofactor(g2, fp, R), nil +} + +func bytesToElement(api frontend.API, fp *emulated.Field[emulated.BLS12381Fp], data []uints.U8) *emulated.Element[emulated.BLS12381Fp] { + // data in BE, need to convert to LE + slices.Reverse(data) + + bits := make([]frontend.Variable, len(data)*8) + for i := 0; i < len(data); i++ { + u8 := data[i] + u8Bits := api.ToBinary(u8.Val, 8) + for j := 0; j < 8; j++ { + bits[i*8+j] = u8Bits[j] + } + } + + cutoff := 17 + tailBits, headBits := bits[:cutoff*8], bits[cutoff*8:] + tail := fp.FromBits(tailBits...) + head := fp.FromBits(headBits...) + + byteMultiplier := big.NewInt(256) + headMultiplier := byteMultiplier.Exp(byteMultiplier, big.NewInt(int64(cutoff)), big.NewInt(0)) + head = fp.MulConst(head, headMultiplier) + + return fp.Add(head, tail) +} + +type sswuMapper struct { + A, B, Z fields_bls12381.E2 + ext2 *fields_bls12381.Ext2 + fp *emulated.Field[emulated.BLS12381Fp] + api frontend.API + iso *isogeny +} + +func newMapper(api frontend.API, ext2 *fields_bls12381.Ext2, fp *emulated.Field[emulated.BLS12381Fp]) *sswuMapper { + coeff_a := fields_bls12381.E2{ + A0: emulated.ValueOf[emparams.BLS12381Fp](0), + A1: emulated.ValueOf[emparams.BLS12381Fp](240), + } + coeff_b := fields_bls12381.E2{ + A0: emulated.ValueOf[emparams.BLS12381Fp](1012), + A1: emulated.ValueOf[emparams.BLS12381Fp](1012), + } + + one := emulated.ValueOf[emulated.BLS12381Fp](1) + two := emulated.ValueOf[emulated.BLS12381Fp](2) + zeta := fields_bls12381.E2{ + A0: *fp.Neg(&two), + A1: *fp.Neg(&one), + } + + return &sswuMapper{ + A: coeff_a, + B: coeff_b, + Z: zeta, + ext2: ext2, + fp: fp, + api: api, + iso: newIsogeny(), + } +} + +// Apply the Simplified SWU for the E' curve (RFC 9380 Section 6.6.3) +func (m sswuMapper) mapToCurve(u fields_bls12381.E2) *G2Affine { + // SSWU Steps: + // 1. tv1 = u^2 + tv1 := m.ext2.Square(&u) + // 2. tv1 = Z * tv1 + tv1 = m.ext2.Mul(&m.Z, tv1) + // 3. tv2 = tv1^2 + tv2 := m.ext2.Square(tv1) + // 4. tv2 = tv2 + tv1 + tv2 = m.ext2.Add(tv2, tv1) + // 5. tv3 = tv2 + 1 + tv3 := m.ext2.Add(tv2, m.ext2.One()) + // 6. tv3 = B * tv3 + tv3 = m.ext2.Mul(&m.B, tv3) + // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) + s1 := m.ext2.IsZero(tv2) + tv4 := m.ext2.Select(s1, &m.Z, m.ext2.Neg(tv2)) + // 8. tv4 = A * tv4 + tv4 = m.ext2.Mul(&m.A, tv4) + // 9. tv2 = tv3^2 + tv2 = m.ext2.Square(tv3) + // 10. tv6 = tv4^2 + tv6 := m.ext2.Square(tv4) + // 11. tv5 = A * tv6 + tv5 := m.ext2.Mul(&m.A, tv6) + // 12. tv2 = tv2 + tv5 + tv2 = m.ext2.Add(tv2, tv5) + // 13. tv2 = tv2 * tv3 + tv2 = m.ext2.Mul(tv2, tv3) + // 14. tv6 = tv6 * tv4 + tv6 = m.ext2.Mul(tv6, tv4) + // 15. tv5 = B * tv6 + tv5 = m.ext2.Mul(&m.B, tv6) + // 16. tv2 = tv2 + tv5 + tv2 = m.ext2.Add(tv2, tv5) + // 17. x = tv1 * tv3 + x := m.ext2.Mul(tv1, tv3) + // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) + isGx1Square, y1 := m.sqrtRatio(tv2, tv6) + // 19. y = tv1 * u + y := m.ext2.Mul(tv1, &u) + // 20. y = y * y1 + y = m.ext2.Mul(y, y1) + // 21. x = CMOV(x, tv3, is_gx1_square) + x = m.ext2.Select(isGx1Square, tv3, x) + // 22. y = CMOV(y, y1, is_gx1_square) + y = m.ext2.Select(isGx1Square, y1, y) + // 23. e1 = sgn0(u) == sgn0(y) + sgn0U := m.sgn0(&u) + sgn0Y := m.sgn0(y) + diff := m.api.Sub(sgn0U, sgn0Y) + e1 := m.api.IsZero(diff) + // 24. y = CMOV(-y, y, e1) + yNeg := m.ext2.Neg(y) + y = m.ext2.Select(e1, y, yNeg) + // 25. x = x / tv4 + x = m.ext2.DivUnchecked(x, tv4) + // 26. return (x, y) + return &G2Affine{ + P: g2AffP{X: *x, Y: *y}, + } +} + +func (m sswuMapper) sgn0(x *fields_bls12381.E2) frontend.Variable { + // Steps for sgn0_m_eq_2 + // 1. sign_0 = x_0 mod 2 + x0 := m.fp.ToBits(&x.A0) + sign0 := x0[0] + // 2. zero_0 = x_0 == 0 + zero0 := m.fp.IsZero(&x.A0) + // 3. sign_1 = x_1 mod 2 + x1 := m.fp.ToBits(&x.A1) + sign1 := x1[0] + // 4. s = sign_0 OR (zero_0 AND sign_1) # Avoid short-circuit logic ops + tv := m.api.And(zero0, sign1) + s := m.api.Or(sign0, tv) + // 5. return s + return s +} + +// Let's not mechanically translate the spec algorithm (Section F.2.1) into R1CS circuits. +// We could simply compute the result as a hint, then apply proper constraints, which is: +// for output of (b, y) +// +// b1 := {b = True AND y^2 * v = u} +// b2 := {b = False AND y^2 * v = Z * u} +// AssertTrue: {b1 OR b2} +func (m sswuMapper) sqrtRatio(u, v *fields_bls12381.E2) (frontend.Variable, *fields_bls12381.E2) { + // Steps + // 1. extract the base values of u, v, then compute G2SqrtRatio with gnark-crypto + x, err := m.fp.NewHint(GetHints()[0], 3, &u.A0, &u.A1, &v.A0, &v.A1) + if err != nil { + panic("failed to calculate sqrtRatio with gnark-crypto " + err.Error()) + } + + b := m.fp.IsZero(x[0]) + y := fields_bls12381.E2{A0: *x[1], A1: *x[2]} + + // 2. apply constraints + // b1 := {b = True AND y^2 * v = u} + m.api.AssertIsBoolean(b) + y2 := m.ext2.Square(&y) + y2v := m.ext2.Mul(y2, v) + bY2vu := m.ext2.IsZero(m.ext2.Sub(y2v, u)) + b1 := m.api.And(b, bY2vu) + + // b2 := {b = False AND y^2 * v = Z * u} + uZ := m.ext2.Mul(&m.Z, u) + bY2vZu := m.ext2.IsZero(m.ext2.Sub(y2v, uZ)) + nb := m.api.IsZero(b) + b2 := m.api.And(nb, bY2vZu) + + cmp := m.api.Or(b1, b2) + m.api.AssertIsEqual(cmp, 1) + + return b, &y +} + +type g2Polynomial []fields_bls12381.E2 + +func (p g2Polynomial) eval(m *sswuMapper, at fields_bls12381.E2) (pAt *fields_bls12381.E2) { + pAt = &p[len(p)-1] + + for i := len(p) - 2; i >= 0; i-- { + pAt = m.ext2.Mul(pAt, &at) + pAt = m.ext2.Add(pAt, &p[i]) + } + + return +} + +type isogeny struct { + x_numerator, x_denominator, y_numerator, y_denominator g2Polynomial +} + +func newIsogeny() *isogeny { + return &isogeny{ + x_numerator: g2Polynomial([]fields_bls12381.E2{ + *e2FromStrings( + "889424345604814976315064405719089812568196182208668418962679585805340366775741747653930584250892369786198727235542", + "889424345604814976315064405719089812568196182208668418962679585805340366775741747653930584250892369786198727235542"), + *e2FromStrings( + "0", + "2668273036814444928945193217157269437704588546626005256888038757416021100327225242961791752752677109358596181706522"), + *e2FromStrings( + "2668273036814444928945193217157269437704588546626005256888038757416021100327225242961791752752677109358596181706526", + "1334136518407222464472596608578634718852294273313002628444019378708010550163612621480895876376338554679298090853261"), + *e2FromStrings( + "3557697382419259905260257622876359250272784728834673675850718343221361467102966990615722337003569479144794908942033", + "0"), + }), + x_denominator: g2Polynomial([]fields_bls12381.E2{ + *e2FromStrings( + "0", + "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559715"), + *e2FromStrings( + "12", + "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559775"), + *e2FromStrings( + "1", + "0"), + }), + y_numerator: g2Polynomial([]fields_bls12381.E2{ + *e2FromStrings( + "3261222600550988246488569487636662646083386001431784202863158481286248011511053074731078808919938689216061999863558", + "3261222600550988246488569487636662646083386001431784202863158481286248011511053074731078808919938689216061999863558"), + *e2FromStrings( + "0", + "889424345604814976315064405719089812568196182208668418962679585805340366775741747653930584250892369786198727235518"), + *e2FromStrings( + "2668273036814444928945193217157269437704588546626005256888038757416021100327225242961791752752677109358596181706524", + "1334136518407222464472596608578634718852294273313002628444019378708010550163612621480895876376338554679298090853263"), + *e2FromStrings( + "2816510427748580758331037284777117739799287910327449993381818688383577828123182200904113516794492504322962636245776", + "0"), + }), + y_denominator: g2Polynomial([]fields_bls12381.E2{ + *e2FromStrings( + "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559355", + "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559355"), + *e2FromStrings( + "0", + "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559571"), + *e2FromStrings( + "18", + "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559769"), + *e2FromStrings( + "1", + "0"), + }), + } +} + +// Map the point from E' to E +func (m sswuMapper) isogeny(x, y *fields_bls12381.E2) *G2Affine { + xn := m.iso.x_numerator.eval(&m, *x) + + xd := m.iso.x_denominator.eval(&m, *x) + xdInv := m.ext2.Inverse(xd) + + yn := m.iso.y_numerator.eval(&m, *x) + yn = m.ext2.Mul(yn, y) + + yd := m.iso.y_denominator.eval(&m, *x) + ydInv := m.ext2.Inverse(yd) + + return &G2Affine{ + P: g2AffP{ + X: *m.ext2.Mul(xn, xdInv), + Y: *m.ext2.Mul(yn, ydInv), + }, + } +} + +func e2FromStrings(x, y string) *fields_bls12381.E2 { + A0, _ := new(big.Int).SetString(x, 10) + A1, _ := new(big.Int).SetString(y, 10) + + a0 := emulated.ValueOf[emulated.BLS12381Fp](A0) + a1 := emulated.ValueOf[emulated.BLS12381Fp](A1) + + return &fields_bls12381.E2{A0: a0, A1: a1} +} + +// Follow RFC 9380 Apendix G.3 to compute efficiently. +func clearCofactor(g2 *G2, fp *emulated.Field[emparams.BLS12381Fp], p *G2Affine) *G2Affine { + // Steps: + // 1. t1 = c1 * P + // c1 = -15132376222941642752 + t1 := g2.scalarMulBySeed(p) + // 2. t2 = psi(P) + t2 := g2.psi(p) + // 3. t3 = 2 * P + t3 := g2.double(p) + // 4. t3 = psi2(t3) + t3 = g2.psi2(t3) + // 5. t3 = t3 - t2 + t3 = g2.sub(t3, t2) + // 6. t2 = t1 + t2 + t2 = g2.addUnified(t1, t2) + // 7. t2 = c1 * t2 + t2 = g2.scalarMulBySeed(t2) + // 8. t3 = t3 + t2 + t3 = g2.addUnified(t3, t2) + // 9. t3 = t3 - t1 + t3 = g2.sub(t3, t1) + // 10. Q = t3 - P + Q := g2.sub(t3, p) + // 11. return Q + return Q +} diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go b/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go new file mode 100644 index 0000000000..4c2e8250f5 --- /dev/null +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go @@ -0,0 +1,167 @@ +package sw_bls12381 + +import ( + "encoding/hex" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + bls12381fp "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/emulated/fields_bls12381" + "github.com/consensys/gnark/std/hash/tofield" + "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/math/uints" + "github.com/consensys/gnark/test" +) + +func getMsgs() []string { + return []string{"", "a", "ab", "abc", "abcd", "abcde", "abcdef", "abcdefg", "1", "2", "3", "4", "5"} +} + +func getDst() []byte { + dstHex := "412717974da474d0f8c420f320ff81e8432adb7c927d9bd082b4fb4d16c0a236" + dst := make([]byte, len(dstHex)/2) + hex.Decode(dst, []byte(dstHex)) + return dst +} + +type hashToFieldCircuit struct { + msg []byte + dst []byte +} + +func (c *hashToFieldCircuit) Define(api frontend.API) error { + msg := uints.NewU8Array(c.msg) + uniformBytes, _ := tofield.ExpandMsgXmd(api, msg, c.dst, 64) + fp, _ := emulated.NewField[emulated.BLS12381Fp](api) + + ele := bytesToElement(api, fp, uniformBytes) + + rawEles, _ := bls12381fp.Hash(c.msg, c.dst, 1) + wrappedEle := fp.NewElement(rawEles[0]) + + fp.AssertIsEqual(ele, wrappedEle) + + return nil +} + +func TestHashToFieldTestSolve(t *testing.T) { + assert := test.NewAssert(t) + + for _, msg := range getMsgs() { + + witness := hashToFieldCircuit{ + msg: []byte(msg), + dst: getDst(), + } + err := test.IsSolved(&hashToFieldCircuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + } +} + +type mapToCurveCircuit struct { + msg []byte + dst []byte +} + +func (c *mapToCurveCircuit) Define(api frontend.API) error { + msg := uints.NewU8Array(c.msg) + uniformBytes, _ := tofield.ExpandMsgXmd(api, msg, c.dst, 128) + fp, _ := emulated.NewField[emulated.BLS12381Fp](api) + ext2 := fields_bls12381.NewExt2(api) + mapper := newMapper(api, ext2, fp) + + ele1 := bytesToElement(api, fp, uniformBytes[:64]) + ele2 := bytesToElement(api, fp, uniformBytes[64:]) + e := fields_bls12381.E2{A0: *ele1, A1: *ele2} + affine := mapper.mapToCurve(e) + + rawEles, _ := bls12381fp.Hash(c.msg, c.dst, 2) + rawAffine := bls12381.MapToCurve2(&bls12381.E2{A0: rawEles[0], A1: rawEles[1]}) + wrappedRawAffine := NewG2Affine(rawAffine) + + g2 := NewG2(api) + g2.AssertIsEqual(affine, &wrappedRawAffine) + + return nil +} + +func TestMapToCurveTestSolve(t *testing.T) { + assert := test.NewAssert(t) + + for _, msg := range getMsgs() { + + witness := hashToFieldCircuit{ + msg: []byte(msg), + dst: getDst(), + } + err := test.IsSolved(&mapToCurveCircuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + } +} + +type clearCofactorCircuit struct { + In G2Affine + Res G2Affine +} + +func (c *clearCofactorCircuit) Define(api frontend.API) error { + g2 := NewG2(api) + fp, _ := emulated.NewField[emulated.BLS12381Fp](api) + res := clearCofactor(g2, fp, &c.In) + g2.AssertIsEqual(res, &c.Res) + return nil +} + +func TestClearCofactorTestSolve(t *testing.T) { + assert := test.NewAssert(t) + _, in := randomG1G2Affines() + + inAffine := NewG2Affine(in) + + in.ClearCofactor(&in) + circuit := clearCofactorCircuit{ + In: inAffine, + Res: NewG2Affine(in), + } + witness := clearCofactorCircuit{ + In: inAffine, + Res: NewG2Affine(in), + } + err := test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} + +type hashToG2Circuit struct { + msg []byte + dst []byte +} + +func (c *hashToG2Circuit) Define(api frontend.API) error { + res, e := HashToG2(api, uints.NewU8Array(c.msg), c.dst) + if e != nil { + return e + } + + expected, _ := bls12381.HashToG2(c.msg, c.dst) + wrappedRes := NewG2Affine(expected) + + g2 := NewG2(api) + g2.AssertIsEqual(res, &wrappedRes) + return nil +} + +func TestHashToG2TestSolve(t *testing.T) { + assert := test.NewAssert(t) + + for _, msg := range getMsgs() { + + witness := hashToG2Circuit{ + msg: []uint8(msg), + dst: getDst(), + } + err := test.IsSolved(&hashToG2Circuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + } +} diff --git a/std/algebra/emulated/sw_bls12381/hints.go b/std/algebra/emulated/sw_bls12381/hints.go new file mode 100644 index 0000000000..6d6bbc4431 --- /dev/null +++ b/std/algebra/emulated/sw_bls12381/hints.go @@ -0,0 +1,44 @@ +package sw_bls12381 + +import ( + "fmt" + "math/big" + + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark/constraint/solver" + "github.com/consensys/gnark/std/math/emulated" +) + +func GetHints() []solver.Hint { + return []solver.Hint{ + sqrtRatioHint, + } +} + +func init() { + solver.RegisterHint(GetHints()...) +} + +func sqrtRatioHint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { + return emulated.UnwrapHint(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 4 { + return fmt.Errorf("expecting 4 inputs") + } + if len(outputs) != 3 { + return fmt.Errorf("expecting 3 outputs") + } + + var z0, z1, u0, u1, v0, v1 fp.Element + u0.SetBigInt(inputs[0]) + u1.SetBigInt(inputs[1]) + v0.SetBigInt(inputs[2]) + v1.SetBigInt(inputs[3]) + + b := bls12381.G2SqrtRatio(&z0, &z1, &u0, &u1, &v0, &v1) + outputs[0].SetUint64(b) + z0.BigInt(outputs[1]) + z1.BigInt(outputs[2]) + return nil + }) +} diff --git a/std/hints.go b/std/hints.go index c95ef7b955..a8e6cf9a31 100644 --- a/std/hints.go +++ b/std/hints.go @@ -4,6 +4,7 @@ import ( "sync" "github.com/consensys/gnark/constraint/solver" + "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" "github.com/consensys/gnark/std/algebra/native/sw_bls12377" "github.com/consensys/gnark/std/algebra/native/sw_bls24315" @@ -42,4 +43,5 @@ func registerHints() { solver.RegisterHint(logderivarg.GetHints()...) solver.RegisterHint(bitslice.GetHints()...) solver.RegisterHint(sw_emulated.GetHints()...) + solver.RegisterHint(sw_bls12381.GetHints()...) } From b0b11d2aa8a6ef575e363226b3371319e8daf62f Mon Sep 17 00:00:00 2001 From: Weiji Guo Date: Sun, 28 Jan 2024 20:10:36 +0800 Subject: [PATCH 003/105] revised G2.addUnified function based on the Brier and Joye algorithm --- std/algebra/emulated/sw_bls12381/g2.go | 78 ++++++++++++--------- std/algebra/emulated/sw_bls12381/g2_test.go | 55 ++++++++++++--- 2 files changed, 91 insertions(+), 42 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/g2.go b/std/algebra/emulated/sw_bls12381/g2.go index 91daab066d..a6e612c380 100644 --- a/std/algebra/emulated/sw_bls12381/g2.go +++ b/std/algebra/emulated/sw_bls12381/g2.go @@ -150,46 +150,56 @@ func (g2 G2) add(p, q *G2Affine) *G2Affine { } } -// The add() function is not complete. If p == q, then λ is 0, while it should be (3p.x²)/2*p.y according to double() -// Moreover, the AssertIsEqual() check will fail within the DivUnchecked() call as the div hint will return 0. -// -// Provide addUnified to handle this situation at the cost of additional constraints. Useful when not certain if p != q. -// Note, ClearCofactor (covered in TestClearCofactorTestSolve of hash_to_g2_test.go) might fail without this function. +// Follow sw_emulated.Curve.AddUnified to implement the Brier and Joye algorithm +// to handle edge cases, i.e., p == q, p == 0 or/and q == 0 func (g2 G2) addUnified(p, q *G2Affine) *G2Affine { - // if p != q, compute λ = (q.y-p.y)/(q.x-p.x) - qypy := g2.Ext2.Sub(&q.P.Y, &p.P.Y) - qxpx := g2.Ext2.Sub(&q.P.X, &p.P.X) - - // else if p == q, compute λ = (3p.x²)/2*p.y - xx3a := g2.Square(&p.P.X) - xx3a = g2.MulByConstElement(xx3a, big.NewInt(3)) - y2 := g2.Double(&p.P.Y) - - z := g2.Ext2.IsZero(qxpx) - deltaX := g2.Select(z, y2, qxpx) - deltaY := g2.Select(z, xx3a, qypy) - λ := g2.Ext2.DivUnchecked(deltaY, deltaX) - - // if p == -q, result should zero - zeroConst := g2.Zero() - z1 := g2.Ext2.Add(&q.P.Y, &p.P.Y) - z2 := g2.Ext2.IsZero(z1) - z3 := g2.api.And(z, z2) - // xr = λ²-p.x-q.x - λλ := g2.Ext2.Square(λ) - qxpx = g2.Ext2.Add(&p.P.X, &q.P.X) - xr := g2.Ext2.Sub(λλ, qxpx) + // selector1 = 1 when p is (0,0) and 0 otherwise + selector1 := g2.api.And(g2.Ext2.IsZero(&p.P.X), g2.Ext2.IsZero(&p.P.Y)) + // selector2 = 1 when q is (0,0) and 0 otherwise + selector2 := g2.api.And(g2.Ext2.IsZero(&q.P.X), g2.Ext2.IsZero(&q.P.Y)) + + // λ = ((p.x+q.x)² - p.x*q.x + a)/(p.y + q.y) + pxqx := g2.Ext2.Mul(&p.P.X, &q.P.X) + pxplusqx := g2.Ext2.Add(&p.P.X, &q.P.X) + num := g2.Ext2.Mul(pxplusqx, pxplusqx) + num = g2.Ext2.Sub(num, pxqx) + denum := g2.Ext2.Add(&p.P.Y, &q.P.Y) + // if p.y + q.y = 0, assign dummy 1 to denum and continue + selector3 := g2.Ext2.IsZero(denum) + denum = g2.Ext2.Select(selector3, g2.Ext2.One(), denum) + λ := g2.Ext2.DivUnchecked(num, denum) // we already know that denum won't be zero + + // x = λ^2 - p.x - q.x + xr := g2.Ext2.Mul(λ, λ) + xr = g2.Ext2.Sub(xr, pxplusqx) + + // y = λ(p.x - xr) - p.y + yr := g2.Ext2.Sub(&p.P.X, xr) + yr = g2.Ext2.Mul(yr, λ) + yr = g2.Ext2.Sub(yr, &p.P.Y) + result := &G2Affine{ + P: g2AffP{ + X: *xr, + Y: *yr, + }, + } - // yr = λ(p.x-r.x) - p.y - pxrx := g2.Ext2.Sub(&p.P.X, xr) - λpxrx := g2.Ext2.Mul(λ, pxrx) - yr := g2.Ext2.Sub(λpxrx, &p.P.Y) + zero := g2.Ext2.Zero() + // if p=(0,0) return q + resultX := *g2.Select(selector1, &q.P.X, &result.P.X) + resultY := *g2.Select(selector1, &q.P.Y, &result.P.Y) + // if q=(0,0) return p + resultX = *g2.Select(selector2, &p.P.X, &resultX) + resultY = *g2.Select(selector2, &p.P.Y, &resultY) + // if p.y + q.y = 0, return (0, 0) + resultX = *g2.Select(selector3, zero, &resultX) + resultY = *g2.Select(selector3, zero, &resultY) return &G2Affine{ P: g2AffP{ - X: *g2.Select(z3, zeroConst, xr), - Y: *g2.Select(z3, zeroConst, yr), + X: resultX, + Y: resultY, }, } } diff --git a/std/algebra/emulated/sw_bls12381/g2_test.go b/std/algebra/emulated/sw_bls12381/g2_test.go index 467925bea4..534843ccd4 100644 --- a/std/algebra/emulated/sw_bls12381/g2_test.go +++ b/std/algebra/emulated/sw_bls12381/g2_test.go @@ -93,19 +93,58 @@ func TestAddG2UnifiedTestSolveDbl(t *testing.T) { assert.NoError(err) } -func TestAddG2UnifiedTestSolveNeg(t *testing.T) { +func TestAddG2UnifiedTestSolveEdgeCases(t *testing.T) { assert := test.NewAssert(t) - _, in1 := randomG1G2Affines() - var in2, res bls12381.G2Affine - in2.Neg(&in1) - res.Add(&in1, &in2) + _, p := randomG1G2Affines() + var np, zero bls12381.G2Affine + np.Neg(&p) + zero.Sub(&p, &p) + + // p + (-p) == (0, 0) witness := addG2UnifiedCircuit{ - In1: NewG2Affine(in1), - In2: NewG2Affine(in2), - Res: NewG2Affine(res), + In1: NewG2Affine(p), + In2: NewG2Affine(np), + Res: NewG2Affine(zero), } err := test.IsSolved(&addG2UnifiedCircuit{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) + + // (-p) + p == (0, 0) + witness2 := addG2UnifiedCircuit{ + In1: NewG2Affine(np), + In2: NewG2Affine(p), + Res: NewG2Affine(zero), + } + err2 := test.IsSolved(&addG2UnifiedCircuit{}, &witness2, ecc.BN254.ScalarField()) + assert.NoError(err2) + + // p + (0, 0) == p + witness3 := addG2UnifiedCircuit{ + In1: NewG2Affine(p), + In2: NewG2Affine(zero), + Res: NewG2Affine(p), + } + err3 := test.IsSolved(&addG2UnifiedCircuit{}, &witness3, ecc.BN254.ScalarField()) + assert.NoError(err3) + + // (0, 0) + p == p + witness4 := addG2UnifiedCircuit{ + In1: NewG2Affine(zero), + In2: NewG2Affine(p), + Res: NewG2Affine(p), + } + err4 := test.IsSolved(&addG2UnifiedCircuit{}, &witness4, ecc.BN254.ScalarField()) + assert.NoError(err4) + + // (0, 0) + (0, 0) == (0, 0) + witness5 := addG2UnifiedCircuit{ + In1: NewG2Affine(zero), + In2: NewG2Affine(zero), + Res: NewG2Affine(zero), + } + err5 := test.IsSolved(&addG2UnifiedCircuit{}, &witness5, ecc.BN254.ScalarField()) + assert.NoError(err5) + } type doubleG2Circuit struct { From b4da7688710bd8400c8c53d8f744edfdc3e77eba Mon Sep 17 00:00:00 2001 From: Weiji Guo Date: Mon, 29 Jan 2024 19:59:20 +0800 Subject: [PATCH 004/105] fixed G2.sgn0 function and associated unit tests for BLS12-381 --- .../emulated/sw_bls12381/hash_to_g2.go | 6 +- .../emulated/sw_bls12381/hash_to_g2_test.go | 93 ++++++++++++------- 2 files changed, 62 insertions(+), 37 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2.go b/std/algebra/emulated/sw_bls12381/hash_to_g2.go index 10266f3839..55d8d9a52f 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2.go @@ -184,12 +184,14 @@ func (m sswuMapper) mapToCurve(u fields_bls12381.E2) *G2Affine { func (m sswuMapper) sgn0(x *fields_bls12381.E2) frontend.Variable { // Steps for sgn0_m_eq_2 // 1. sign_0 = x_0 mod 2 - x0 := m.fp.ToBits(&x.A0) + A0 := m.fp.Reduce(&x.A0) + x0 := m.fp.ToBits(A0) sign0 := x0[0] // 2. zero_0 = x_0 == 0 zero0 := m.fp.IsZero(&x.A0) // 3. sign_1 = x_1 mod 2 - x1 := m.fp.ToBits(&x.A1) + A1 := m.fp.Reduce(&x.A1) + x1 := m.fp.ToBits(A1) sign1 := x1[0] // 4. s = sign_0 OR (zero_0 AND sign_1) # Avoid short-circuit logic ops tv := m.api.And(zero0, sign1) diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go b/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go index 4c2e8250f5..a0dbc6e527 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go @@ -16,7 +16,7 @@ import ( ) func getMsgs() []string { - return []string{"", "a", "ab", "abc", "abcd", "abcde", "abcdef", "abcdefg", "1", "2", "3", "4", "5"} + return []string{"", "a", "ab", "abc", "abcd", "abcde", "abcdef", "abcdefg", "1", "2", "3", "4", "5", "5656565656565656565656565656565656565656565656565656565656565656"} } func getDst() []byte { @@ -27,76 +27,91 @@ func getDst() []byte { } type hashToFieldCircuit struct { - msg []byte - dst []byte + Msg []byte + Dst []byte + Res bls12381fp.Element } func (c *hashToFieldCircuit) Define(api frontend.API) error { - msg := uints.NewU8Array(c.msg) - uniformBytes, _ := tofield.ExpandMsgXmd(api, msg, c.dst, 64) + msg := uints.NewU8Array(c.Msg) + uniformBytes, _ := tofield.ExpandMsgXmd(api, msg, c.Dst, 64) fp, _ := emulated.NewField[emulated.BLS12381Fp](api) ele := bytesToElement(api, fp, uniformBytes) - rawEles, _ := bls12381fp.Hash(c.msg, c.dst, 1) - wrappedEle := fp.NewElement(rawEles[0]) - - fp.AssertIsEqual(ele, wrappedEle) + fp.AssertIsEqual(ele, fp.NewElement(c.Res)) return nil } func TestHashToFieldTestSolve(t *testing.T) { assert := test.NewAssert(t) + dst := getDst() for _, msg := range getMsgs() { + rawEles, _ := bls12381fp.Hash([]byte(msg), dst, 1) + + circuit := hashToFieldCircuit{ + Msg: []byte(msg), + Dst: dst, + Res: rawEles[0], + } witness := hashToFieldCircuit{ - msg: []byte(msg), - dst: getDst(), + Msg: []byte(msg), + Dst: dst, + Res: rawEles[0], } - err := test.IsSolved(&hashToFieldCircuit{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } } type mapToCurveCircuit struct { - msg []byte - dst []byte + Msg []byte + Dst []byte + Res G2Affine } func (c *mapToCurveCircuit) Define(api frontend.API) error { - msg := uints.NewU8Array(c.msg) - uniformBytes, _ := tofield.ExpandMsgXmd(api, msg, c.dst, 128) + msg := uints.NewU8Array(c.Msg) fp, _ := emulated.NewField[emulated.BLS12381Fp](api) ext2 := fields_bls12381.NewExt2(api) mapper := newMapper(api, ext2, fp) + uniformBytes, _ := tofield.ExpandMsgXmd(api, msg, c.Dst, 128) ele1 := bytesToElement(api, fp, uniformBytes[:64]) ele2 := bytesToElement(api, fp, uniformBytes[64:]) e := fields_bls12381.E2{A0: *ele1, A1: *ele2} affine := mapper.mapToCurve(e) - rawEles, _ := bls12381fp.Hash(c.msg, c.dst, 2) - rawAffine := bls12381.MapToCurve2(&bls12381.E2{A0: rawEles[0], A1: rawEles[1]}) - wrappedRawAffine := NewG2Affine(rawAffine) - g2 := NewG2(api) - g2.AssertIsEqual(affine, &wrappedRawAffine) + g2.AssertIsEqual(affine, &c.Res) return nil } func TestMapToCurveTestSolve(t *testing.T) { assert := test.NewAssert(t) + dst := getDst() for _, msg := range getMsgs() { - witness := hashToFieldCircuit{ - msg: []byte(msg), - dst: getDst(), + rawEles, _ := bls12381fp.Hash([]byte(msg), dst, 2) + rawAffine := bls12381.MapToCurve2(&bls12381.E2{A0: rawEles[0], A1: rawEles[1]}) + wrappedRawAffine := NewG2Affine(rawAffine) + + circuit := mapToCurveCircuit{ + Msg: []byte(msg), + Dst: dst, + Res: wrappedRawAffine, } - err := test.IsSolved(&mapToCurveCircuit{}, &witness, ecc.BN254.ScalarField()) + witness := mapToCurveCircuit{ + Msg: []byte(msg), + Dst: dst, + Res: wrappedRawAffine, + } + err := test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } } @@ -134,34 +149,42 @@ func TestClearCofactorTestSolve(t *testing.T) { } type hashToG2Circuit struct { - msg []byte - dst []byte + Msg []byte + Dst []byte + Res G2Affine } func (c *hashToG2Circuit) Define(api frontend.API) error { - res, e := HashToG2(api, uints.NewU8Array(c.msg), c.dst) + res, e := HashToG2(api, uints.NewU8Array(c.Msg), c.Dst) if e != nil { return e } - expected, _ := bls12381.HashToG2(c.msg, c.dst) - wrappedRes := NewG2Affine(expected) - g2 := NewG2(api) - g2.AssertIsEqual(res, &wrappedRes) + g2.AssertIsEqual(res, &c.Res) return nil } func TestHashToG2TestSolve(t *testing.T) { assert := test.NewAssert(t) + dst := getDst() for _, msg := range getMsgs() { + expected, _ := bls12381.HashToG2([]uint8(msg), dst) + wrappedRes := NewG2Affine(expected) + + circuit := hashToG2Circuit{ + Msg: []uint8(msg), + Dst: dst, + Res: wrappedRes, + } witness := hashToG2Circuit{ - msg: []uint8(msg), - dst: getDst(), + Msg: []uint8(msg), + Dst: dst, + Res: wrappedRes, } - err := test.IsSolved(&hashToG2Circuit{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } } From 86d12ce7661a4261159f000f6e5de8d2760c3e69 Mon Sep 17 00:00:00 2001 From: Weiji Guo Date: Mon, 29 Jan 2024 20:23:05 +0800 Subject: [PATCH 005/105] implemented BLS signature verification for BLS12-381/G2 --- std/algebra/emulated/sw_bls12381/bls_sig.go | 34 ++++++++ .../emulated/sw_bls12381/bls_sig_test.go | 83 +++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 std/algebra/emulated/sw_bls12381/bls_sig.go create mode 100644 std/algebra/emulated/sw_bls12381/bls_sig_test.go diff --git a/std/algebra/emulated/sw_bls12381/bls_sig.go b/std/algebra/emulated/sw_bls12381/bls_sig.go new file mode 100644 index 0000000000..410da6be19 --- /dev/null +++ b/std/algebra/emulated/sw_bls12381/bls_sig.go @@ -0,0 +1,34 @@ +package sw_bls12381 + +import ( + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/uints" +) + +const g2_dst = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_" + +func BlsAssertG2Verification(api frontend.API, pub G1Affine, sig G2Affine, msg []uints.U8) error { + pairing, e := NewPairing(api) + if e != nil { + return e + } + + // public key cannot be infinity + xtest := pairing.g1.curveF.IsZero(&pub.X) + ytest := pairing.g1.curveF.IsZero(&pub.Y) + pubTest := api.Or(xtest, ytest) + api.AssertIsEqual(pubTest, 0) + + // prime order subgroup checks + pairing.AssertIsOnG1(&pub) + pairing.AssertIsOnG2(&sig) + + var g1GNeg bls12381.G1Affine + _, _, g1Gen, _ := bls12381.Generators() + g1GNeg.Neg(&g1Gen) + g1GN := NewG1Affine(g1GNeg) + + h, e := HashToG2(api, msg, []byte(g2_dst)) + return pairing.PairingCheck([]*G1Affine{&g1GN, &pub}, []*G2Affine{&sig, h}) +} diff --git a/std/algebra/emulated/sw_bls12381/bls_sig_test.go b/std/algebra/emulated/sw_bls12381/bls_sig_test.go new file mode 100644 index 0000000000..47827ab258 --- /dev/null +++ b/std/algebra/emulated/sw_bls12381/bls_sig_test.go @@ -0,0 +1,83 @@ +package sw_bls12381 + +import ( + "encoding/hex" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/uints" + "github.com/consensys/gnark/test" +) + +type blsG2SigCircuit struct { + Pub bls12381.G1Affine + msg []byte + Sig bls12381.G2Affine +} + +func (c *blsG2SigCircuit) Define(api frontend.API) error { + msg := uints.NewU8Array(c.msg) + return BlsAssertG2Verification(api, NewG1Affine(c.Pub), NewG2Affine(c.Sig), msg) +} + +// "pubkey": "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a", +// "message": "0x5656565656565656565656565656565656565656565656565656565656565656", +// "signature": "0x882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb"}, +// "output": true} +func TestBlsSigTestSolve(t *testing.T) { + assert := test.NewAssert(t) + + msgHex := "5656565656565656565656565656565656565656565656565656565656565656" + pubHex := "a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a" + sigHex := "882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb" + + msgBytes := make([]byte, len(msgHex)>>1) + hex.Decode(msgBytes, []byte(msgHex)) + pubBytes := make([]byte, len(pubHex)>>1) + hex.Decode(pubBytes, []byte(pubHex)) + sigBytes := make([]byte, len(sigHex)>>1) + hex.Decode(sigBytes, []byte(sigHex)) + + var pub bls12381.G1Affine + _, e := pub.SetBytes(pubBytes) + if e != nil { + t.Fail() + } + var sig bls12381.G2Affine + _, e = sig.SetBytes(sigBytes) + if e != nil { + t.Fail() + } + + var g1GNeg bls12381.G1Affine + _, _, g1Gen, _ := bls12381.Generators() + g1GNeg.Neg(&g1Gen) + + h, e := bls12381.HashToG2(msgBytes, []byte(g2_dst)) + if e != nil { + t.Fail() + } + + b, e := bls12381.PairingCheck([]bls12381.G1Affine{g1GNeg, pub}, []bls12381.G2Affine{sig, h}) + if e != nil { + t.Fail() + } + if !b { + t.Fail() // invalid inputs, won't verify + } + + circuit := blsG2SigCircuit{ + Pub: pub, + msg: msgBytes, + Sig: sig, + } + witness := blsG2SigCircuit{ + Pub: pub, + msg: msgBytes, + Sig: sig, + } + err := test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} From 78bdcf6a20aadf03a659233088bed182c6d91333 Mon Sep 17 00:00:00 2001 From: Weiji Guo Date: Tue, 30 Jan 2024 20:46:22 +0800 Subject: [PATCH 006/105] added benchmarks for HashToG2/BLS12-381 --- .../emulated/sw_bls12381/hash_to_g2_test.go | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go b/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go index a0dbc6e527..683106b5b6 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go @@ -1,13 +1,17 @@ package sw_bls12381 import ( + "bytes" "encoding/hex" "testing" "github.com/consensys/gnark-crypto/ecc" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" bls12381fp "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark/constraint" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" + "github.com/consensys/gnark/frontend/cs/scs" "github.com/consensys/gnark/std/algebra/emulated/fields_bls12381" "github.com/consensys/gnark/std/hash/tofield" "github.com/consensys/gnark/std/math/emulated" @@ -188,3 +192,75 @@ func TestHashToG2TestSolve(t *testing.T) { assert.NoError(err) } } + +type hashToG2BenchCircuit struct { + Msg []byte + Dst []byte +} + +func (c *hashToG2BenchCircuit) Define(api frontend.API) error { + _, e := HashToG2(api, uints.NewU8Array(c.Msg), c.Dst) + return e +} + +func BenchmarkHashToG2(b *testing.B) { + + dst := getDst() + + msg := "abcd" + witness := hashToG2BenchCircuit{ + Msg: []uint8(msg), + Dst: dst, + } + w, err := frontend.NewWitness(&witness, ecc.BN254.ScalarField()) + if err != nil { + b.Fatal(err) + } + var ccs constraint.ConstraintSystem + b.Run("compile scs", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + if ccs, err = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &hashToG2BenchCircuit{}); err != nil { + b.Fatal(err) + } + } + }) + var buf bytes.Buffer + _, err = ccs.WriteTo(&buf) + if err != nil { + b.Fatal(err) + } + b.Logf("scs size: %d (bytes), nb constraints %d, nbInstructions: %d", buf.Len(), ccs.GetNbConstraints(), ccs.GetNbInstructions()) + b.Run("solve scs", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := ccs.Solve(w); err != nil { + b.Fatal(err) + } + } + }) + b.Run("compile r1cs", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + if ccs, err = frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &hashToG2BenchCircuit{}); err != nil { + b.Fatal(err) + } + } + }) + buf.Reset() + _, err = ccs.WriteTo(&buf) + if err != nil { + b.Fatal(err) + } + b.Logf("r1cs size: %d (bytes), nb constraints %d, nbInstructions: %d", buf.Len(), ccs.GetNbConstraints(), ccs.GetNbInstructions()) + + b.Run("solve r1cs", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := ccs.Solve(w); err != nil { + b.Fatal(err) + } + } + }) + +} From 8e2fc0eb790995cbab894df2a39c2a2530f02ec9 Mon Sep 17 00:00:00 2001 From: Weiji Guo Date: Sat, 10 Feb 2024 18:02:38 +0800 Subject: [PATCH 007/105] gofmt --- std/hints.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/hints.go b/std/hints.go index 54ed859322..33149e9ef2 100644 --- a/std/hints.go +++ b/std/hints.go @@ -4,10 +4,10 @@ import ( "sync" "github.com/consensys/gnark/constraint/solver" - "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" "github.com/consensys/gnark/std/algebra/emulated/fields_bls12381" "github.com/consensys/gnark/std/algebra/emulated/fields_bn254" "github.com/consensys/gnark/std/algebra/emulated/fields_bw6761" + "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" "github.com/consensys/gnark/std/algebra/native/fields_bls12377" "github.com/consensys/gnark/std/algebra/native/fields_bls24315" From 4a2f9b0ee1b7956e2bd74c88bb37fe7d405a07a6 Mon Sep 17 00:00:00 2001 From: Weiji Guo Date: Mon, 19 Feb 2024 14:53:46 +0800 Subject: [PATCH 008/105] golangci-lint --- std/algebra/emulated/sw_bls12381/bls_sig.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/std/algebra/emulated/sw_bls12381/bls_sig.go b/std/algebra/emulated/sw_bls12381/bls_sig.go index 410da6be19..2969485391 100644 --- a/std/algebra/emulated/sw_bls12381/bls_sig.go +++ b/std/algebra/emulated/sw_bls12381/bls_sig.go @@ -30,5 +30,9 @@ func BlsAssertG2Verification(api frontend.API, pub G1Affine, sig G2Affine, msg [ g1GN := NewG1Affine(g1GNeg) h, e := HashToG2(api, msg, []byte(g2_dst)) + if e != nil { + return e + } + return pairing.PairingCheck([]*G1Affine{&g1GN, &pub}, []*G2Affine{&sig, h}) } From 8921270f1a25c1a77b3db54dc1da480f339d513a Mon Sep 17 00:00:00 2001 From: Weiji Guo Date: Fri, 24 Jan 2025 09:06:27 +0800 Subject: [PATCH 009/105] fixed spacing issue introduced during merging --- std/algebra/emulated/sw_bls12381/hints.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/algebra/emulated/sw_bls12381/hints.go b/std/algebra/emulated/sw_bls12381/hints.go index fc05c91cd0..796ed964c9 100644 --- a/std/algebra/emulated/sw_bls12381/hints.go +++ b/std/algebra/emulated/sw_bls12381/hints.go @@ -13,7 +13,7 @@ import ( func GetHints() []solver.Hint { return []solver.Hint{ sqrtRatioHint, - finalExpHint, + finalExpHint, pairingCheckHint, } } From d179d7146f933652a83697006f7cd68231f83670 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 13 Mar 2025 15:32:31 -0400 Subject: [PATCH 010/105] feat(pectra/precompiles): BLS12_G1ADD and BLS12_PAIRING_CHECK --- std/algebra/emulated/fields_bls12381/e2.go | 8 + std/algebra/emulated/sw_bls12381/g1.go | 9 + std/algebra/emulated/sw_bls12381/g2.go | 9 +- std/algebra/emulated/sw_bls12381/hints.go | 73 ++++++++ std/algebra/emulated/sw_bls12381/pairing.go | 193 +++++++++++++++++++- std/evmprecompiles/11-blsadd.go | 21 +++ std/evmprecompiles/15-blspairing.go | 116 ++++++++++++ std/evmprecompiles/bls_test.go | 136 ++++++++++++++ 8 files changed, 561 insertions(+), 4 deletions(-) create mode 100644 std/evmprecompiles/11-blsadd.go create mode 100644 std/evmprecompiles/15-blspairing.go create mode 100644 std/evmprecompiles/bls_test.go diff --git a/std/algebra/emulated/fields_bls12381/e2.go b/std/algebra/emulated/fields_bls12381/e2.go index ba378f6796..da31205f7b 100644 --- a/std/algebra/emulated/fields_bls12381/e2.go +++ b/std/algebra/emulated/fields_bls12381/e2.go @@ -292,6 +292,14 @@ func (e Ext2) AssertIsEqual(x, y *E2) { e.fp.AssertIsEqual(&x.A1, &y.A1) } +func (e Ext2) IsEqual(x, y *E2) frontend.Variable { + xDiff := e.fp.Sub(&x.A0, &y.A0) + yDiff := e.fp.Sub(&x.A1, &y.A1) + xIsZero := e.fp.IsZero(xDiff) + yIsZero := e.fp.IsZero(yDiff) + return e.api.And(xIsZero, yIsZero) +} + func FromE2(y *bls12381.E2) E2 { return E2{ A0: emulated.ValueOf[emulated.BLS12381Fp](y.A0), diff --git a/std/algebra/emulated/sw_bls12381/g1.go b/std/algebra/emulated/sw_bls12381/g1.go index ce8398616a..29f392d482 100644 --- a/std/algebra/emulated/sw_bls12381/g1.go +++ b/std/algebra/emulated/sw_bls12381/g1.go @@ -28,6 +28,7 @@ func NewG1Affine(v bls12381.G1Affine) G1Affine { } type G1 struct { + api frontend.API curveF *emulated.Field[BaseField] w *emulated.Element[BaseField] } @@ -157,6 +158,14 @@ func (g1 *G1) scalarMulBySeedSquare(q *G1Affine) *G1Affine { return z } +func (g1 *G1) IsEqual(p, q *G1Affine) frontend.Variable { + xDiff := g1.curveF.Sub(&p.X, &q.X) + yDiff := g1.curveF.Sub(&p.Y, &q.Y) + xIsZero := g1.curveF.IsZero(xDiff) + yIsZero := g1.curveF.IsZero(yDiff) + return g1.api.And(xIsZero, yIsZero) +} + // NewScalar allocates a witness from the native scalar and returns it. func NewScalar(v fr_bls12381.Element) Scalar { return emulated.ValueOf[ScalarField](v) diff --git a/std/algebra/emulated/sw_bls12381/g2.go b/std/algebra/emulated/sw_bls12381/g2.go index 4f3245ee21..5cd2f22289 100644 --- a/std/algebra/emulated/sw_bls12381/g2.go +++ b/std/algebra/emulated/sw_bls12381/g2.go @@ -10,7 +10,8 @@ import ( ) type G2 struct { - fp *emulated.Field[BaseField] + api frontend.API + fp *emulated.Field[BaseField] *fields_bls12381.Ext2 u1, w *emulated.Element[BaseField] v *fields_bls12381.E2 @@ -281,3 +282,9 @@ func (g2 *G2) AssertIsEqual(p, q *G2Affine) { g2.Ext2.AssertIsEqual(&p.P.X, &q.P.X) g2.Ext2.AssertIsEqual(&p.P.Y, &q.P.Y) } + +func (g2 *G2) IsEqual(p, q *G2Affine) frontend.Variable { + xEqual := g2.Ext2.IsEqual(&p.P.X, &q.P.X) + yEqual := g2.Ext2.IsEqual(&p.P.Y, &q.P.Y) + return g2.api.And(xEqual, yEqual) +} diff --git a/std/algebra/emulated/sw_bls12381/hints.go b/std/algebra/emulated/sw_bls12381/hints.go index b767113daf..0620406e7d 100644 --- a/std/algebra/emulated/sw_bls12381/hints.go +++ b/std/algebra/emulated/sw_bls12381/hints.go @@ -1,6 +1,7 @@ package sw_bls12381 import ( + "errors" "math/big" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" @@ -17,6 +18,7 @@ func GetHints() []solver.Hint { return []solver.Hint{ finalExpHint, pairingCheckHint, + millerLoopAndCheckFinalExpHint, } } @@ -200,3 +202,74 @@ func finalExpWitness(millerLoop *bls12381.E12) (residueWitness, scalingFactor bl return residueWitness, scalingFactor } + +func millerLoopAndCheckFinalExpHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { + return emulated.UnwrapHint(nativeInputs, nativeOutputs, + func(mod *big.Int, inputs, outputs []*big.Int) error { + var P bls12381.G1Affine + var Q bls12381.G2Affine + var previous bls12381.E12 + + P.X.SetBigInt(inputs[0]) + P.Y.SetBigInt(inputs[1]) + Q.X.A0.SetBigInt(inputs[2]) + Q.X.A1.SetBigInt(inputs[3]) + Q.Y.A0.SetBigInt(inputs[4]) + Q.Y.A1.SetBigInt(inputs[5]) + + previous.C0.B0.A0.SetBigInt(inputs[6]) + previous.C0.B0.A1.SetBigInt(inputs[7]) + previous.C0.B1.A0.SetBigInt(inputs[8]) + previous.C0.B1.A1.SetBigInt(inputs[9]) + previous.C0.B2.A0.SetBigInt(inputs[10]) + previous.C0.B2.A1.SetBigInt(inputs[11]) + previous.C1.B0.A0.SetBigInt(inputs[12]) + previous.C1.B0.A1.SetBigInt(inputs[13]) + previous.C1.B1.A0.SetBigInt(inputs[14]) + previous.C1.B1.A1.SetBigInt(inputs[15]) + previous.C1.B2.A0.SetBigInt(inputs[16]) + previous.C1.B2.A1.SetBigInt(inputs[17]) + + if previous.IsZero() { + return errors.New("previous Miller loop result is zero") + } + + lines := bls12381.PrecomputeLines(Q) + millerLoop, err := bls12381.MillerLoopFixedQ( + []bls12381.G1Affine{P}, + [][2][len(bls12381.LoopCounter) - 1]bls12381.LineEvaluationAff{lines}, + ) + if err != nil { + return err + } + millerLoop.Conjugate(&millerLoop) + + millerLoop.Mul(&millerLoop, &previous) + + residueWitnessInv, scalingFactor := finalExpWitness(&millerLoop) + residueWitnessInv.Inverse(&residueWitnessInv) + + residueWitnessInv.C0.B0.A0.BigInt(outputs[0]) + residueWitnessInv.C0.B0.A1.BigInt(outputs[1]) + residueWitnessInv.C0.B1.A0.BigInt(outputs[2]) + residueWitnessInv.C0.B1.A1.BigInt(outputs[3]) + residueWitnessInv.C0.B2.A0.BigInt(outputs[4]) + residueWitnessInv.C0.B2.A1.BigInt(outputs[5]) + residueWitnessInv.C1.B0.A0.BigInt(outputs[6]) + residueWitnessInv.C1.B0.A1.BigInt(outputs[7]) + residueWitnessInv.C1.B1.A0.BigInt(outputs[8]) + residueWitnessInv.C1.B1.A1.BigInt(outputs[9]) + residueWitnessInv.C1.B2.A0.BigInt(outputs[10]) + residueWitnessInv.C1.B2.A1.BigInt(outputs[11]) + + // return the scaling factor + scalingFactor.C0.B0.A0.BigInt(outputs[12]) + scalingFactor.C0.B0.A1.BigInt(outputs[13]) + scalingFactor.C0.B1.A0.BigInt(outputs[14]) + scalingFactor.C0.B1.A1.BigInt(outputs[15]) + scalingFactor.C0.B2.A0.BigInt(outputs[16]) + scalingFactor.C0.B2.A1.BigInt(outputs[17]) + + return nil + }) +} diff --git a/std/algebra/emulated/sw_bls12381/pairing.go b/std/algebra/emulated/sw_bls12381/pairing.go index 183bde4f30..6be722ac01 100644 --- a/std/algebra/emulated/sw_bls12381/pairing.go +++ b/std/algebra/emulated/sw_bls12381/pairing.go @@ -181,6 +181,10 @@ func (pr Pairing) PairingCheck(P []*G1Affine, Q []*G2Affine) error { return nil } +func (pr Pairing) IsEqual(x, y *GTEl) frontend.Variable { + return pr.Ext12.IsEqual(x, y) +} + func (pr Pairing) AssertIsEqual(x, y *GTEl) { pr.Ext12.AssertIsEqual(x, y) } @@ -189,7 +193,30 @@ func (pr Pairing) AssertIsOnCurve(P *G1Affine) { pr.curve.AssertIsOnCurve(P) } -func (pr Pairing) AssertIsOnTwist(Q *G2Affine) { +func (pr Pairing) computeCurveEquation(P *G1Affine) (left, right *baseEl) { + // Curve: Y² == X³ + aX + b, where a=0 and b=4 + // (X,Y) ∈ {Y² == X³ + aX + b} U (0,0) + + // if P=(0,0) we assign b=0 otherwise 4, and continue + selector := pr.api.And(pr.curveF.IsZero(&P.X), pr.curveF.IsZero(&P.Y)) + four := emulated.ValueOf[BaseField]("4") + b := pr.curveF.Select(selector, pr.curveF.Zero(), &four) + + left = pr.curveF.Mul(&P.Y, &P.Y) + right = pr.curveF.Mul(&P.X, &P.X) + right = pr.curveF.Mul(right, &P.X) + right = pr.curveF.Add(right, b) + return left, right +} + +// IsOnCurve returns a boolean indicating if the G1 point is in the curve. +func (pr Pairing) IsOnCurve(P *G1Affine) frontend.Variable { + left, right := pr.computeCurveEquation(P) + diff := pr.curveF.Sub(left, right) + return pr.curveF.IsZero(diff) +} + +func (pr Pairing) computeTwistEquation(Q *G2Affine) (left, right *fields_bls12381.E2) { // Twist: Y² == X³ + aX + b, where a=0 and b=4(1+u) // (X,Y) ∈ {Y² == X³ + aX + b} U (0,0) @@ -197,13 +224,25 @@ func (pr Pairing) AssertIsOnTwist(Q *G2Affine) { selector := pr.api.And(pr.Ext2.IsZero(&Q.P.X), pr.Ext2.IsZero(&Q.P.Y)) b := pr.Ext2.Select(selector, pr.Ext2.Zero(), pr.bTwist) - left := pr.Ext2.Square(&Q.P.Y) - right := pr.Ext2.Square(&Q.P.X) + left = pr.Ext2.Square(&Q.P.Y) + right = pr.Ext2.Square(&Q.P.X) right = pr.Ext2.Mul(right, &Q.P.X) right = pr.Ext2.Add(right, b) + return left, right +} + +func (pr Pairing) AssertIsOnTwist(Q *G2Affine) { + left, right := pr.computeTwistEquation(Q) pr.Ext2.AssertIsEqual(left, right) } +// IsOnTwist returns a boolean indicating if the G2 point is in the twist. +func (pr Pairing) IsOnTwist(Q *G2Affine) frontend.Variable { + left, right := pr.computeTwistEquation(Q) + diff := pr.Ext2.Sub(left, right) + return pr.Ext2.IsZero(diff) +} + func (pr Pairing) AssertIsOnG1(P *G1Affine) { // 1- Check P is on the curve pr.AssertIsOnCurve(P) @@ -218,6 +257,21 @@ func (pr Pairing) AssertIsOnG1(P *G1Affine) { pr.curve.AssertIsEqual(_P, P) } +// IsOnG1 returns a boolean indicating if the G1 point is in the subgroup. The +// method assumes that the point is already on the curve. Call +// [Pairing.AssertIsOnTwist] before to ensure point is on the curve. +func (pr Pairing) IsOnG1(P *G1Affine) frontend.Variable { + // 1 - is P on curve + isOnCurve := pr.IsOnCurve(P) + // 2 - is P in the subgroup + phiP := pr.g1.phi(P) + _P := pr.g1.scalarMulBySeedSquare(phiP) + _P = pr.curve.Neg(_P) + isInSubgroup := pr.g1.IsEqual(_P, phiP) + + return pr.api.And(isOnCurve, isInSubgroup) +} + func (pr Pairing) AssertIsOnG2(Q *G2Affine) { // 1- Check Q is on the curve pr.AssertIsOnTwist(Q) @@ -232,6 +286,19 @@ func (pr Pairing) AssertIsOnG2(Q *G2Affine) { pr.g2.AssertIsEqual(xQ, psiQ) } +// IsOnG2 returns a boolean indicating if the G2 point is in the subgroup. The +// method assumes that the point is already on the curve. Call +// [Pairing.AssertIsOnTwist] before to ensure point is on the curve. +func (pr Pairing) IsOnG2(Q *G2Affine) frontend.Variable { + // 1 - is Q on curve + isOnCurve := pr.IsOnTwist(Q) + // 2 - is Q in the subgroup + xQ := pr.g2.scalarMulBySeed(Q) + psiQ := pr.g2.psi(Q) + isInSubgroup := pr.g2.IsEqual(xQ, psiQ) + return pr.api.And(isOnCurve, isInSubgroup) +} + // loopCounter = seed in binary // // seed=-15132376222941642752 @@ -608,3 +675,123 @@ func (pr Pairing) tangentCompute(p1 *g2AffP) *lineEvaluation { return &line } + +// MillerLoopAndMul computes the Miller loop between P and Q +// and multiplies it in 𝔽p¹² by previous. +// +// This method is needed for evmprecompiles/ecpair. +func (pr Pairing) MillerLoopAndMul(P *G1Affine, Q *G2Affine, previous *GTEl) (*GTEl, error) { + res, err := pr.MillerLoop([]*G1Affine{P}, []*G2Affine{Q}) + if err != nil { + return nil, fmt.Errorf("miller loop: %w", err) + } + res = pr.Ext12.Conjugate(res) + res = pr.Ext12.Mul(res, previous) + return res, err +} + +// AssertMillerLoopAndFinalExpIsOne computes the Miller loop between P and Q, +// multiplies it in 𝔽p¹² by previous and checks that the result lies in the +// same equivalence class as the reduced pairing purported to be 1. This check +// replaces the final exponentiation step in-circuit and follows Section 4 of +// [On Proving Pairings] paper by A. Novakovic and L. Eagen. +// +// This method is needed for evmprecompiles/ecpair. +// +// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf +func (pr Pairing) AssertMillerLoopAndFinalExpIsOne(P *G1Affine, Q *G2Affine, previous *GTEl) { + t2 := pr.millerLoopAndFinalExpResult(P, Q, previous) + pr.AssertIsEqual(t2, pr.Ext12.One()) +} + +// millerLoopAndFinalExpResult computes the Miller loop between P and Q, +// multiplies it in 𝔽p¹² by previous and returns the result. +func (pr Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previous *GTEl) *GTEl { + tower := pr.ToTower(previous) + + // hint the non-residue witness + hint, err := pr.curveF.NewHint(millerLoopAndCheckFinalExpHint, 18, &P.X, &P.Y, &Q.P.X.A0, &Q.P.X.A1, &Q.P.Y.A0, &Q.P.Y.A1, tower[0], tower[1], tower[2], tower[3], tower[4], tower[5], tower[6], tower[7], tower[8], tower[9], tower[10], tower[11]) + if err != nil { + // err is non-nil only for invalid number of inputs + panic(err) + } + residueWitnessInv := pr.Ext12.FromTower([12]*baseEl{hint[0], hint[1], hint[2], hint[3], hint[4], hint[5], hint[6], hint[7], hint[8], hint[9], hint[10], hint[11]}) + // constrain scalingFactor to be in Fp6 + // that is: a100=a101=a110=a111=a120=a121=0 + // or + // A0 = a000 - a001 + // A1 = 0 + // A2 = a010 - a011 + // A3 = 0 + // A4 = a020 - a021 + // A5 = 0 + // A6 = a001 + // A7 = 0 + // A8 = a011 + // A9 = 0 + // A10 = a021 + // A11 = 0 + scalingFactor := GTEl{ + A0: *pr.curveF.Sub(hint[12], hint[13]), + A1: *pr.curveF.Zero(), + A2: *pr.curveF.Sub(hint[14], hint[15]), + A3: *pr.curveF.Zero(), + A4: *pr.curveF.Sub(hint[16], hint[17]), + A5: *pr.curveF.Zero(), + A6: *hint[13], + A7: *pr.curveF.Zero(), + A8: *hint[15], + A9: *pr.curveF.Zero(), + A10: *hint[17], + A11: *pr.curveF.Zero(), + } + + if Q.Lines == nil { + Qlines := pr.computeLines(&Q.P) + Q.Lines = &Qlines + } + lines := *Q.Lines + + res, err := pr.millerLoopLines( + []*G1Affine{P}, + []lineEvaluations{lines}, + residueWitnessInv, + false, + ) + if err != nil { + return nil + } + res = pr.Ext12.Conjugate(res) + + // multiply by previous multi-Miller function + res = pr.Ext12.Mul(res, previous) + + // Check that: MillerLoop(P,Q) * scalingFactor * residueWitnessInv^(p-x₀) == 1 + // where u=-0xd201000000010000 is the BLS12-381 seed, and residueWitnessInv, + // scalingFactor from the hint. + // Note that res is already MillerLoop(P,Q) * residueWitnessInv^{-x₀} since + // we initialized the Miller loop accumulator with residueWitnessInv. + // So we only need to check that: + // res * scalingFactor * residueWitnessInv^p == 1 + res = pr.Ext12.Mul(res, &scalingFactor) + t0 := pr.Frobenius(residueWitnessInv) + res = pr.Ext12.Mul(res, t0) + + return res + +} + +// IsMillerLoopAndFinalExpOne computes the Miller loop between P and Q, +// multiplies it in 𝔽p¹² by previous and returns a boolean indicating if +// the result lies in the same equivalence class as the reduced pairing +// purported to be 1. +// +// This method is needed for evmprecompiles/ecpair. +// +// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf +func (pr Pairing) IsMillerLoopAndFinalExpOne(P *G1Affine, Q *G2Affine, previous *GTEl) frontend.Variable { + t2 := pr.millerLoopAndFinalExpResult(P, Q, previous) + + res := pr.IsEqual(t2, pr.Ext12.One()) + return res +} diff --git a/std/evmprecompiles/11-blsadd.go b/std/evmprecompiles/11-blsadd.go new file mode 100644 index 0000000000..404d1c3ad6 --- /dev/null +++ b/std/evmprecompiles/11-blsadd.go @@ -0,0 +1,21 @@ +package evmprecompiles + +import ( + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" + "github.com/consensys/gnark/std/math/emulated" +) + +// ECAddBLS implements [BLS12_G1ADD] precompile contract at address 0x0b. +// +// [BLS12_G1ADD]: https://eips.ethereum.org/EIPS/eip-2537 +func ECAddBLS(api frontend.API, P, Q *sw_emulated.AffinePoint[emulated.BLS12381Fp]) *sw_emulated.AffinePoint[emulated.BLS12381Fp] { + curve, err := sw_emulated.New[emulated.BLS12381Fp, emulated.BLS12381Fr](api, sw_emulated.GetBLS12381Params()) + if err != nil { + panic(err) + } + // Check that P and Q are on the curve (done in the zkEVM ⚠️ ) + // We use AddUnified because P can be equal to Q, -Q and either or both can be (0,0) + res := curve.AddUnified(P, Q) + return res +} diff --git a/std/evmprecompiles/15-blspairing.go b/std/evmprecompiles/15-blspairing.go new file mode 100644 index 0000000000..c0498540ee --- /dev/null +++ b/std/evmprecompiles/15-blspairing.go @@ -0,0 +1,116 @@ +package evmprecompiles + +import ( + "fmt" + + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" +) + +// ECPairBLS implements [BLS12_PAIRING_CHECK] precompile contract at address 0x0f. +// +// To have a fixed-circuit regardless of the number of inputs, we need 2 fixed circuits: +// - MillerLoopAndMul: +// A Miller loop of fixed size 1 followed by a multiplication in 𝔽p¹². +// - MillerLoopAndFinalExpCheck: +// A Miller loop of fixed size 1 followed by a multiplication in 𝔽p¹², and +// a check that the result lies in the same equivalence class as the +// reduced pairing purported to be 1. This check replaces the final +// exponentiation step in-circuit and follows Section 4 of [On Proving +// Pairings] paper by A. Novakovic and L. Eagen. +// +// N.B.: This is a sub-optimal routine but defines a fixed circuit regardless +// of the number of inputs. We can extend this routine to handle a 2-by-2 +// logic but we prefer a minimal number of circuits (2). +// +// See the methods [ECPairMillerLoopAndMul] and [ECPairMillerLoopAndFinalExpCheck] for the fixed circuits. +// See the method [ECPairIsOnG2] for the check that Qᵢ are on G2. +// +// [BLS12_PAIRING_CHECK]: https://eips.ethereum.org/EIPS/eip-2537 +// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf +func ECPairBLS(api frontend.API, P []*sw_bls12381.G1Affine, Q []*sw_bls12381.G2Affine) { + if len(P) != len(Q) { + panic("P and Q length mismatch") + } + if len(P) < 2 { + panic("invalid multipairing size bound") + } + n := len(P) + pair, err := sw_bls12381.NewPairing(api) + if err != nil { + panic(err) + } + for i := 0; i < n; i++ { + // 1- Check that Pᵢ are on G1 + pair.AssertIsOnG1(P[i]) + // 2- Check that Qᵢ are on G2 + // TODO: @yelousni include this check in `computeLines` + pair.AssertIsOnG2(Q[i]) + } + + // 3- Check that ∏ᵢ e(Pᵢ, Qᵢ) == 1 + ml := pair.Ext12.One() + for i := 0; i < n-1; i++ { + // fixed circuit 1 + ml, err = pair.MillerLoopAndMul(P[i], Q[i], ml) + if err != nil { + panic(err) + } + } + + // fixed circuit 2 + pair.AssertMillerLoopAndFinalExpIsOne(P[n-1], Q[n-1], ml) +} + +// ECPairBLSIsOnG1 implements the fixed circuit for checking G1 membership and non-membership. +func ECPairBLSIsOnG1(api frontend.API, Q *sw_bls12381.G1Affine, expectedIsOnG1 frontend.Variable) error { + pairing, err := sw_bls12381.NewPairing(api) + if err != nil { + return err + } + isOnG1 := pairing.IsOnG1(Q) + api.AssertIsEqual(expectedIsOnG1, isOnG1) + return nil +} + +// ECPairBLSIsOnG2 implements the fixed circuit for checking G2 membership and non-membership. +func ECPairBLSIsOnG2(api frontend.API, Q *sw_bls12381.G2Affine, expectedIsOnG2 frontend.Variable) error { + pairing, err := sw_bls12381.NewPairing(api) + if err != nil { + return err + } + isOnG2 := pairing.IsOnG2(Q) + api.AssertIsEqual(expectedIsOnG2, isOnG2) + return nil +} + +// ECPairMillerLoopAndMul implements the fixed circuit for a Miller loop of +// fixed size 1 followed by a multiplication with an accumulator in 𝔽p¹². It +// asserts that the result corresponds to the expected result. +func ECPairBLSMillerLoopAndMul(api frontend.API, accumulator *sw_bls12381.GTEl, P *sw_bls12381.G1Affine, Q *sw_bls12381.G2Affine, expected *sw_bls12381.GTEl) error { + pairing, err := sw_bls12381.NewPairing(api) + if err != nil { + return fmt.Errorf("new pairing: %w", err) + } + ml, err := pairing.MillerLoopAndMul(P, Q, accumulator) + if err != nil { + return fmt.Errorf("miller loop and mul: %w", err) + } + pairing.AssertIsEqual(expected, ml) + return nil +} + +// ECPairMillerLoopAndFinalExpCheck implements the fixed circuit for a Miller +// loop of fixed size 1 followed by a multiplication with an accumulator in +// 𝔽p¹², and a check that the result corresponds to the expected result. +func ECPairBLSMillerLoopAndFinalExpCheck(api frontend.API, accumulator *sw_bls12381.GTEl, P *sw_bls12381.G1Affine, Q *sw_bls12381.G2Affine, expectedIsSuccess frontend.Variable) error { + api.AssertIsBoolean(expectedIsSuccess) + pairing, err := sw_bls12381.NewPairing(api) + if err != nil { + return fmt.Errorf("new pairing: %w", err) + } + + isSuccess := pairing.IsMillerLoopAndFinalExpOne(P, Q, accumulator) + api.AssertIsEqual(expectedIsSuccess, isSuccess) + return nil +} diff --git a/std/evmprecompiles/bls_test.go b/std/evmprecompiles/bls_test.go new file mode 100644 index 0000000000..df02e9aa04 --- /dev/null +++ b/std/evmprecompiles/bls_test.go @@ -0,0 +1,136 @@ +package evmprecompiles + +import ( + "fmt" + "math/big" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" + "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" + "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/test" +) + +type ecaddBLSCircuit struct { + X0 sw_emulated.AffinePoint[emulated.BLS12381Fp] + X1 sw_emulated.AffinePoint[emulated.BLS12381Fp] + Expected sw_emulated.AffinePoint[emulated.BLS12381Fp] +} + +func (c *ecaddBLSCircuit) Define(api frontend.API) error { + curve, err := sw_emulated.New[emulated.BLS12381Fp, emulated.BLS12381Fr](api, sw_emulated.GetBLS12381Params()) + if err != nil { + return err + } + res := ECAddBLS(api, &c.X0, &c.X1) + curve.AssertIsEqual(res, &c.Expected) + return nil +} + +func testRoutineECAddBLS() (circ, wit frontend.Circuit) { + _, _, G, _ := bls12381.Generators() + var u, v fr.Element + u.SetRandom() + v.SetRandom() + var P, Q bls12381.G1Affine + P.ScalarMultiplication(&G, u.BigInt(new(big.Int))) + Q.ScalarMultiplication(&G, v.BigInt(new(big.Int))) + var expected bls12381.G1Affine + expected.Add(&P, &Q) + circuit := ecaddBLSCircuit{} + witness := ecaddBLSCircuit{ + X0: sw_emulated.AffinePoint[emulated.BLS12381Fp]{ + X: emulated.ValueOf[emulated.BLS12381Fp](P.X), + Y: emulated.ValueOf[emulated.BLS12381Fp](P.Y), + }, + X1: sw_emulated.AffinePoint[emulated.BLS12381Fp]{ + X: emulated.ValueOf[emulated.BLS12381Fp](Q.X), + Y: emulated.ValueOf[emulated.BLS12381Fp](Q.Y), + }, + Expected: sw_emulated.AffinePoint[emulated.BLS12381Fp]{ + X: emulated.ValueOf[emulated.BLS12381Fp](expected.X), + Y: emulated.ValueOf[emulated.BLS12381Fp](expected.Y), + }, + } + return &circuit, &witness +} + +func TestECAddBLSCircuitShort(t *testing.T) { + assert := test.NewAssert(t) + circuit, witness := testRoutineECAdd() + err := test.IsSolved(circuit, witness, ecc.BLS12_381.ScalarField()) + assert.NoError(err) +} + +func TestECAddBLSCircuitFull(t *testing.T) { + assert := test.NewAssert(t) + circuit, witness := testRoutineECAdd() + assert.CheckCircuit(circuit, test.WithValidAssignment(witness)) +} + +type ecPairBLSBatchCircuit struct { + P sw_bls12381.G1Affine + NP sw_bls12381.G1Affine + DP sw_bls12381.G1Affine + Q sw_bls12381.G2Affine + n int +} + +func (c *ecPairBLSBatchCircuit) Define(api frontend.API) error { + Q := make([]*sw_bls12381.G2Affine, c.n) + for i := range Q { + Q[i] = &c.Q + } + switch c.n { + case 2: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP}, Q) + case 3: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.NP, &c.NP, &c.DP}, Q) + case 4: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP}, Q) + case 5: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.NP, &c.NP, &c.DP}, Q) + case 6: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP}, Q) + case 7: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.NP, &c.NP, &c.DP}, Q) + case 8: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP}, Q) + case 9: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP, &c.NP, &c.NP, &c.DP}, Q) + default: + return fmt.Errorf("not handled %d", c.n) + } + return nil +} + +func TestECPairBLSBLSMulBatch(t *testing.T) { + assert := test.NewAssert(t) + _, _, p, q := bls12381.Generators() + + var u, v fr.Element + u.SetRandom() + v.SetRandom() + + p.ScalarMultiplication(&p, u.BigInt(new(big.Int))) + q.ScalarMultiplication(&q, v.BigInt(new(big.Int))) + + var dp, np bls12381.G1Affine + dp.Double(&p) + np.Neg(&p) + + for i := 2; i < 10; i++ { + err := test.IsSolved(&ecPairBLSBatchCircuit{n: i}, &ecPairBLSBatchCircuit{ + n: i, + P: sw_bls12381.NewG1Affine(p), + NP: sw_bls12381.NewG1Affine(np), + DP: sw_bls12381.NewG1Affine(dp), + Q: sw_bls12381.NewG2Affine(q), + }, ecc.BLS12_381.ScalarField()) + assert.NoError(err) + } +} From 5c58de379a1522f46c158ef45b3ccff84013fbbd Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 13 Mar 2025 18:17:18 -0400 Subject: [PATCH 011/105] feat(pectra/precompiles): BLS12_G2ADD --- std/algebra/emulated/sw_bls12381/g2.go | 67 +++++++++++++++++++ .../{11-blsadd.go => 11-blsg1add.go} | 0 std/evmprecompiles/13-blsg2add.go | 18 +++++ std/evmprecompiles/bls_test.go | 46 +++++++++++++ 4 files changed, 131 insertions(+) rename std/evmprecompiles/{11-blsadd.go => 11-blsg1add.go} (100%) create mode 100644 std/evmprecompiles/13-blsg2add.go diff --git a/std/algebra/emulated/sw_bls12381/g2.go b/std/algebra/emulated/sw_bls12381/g2.go index 5cd2f22289..6268569c3a 100644 --- a/std/algebra/emulated/sw_bls12381/g2.go +++ b/std/algebra/emulated/sw_bls12381/g2.go @@ -53,6 +53,7 @@ func NewG2(api frontend.API) *G2 { A1: emulated.ValueOf[BaseField]("1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257"), } return &G2{ + api: api, fp: fp, Ext2: fields_bls12381.NewExt2(api), w: &w, @@ -120,6 +121,61 @@ func (g2 *G2) scalarMulBySeed(q *G2Affine) *G2Affine { return g2.neg(z) } +// AddUnified adds p and q and returns it. It doesn't modify p nor q. +// +// ✅ p can be equal to q, and either or both can be (0,0). +// ([0,0],[0,0]) is not on the twist but we conventionally take it as the +// neutral/infinity point as per the [EVM]. +// +// It uses the unified formulas of Brier and Joye ([[BriJoy02]] (Corollary 1)). +// +// [BriJoy02]: https://link.springer.com/content/pdf/10.1007/3-540-45664-3_24.pdf +// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf +func (g2 *G2) AddUnified(p, q *G2Affine) *G2Affine { + + // selector1 = 1 when p is ([0,0],[0,0]) and 0 otherwise + selector1 := g2.api.And(g2.Ext2.IsZero(&p.P.X), g2.Ext2.IsZero(&p.P.Y)) + // selector2 = 1 when q is ([0,0],[0,0]) and 0 otherwise + selector2 := g2.api.And(g2.Ext2.IsZero(&q.P.X), g2.Ext2.IsZero(&q.P.Y)) + // λ = ((p.x+q.x)² - p.x*q.x + a)/(p.y + q.y) + pxqx := g2.Mul(&p.P.X, &q.P.X) + pxplusqx := g2.Add(&p.P.X, &q.P.X) + num := g2.Mul(pxplusqx, pxplusqx) + num = g2.Sub(num, pxqx) + denum := g2.Add(&p.P.Y, &q.P.Y) + // if p.y + q.y = 0, assign dummy 1 to denum and continue + selector3 := g2.IsZero(denum) + denum = g2.Ext2.Select(selector3, g2.One(), denum) + λ := g2.DivUnchecked(num, denum) + + // x = λ^2 - p.x - q.x + xr := g2.Mul(λ, λ) + xr = g2.Sub(xr, pxplusqx) + + // y = λ(p.x - xr) - p.y + yr := g2.Sub(&p.P.X, xr) + yr = g2.Mul(yr, λ) + yr = g2.Sub(yr, &p.P.Y) + result := G2Affine{ + P: g2AffP{X: *xr, Y: *yr}, + Lines: nil, + } + + zero := g2.Ext2.Zero() + infinity := G2Affine{ + P: g2AffP{X: *zero, Y: *zero}, + Lines: nil, + } + // if p=([0,0],[0,0]) return q + result = *g2.Select(selector1, q, &result) + // if q=([0,0],[0,0]) return p + result = *g2.Select(selector2, p, &result) + // if p.y + q.y = 0, return ([0,0],[0,0]) + result = *g2.Select(selector3, &infinity, &result) + + return &result +} + func (g2 G2) add(p, q *G2Affine) *G2Affine { mone := g2.fp.NewElement(-1) @@ -277,6 +333,17 @@ func (g2 G2) doubleAndAdd(p, q *G2Affine) *G2Affine { } } +// Select selects between p and q given the selector b. If b == 1, then returns +// p and q otherwise. +func (g2 *G2) Select(b frontend.Variable, p, q *G2Affine) *G2Affine { + x := g2.Ext2.Select(b, &p.P.X, &q.P.X) + y := g2.Ext2.Select(b, &p.P.Y, &q.P.Y) + return &G2Affine{ + P: g2AffP{X: *x, Y: *y}, + Lines: nil, + } +} + // AssertIsEqual asserts that p and q are the same point. func (g2 *G2) AssertIsEqual(p, q *G2Affine) { g2.Ext2.AssertIsEqual(&p.P.X, &q.P.X) diff --git a/std/evmprecompiles/11-blsadd.go b/std/evmprecompiles/11-blsg1add.go similarity index 100% rename from std/evmprecompiles/11-blsadd.go rename to std/evmprecompiles/11-blsg1add.go diff --git a/std/evmprecompiles/13-blsg2add.go b/std/evmprecompiles/13-blsg2add.go new file mode 100644 index 0000000000..a115c43364 --- /dev/null +++ b/std/evmprecompiles/13-blsg2add.go @@ -0,0 +1,18 @@ +package evmprecompiles + +import ( + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" +) + +// ECAddG2BLS implements [BLS12_G2ADD] precompile contract at address 0x0d. +// +// [BLS12_G2ADD]: https://eips.ethereum.org/EIPS/eip-2537 +func ECAddG2BLS(api frontend.API, P, Q *sw_bls12381.G2Affine) *sw_bls12381.G2Affine { + g2 := sw_bls12381.NewG2(api) + // TODO @yelhousni: Check that P and Q are on the curve + + // We use AddUnified because P can be equal to Q, -Q and either or both can be (0,0) + res := g2.AddUnified(P, Q) + return res +} diff --git a/std/evmprecompiles/bls_test.go b/std/evmprecompiles/bls_test.go index df02e9aa04..efc1d4e12d 100644 --- a/std/evmprecompiles/bls_test.go +++ b/std/evmprecompiles/bls_test.go @@ -134,3 +134,49 @@ func TestECPairBLSBLSMulBatch(t *testing.T) { assert.NoError(err) } } + +// --- +type ecaddG2BLSCircuit struct { + X0 sw_bls12381.G2Affine + X1 sw_bls12381.G2Affine + Expected sw_bls12381.G2Affine +} + +func (c *ecaddG2BLSCircuit) Define(api frontend.API) error { + g2 := sw_bls12381.NewG2(api) + res := ECAddG2BLS(api, &c.X0, &c.X1) + g2.AssertIsEqual(res, &c.Expected) + return nil +} + +func testRoutineECAddG2BLS() (circ, wit frontend.Circuit) { + _, _, _, G := bls12381.Generators() + var u, v fr.Element + u.SetRandom() + v.SetRandom() + var P, Q bls12381.G2Affine + P.ScalarMultiplication(&G, u.BigInt(new(big.Int))) + Q.ScalarMultiplication(&G, v.BigInt(new(big.Int))) + var expected bls12381.G2Affine + expected.Add(&P, &Q) + circuit := ecaddG2BLSCircuit{} + witness := ecaddG2BLSCircuit{ + X0: sw_bls12381.NewG2Affine(P), + X1: sw_bls12381.NewG2Affine(Q), + Expected: sw_bls12381.NewG2Affine(expected), + } + return &circuit, &witness +} + +func TestECAddG2BLSCircuitShort(t *testing.T) { + assert := test.NewAssert(t) + circuit, witness := testRoutineECAddG2BLS() + err := test.IsSolved(circuit, witness, ecc.BLS12_381.ScalarField()) + assert.NoError(err) +} + +func TestECAddG2BLSCircuitFull(t *testing.T) { + assert := test.NewAssert(t) + circuit, witness := testRoutineECAddG2BLS() + assert.CheckCircuit(circuit, test.WithValidAssignment(witness)) +} From 90ad5954c433da62a01bcd198a99d253a869f717 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Thu, 13 Mar 2025 19:20:34 -0400 Subject: [PATCH 012/105] perf(pectra/precompiles): include G2 membership in BLS12_PAIR --- std/algebra/emulated/sw_bls12381/g2.go | 9 ++ .../emulated/sw_bls12381/precomputations.go | 16 +++- std/evmprecompiles/15-blspairing.go | 4 +- std/evmprecompiles/bls_test.go | 90 +++++++++---------- 4 files changed, 68 insertions(+), 51 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/g2.go b/std/algebra/emulated/sw_bls12381/g2.go index 6268569c3a..fbfae61106 100644 --- a/std/algebra/emulated/sw_bls12381/g2.go +++ b/std/algebra/emulated/sw_bls12381/g2.go @@ -71,6 +71,15 @@ func NewG2Affine(v bls12381.G2Affine) G2Affine { // NewG2AffineFixed returns witness of v with precomputations for efficient // pairing computation. func NewG2AffineFixed(v bls12381.G2Affine) G2Affine { + if !v.IsInSubGroup() { + // for the pairing check we check that G2 point is already in the + // subgroup when we compute the lines in circuit. However, when the + // point is given as a constant, then we already precompute the lines at + // circuit compile time without explicitly checking the G2 membership. + // So, we need to check that the point is in the subgroup before we + // compute the lines. + panic("given point is not in the G2 subgroup") + } lines := precomputeLines(v) return G2Affine{ P: newG2AffP(v), diff --git a/std/algebra/emulated/sw_bls12381/precomputations.go b/std/algebra/emulated/sw_bls12381/precomputations.go index a07efc79cd..5049816f15 100644 --- a/std/algebra/emulated/sw_bls12381/precomputations.go +++ b/std/algebra/emulated/sw_bls12381/precomputations.go @@ -31,17 +31,29 @@ func precomputeLines(Q bls12381.G2Affine) lineEvaluations { func (p *Pairing) computeLines(Q *g2AffP) lineEvaluations { + // check Q is on curve + Qaff := G2Affine{P: *Q, Lines: nil} + p.IsOnTwist(&Qaff) + var cLines lineEvaluations Qacc := Q n := len(loopCounter) Qacc, cLines[0][n-2], cLines[1][n-2] = p.tripleStep(Qacc) - for i := n - 3; i >= 1; i-- { + for i := n - 3; i >= 0; i-- { if loopCounter[i] == 0 { Qacc, cLines[0][i] = p.doubleStep(Qacc) } else { Qacc, cLines[0][i], cLines[1][i] = p.doubleAndAddStep(Qacc, Q) } } - cLines[0][0] = p.tangentCompute(Qacc) + + // Check that Q is on G2 subgroup: + // [r]Q == 0 <==> ψ(Q) == [x₀]Q + // This test is equivalent to [AssertIsOnG2]. + // + // At this point Qacc = [x₀]Q. + psiQ := p.g2.psi(&Qaff) + p.g2.AssertIsEqual(p.g2.neg(&G2Affine{P: *Qacc, Lines: nil}), psiQ) + return cLines } diff --git a/std/evmprecompiles/15-blspairing.go b/std/evmprecompiles/15-blspairing.go index c0498540ee..bfce977154 100644 --- a/std/evmprecompiles/15-blspairing.go +++ b/std/evmprecompiles/15-blspairing.go @@ -43,9 +43,7 @@ func ECPairBLS(api frontend.API, P []*sw_bls12381.G1Affine, Q []*sw_bls12381.G2A for i := 0; i < n; i++ { // 1- Check that Pᵢ are on G1 pair.AssertIsOnG1(P[i]) - // 2- Check that Qᵢ are on G2 - // TODO: @yelousni include this check in `computeLines` - pair.AssertIsOnG2(Q[i]) + // 2- Check that Qᵢ are on G2 (done in `computeLines` in `MillerLoopAndMul` and `MillerLoopAndFinalExpCheck) } // 3- Check that ∏ᵢ e(Pᵢ, Qᵢ) == 1 diff --git a/std/evmprecompiles/bls_test.go b/std/evmprecompiles/bls_test.go index efc1d4e12d..d922bfee13 100644 --- a/std/evmprecompiles/bls_test.go +++ b/std/evmprecompiles/bls_test.go @@ -66,6 +66,50 @@ func TestECAddBLSCircuitShort(t *testing.T) { assert.NoError(err) } +type ecaddG2BLSCircuit struct { + X0 sw_bls12381.G2Affine + X1 sw_bls12381.G2Affine + Expected sw_bls12381.G2Affine +} + +func (c *ecaddG2BLSCircuit) Define(api frontend.API) error { + g2 := sw_bls12381.NewG2(api) + res := ECAddG2BLS(api, &c.X0, &c.X1) + g2.AssertIsEqual(res, &c.Expected) + return nil +} + +func testRoutineECAddG2BLS() (circ, wit frontend.Circuit) { + _, _, _, G := bls12381.Generators() + var u, v fr.Element + u.SetRandom() + v.SetRandom() + var P, Q bls12381.G2Affine + P.ScalarMultiplication(&G, u.BigInt(new(big.Int))) + Q.ScalarMultiplication(&G, v.BigInt(new(big.Int))) + var expected bls12381.G2Affine + expected.Add(&P, &Q) + circuit := ecaddG2BLSCircuit{} + witness := ecaddG2BLSCircuit{ + X0: sw_bls12381.NewG2Affine(P), + X1: sw_bls12381.NewG2Affine(Q), + Expected: sw_bls12381.NewG2Affine(expected), + } + return &circuit, &witness +} + +func TestECAddG2BLSCircuitShort(t *testing.T) { + assert := test.NewAssert(t) + circuit, witness := testRoutineECAddG2BLS() + err := test.IsSolved(circuit, witness, ecc.BLS12_381.ScalarField()) + assert.NoError(err) +} + +func TestECAddG2BLSCircuitFull(t *testing.T) { + assert := test.NewAssert(t) + circuit, witness := testRoutineECAddG2BLS() + assert.CheckCircuit(circuit, test.WithValidAssignment(witness)) +} func TestECAddBLSCircuitFull(t *testing.T) { assert := test.NewAssert(t) circuit, witness := testRoutineECAdd() @@ -134,49 +178,3 @@ func TestECPairBLSBLSMulBatch(t *testing.T) { assert.NoError(err) } } - -// --- -type ecaddG2BLSCircuit struct { - X0 sw_bls12381.G2Affine - X1 sw_bls12381.G2Affine - Expected sw_bls12381.G2Affine -} - -func (c *ecaddG2BLSCircuit) Define(api frontend.API) error { - g2 := sw_bls12381.NewG2(api) - res := ECAddG2BLS(api, &c.X0, &c.X1) - g2.AssertIsEqual(res, &c.Expected) - return nil -} - -func testRoutineECAddG2BLS() (circ, wit frontend.Circuit) { - _, _, _, G := bls12381.Generators() - var u, v fr.Element - u.SetRandom() - v.SetRandom() - var P, Q bls12381.G2Affine - P.ScalarMultiplication(&G, u.BigInt(new(big.Int))) - Q.ScalarMultiplication(&G, v.BigInt(new(big.Int))) - var expected bls12381.G2Affine - expected.Add(&P, &Q) - circuit := ecaddG2BLSCircuit{} - witness := ecaddG2BLSCircuit{ - X0: sw_bls12381.NewG2Affine(P), - X1: sw_bls12381.NewG2Affine(Q), - Expected: sw_bls12381.NewG2Affine(expected), - } - return &circuit, &witness -} - -func TestECAddG2BLSCircuitShort(t *testing.T) { - assert := test.NewAssert(t) - circuit, witness := testRoutineECAddG2BLS() - err := test.IsSolved(circuit, witness, ecc.BLS12_381.ScalarField()) - assert.NoError(err) -} - -func TestECAddG2BLSCircuitFull(t *testing.T) { - assert := test.NewAssert(t) - circuit, witness := testRoutineECAddG2BLS() - assert.CheckCircuit(circuit, test.WithValidAssignment(witness)) -} From b8f9004490c972c58ece991c700de82f37b86f35 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 14 Mar 2025 11:02:16 -0400 Subject: [PATCH 013/105] test: update stats --- internal/stats/latest_stats.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/stats/latest_stats.csv b/internal/stats/latest_stats.csv index a565938131..bd212d6882 100644 --- a/internal/stats/latest_stats.csv +++ b/internal/stats/latest_stats.csv @@ -153,14 +153,14 @@ pairing_bls12377,bls24_315,plonk,0,0 pairing_bls12377,bls24_317,plonk,0,0 pairing_bls12377,bw6_761,plonk,51280,51280 pairing_bls12377,bw6_633,plonk,0,0 -pairing_bls12381,bn254,groth16,947528,1567714 +pairing_bls12381,bn254,groth16,949444,1570802 pairing_bls12381,bls12_377,groth16,0,0 pairing_bls12381,bls12_381,groth16,0,0 pairing_bls12381,bls24_315,groth16,0,0 pairing_bls12381,bls24_317,groth16,0,0 pairing_bls12381,bw6_761,groth16,0,0 pairing_bls12381,bw6_633,groth16,0,0 -pairing_bls12381,bn254,plonk,3642638,3233378 +pairing_bls12381,bn254,plonk,3649555,3239775 pairing_bls12381,bls12_377,plonk,0,0 pairing_bls12381,bls12_381,plonk,0,0 pairing_bls12381,bls24_315,plonk,0,0 From e748c1f8e66ea444230bc612b2e4a1860ae1b419 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 14 Mar 2025 12:55:14 -0400 Subject: [PATCH 014/105] feat: add G2 memebrship to BLS12_G2ADD --- std/algebra/emulated/sw_bls12381/g2.go | 37 ++++++++++++++ std/algebra/emulated/sw_bls12381/pairing.go | 53 ++------------------- std/evmprecompiles/06-bnadd.go | 2 +- std/evmprecompiles/08-bnpairing.go | 2 +- std/evmprecompiles/11-blsg1add.go | 2 +- std/evmprecompiles/13-blsg2add.go | 4 +- std/evmprecompiles/15-blspairing.go | 16 +------ 7 files changed, 48 insertions(+), 68 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/g2.go b/std/algebra/emulated/sw_bls12381/g2.go index fbfae61106..2a20cbee1d 100644 --- a/std/algebra/emulated/sw_bls12381/g2.go +++ b/std/algebra/emulated/sw_bls12381/g2.go @@ -342,6 +342,43 @@ func (g2 G2) doubleAndAdd(p, q *G2Affine) *G2Affine { } } +func (g2 *G2) computeTwistEquation(Q *G2Affine) (left, right *fields_bls12381.E2) { + // Twist: Y² == X³ + aX + b, where a=0 and b=4(1+u) + // (X,Y) ∈ {Y² == X³ + aX + b} U (0,0) + bTwist := fields_bls12381.E2{ + A0: emulated.ValueOf[BaseField]("4"), + A1: emulated.ValueOf[BaseField]("4"), + } + // if Q=(0,0) we assign b=0 otherwise 4(1+u), and continue + selector := g2.api.And(g2.Ext2.IsZero(&Q.P.X), g2.Ext2.IsZero(&Q.P.Y)) + b := g2.Ext2.Select(selector, g2.Ext2.Zero(), &bTwist) + + left = g2.Ext2.Square(&Q.P.Y) + right = g2.Ext2.Square(&Q.P.X) + right = g2.Ext2.Mul(right, &Q.P.X) + right = g2.Ext2.Add(right, b) + return left, right +} + +func (g2 *G2) AssertIsOnTwist(Q *G2Affine) { + left, right := g2.computeTwistEquation(Q) + g2.Ext2.AssertIsEqual(left, right) +} + +func (g2 *G2) AssertIsOnG2(Q *G2Affine) { + // 1- Check Q is on the curve + g2.AssertIsOnTwist(Q) + + // 2- Check Q has the right subgroup order + // [x₀]Q + xQ := g2.scalarMulBySeed(Q) + // ψ(Q) + psiQ := g2.psi(Q) + + // [r]Q == 0 <==> ψ(Q) == [x₀]Q + g2.AssertIsEqual(xQ, psiQ) +} + // Select selects between p and q given the selector b. If b == 1, then returns // p and q otherwise. func (g2 *G2) Select(b frontend.Variable, p, q *G2Affine) *G2Affine { diff --git a/std/algebra/emulated/sw_bls12381/pairing.go b/std/algebra/emulated/sw_bls12381/pairing.go index 6be722ac01..d3a1133ff8 100644 --- a/std/algebra/emulated/sw_bls12381/pairing.go +++ b/std/algebra/emulated/sw_bls12381/pairing.go @@ -21,7 +21,6 @@ type Pairing struct { curve *sw_emulated.Curve[BaseField, ScalarField] g2 *G2 g1 *G1 - bTwist *fields_bls12381.E2 } type baseEl = emulated.Element[BaseField] @@ -62,10 +61,6 @@ func NewPairing(api frontend.API) (*Pairing, error) { if err != nil { return nil, fmt.Errorf("new curve: %w", err) } - bTwist := fields_bls12381.E2{ - A0: emulated.ValueOf[BaseField]("4"), - A1: emulated.ValueOf[BaseField]("4"), - } g1, err := NewG1(api) if err != nil { return nil, fmt.Errorf("new G1 struct: %w", err) @@ -78,7 +73,6 @@ func NewPairing(api frontend.API) (*Pairing, error) { curve: curve, g1: g1, g2: NewG2(api), - bTwist: &bTwist, }, nil } @@ -216,29 +210,13 @@ func (pr Pairing) IsOnCurve(P *G1Affine) frontend.Variable { return pr.curveF.IsZero(diff) } -func (pr Pairing) computeTwistEquation(Q *G2Affine) (left, right *fields_bls12381.E2) { - // Twist: Y² == X³ + aX + b, where a=0 and b=4(1+u) - // (X,Y) ∈ {Y² == X³ + aX + b} U (0,0) - - // if Q=(0,0) we assign b=0 otherwise 4(1+u), and continue - selector := pr.api.And(pr.Ext2.IsZero(&Q.P.X), pr.Ext2.IsZero(&Q.P.Y)) - b := pr.Ext2.Select(selector, pr.Ext2.Zero(), pr.bTwist) - - left = pr.Ext2.Square(&Q.P.Y) - right = pr.Ext2.Square(&Q.P.X) - right = pr.Ext2.Mul(right, &Q.P.X) - right = pr.Ext2.Add(right, b) - return left, right -} - func (pr Pairing) AssertIsOnTwist(Q *G2Affine) { - left, right := pr.computeTwistEquation(Q) - pr.Ext2.AssertIsEqual(left, right) + pr.g2.AssertIsOnTwist(Q) } // IsOnTwist returns a boolean indicating if the G2 point is in the twist. func (pr Pairing) IsOnTwist(Q *G2Affine) frontend.Variable { - left, right := pr.computeTwistEquation(Q) + left, right := pr.g2.computeTwistEquation(Q) diff := pr.Ext2.Sub(left, right) return pr.Ext2.IsZero(diff) } @@ -257,33 +235,8 @@ func (pr Pairing) AssertIsOnG1(P *G1Affine) { pr.curve.AssertIsEqual(_P, P) } -// IsOnG1 returns a boolean indicating if the G1 point is in the subgroup. The -// method assumes that the point is already on the curve. Call -// [Pairing.AssertIsOnTwist] before to ensure point is on the curve. -func (pr Pairing) IsOnG1(P *G1Affine) frontend.Variable { - // 1 - is P on curve - isOnCurve := pr.IsOnCurve(P) - // 2 - is P in the subgroup - phiP := pr.g1.phi(P) - _P := pr.g1.scalarMulBySeedSquare(phiP) - _P = pr.curve.Neg(_P) - isInSubgroup := pr.g1.IsEqual(_P, phiP) - - return pr.api.And(isOnCurve, isInSubgroup) -} - func (pr Pairing) AssertIsOnG2(Q *G2Affine) { - // 1- Check Q is on the curve - pr.AssertIsOnTwist(Q) - - // 2- Check Q has the right subgroup order - // [x₀]Q - xQ := pr.g2.scalarMulBySeed(Q) - // ψ(Q) - psiQ := pr.g2.psi(Q) - - // [r]Q == 0 <==> ψ(Q) == [x₀]Q - pr.g2.AssertIsEqual(xQ, psiQ) + pr.g2.AssertIsOnG2(Q) } // IsOnG2 returns a boolean indicating if the G2 point is in the subgroup. The diff --git a/std/evmprecompiles/06-bnadd.go b/std/evmprecompiles/06-bnadd.go index ff6c397fc9..242d5a9859 100644 --- a/std/evmprecompiles/06-bnadd.go +++ b/std/evmprecompiles/06-bnadd.go @@ -14,7 +14,7 @@ func ECAdd(api frontend.API, P, Q *sw_emulated.AffinePoint[emulated.BN254Fp]) *s if err != nil { panic(err) } - // Check that P and Q are on the curve (done in the zkEVM ⚠️ ) + // Check that P and Q are on G1 (done in the zkEVM ⚠️ ) // We use AddUnified because P can be equal to Q, -Q and either or both can be (0,0) res := curve.AddUnified(P, Q) return res diff --git a/std/evmprecompiles/08-bnpairing.go b/std/evmprecompiles/08-bnpairing.go index f4ff6b1b8d..f800e1c2b4 100644 --- a/std/evmprecompiles/08-bnpairing.go +++ b/std/evmprecompiles/08-bnpairing.go @@ -40,7 +40,7 @@ func ECPair(api frontend.API, P []*sw_bn254.G1Affine, Q []*sw_bn254.G2Affine) { if err != nil { panic(err) } - // 1- Check that Pᵢ are on G1 (done in the zkEVM ⚠️ + // 1- Check that Pᵢ are on G1 (done in the zkEVM ⚠️) // 2- Check that Qᵢ are on G2 (done in `computeLines` in `MillerLoopAndMul` and `MillerLoopAndFinalExpCheck) // 3- Check that ∏ᵢ e(Pᵢ, Qᵢ) == 1 diff --git a/std/evmprecompiles/11-blsg1add.go b/std/evmprecompiles/11-blsg1add.go index 404d1c3ad6..95aa99ecb1 100644 --- a/std/evmprecompiles/11-blsg1add.go +++ b/std/evmprecompiles/11-blsg1add.go @@ -14,7 +14,7 @@ func ECAddBLS(api frontend.API, P, Q *sw_emulated.AffinePoint[emulated.BLS12381F if err != nil { panic(err) } - // Check that P and Q are on the curve (done in the zkEVM ⚠️ ) + // Check that P and Q are on G1 (done in the zkEVM ⚠️ ) // We use AddUnified because P can be equal to Q, -Q and either or both can be (0,0) res := curve.AddUnified(P, Q) return res diff --git a/std/evmprecompiles/13-blsg2add.go b/std/evmprecompiles/13-blsg2add.go index a115c43364..100e784f82 100644 --- a/std/evmprecompiles/13-blsg2add.go +++ b/std/evmprecompiles/13-blsg2add.go @@ -10,7 +10,9 @@ import ( // [BLS12_G2ADD]: https://eips.ethereum.org/EIPS/eip-2537 func ECAddG2BLS(api frontend.API, P, Q *sw_bls12381.G2Affine) *sw_bls12381.G2Affine { g2 := sw_bls12381.NewG2(api) - // TODO @yelhousni: Check that P and Q are on the curve + // Check that P and Q are on G2 + g2.AssertIsOnG2(P) + g2.AssertIsOnG2(Q) // We use AddUnified because P can be equal to Q, -Q and either or both can be (0,0) res := g2.AddUnified(P, Q) diff --git a/std/evmprecompiles/15-blspairing.go b/std/evmprecompiles/15-blspairing.go index bfce977154..d7db74f31e 100644 --- a/std/evmprecompiles/15-blspairing.go +++ b/std/evmprecompiles/15-blspairing.go @@ -24,7 +24,7 @@ import ( // logic but we prefer a minimal number of circuits (2). // // See the methods [ECPairMillerLoopAndMul] and [ECPairMillerLoopAndFinalExpCheck] for the fixed circuits. -// See the method [ECPairIsOnG2] for the check that Qᵢ are on G2. +// See the method [ECPairBLSIsOnG2] for the check that Qᵢ are on G2. // // [BLS12_PAIRING_CHECK]: https://eips.ethereum.org/EIPS/eip-2537 // [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf @@ -41,8 +41,7 @@ func ECPairBLS(api frontend.API, P []*sw_bls12381.G1Affine, Q []*sw_bls12381.G2A panic(err) } for i := 0; i < n; i++ { - // 1- Check that Pᵢ are on G1 - pair.AssertIsOnG1(P[i]) + // 1- Check that Pᵢ are on G1 (done in the zkEVM ⚠️) // 2- Check that Qᵢ are on G2 (done in `computeLines` in `MillerLoopAndMul` and `MillerLoopAndFinalExpCheck) } @@ -60,17 +59,6 @@ func ECPairBLS(api frontend.API, P []*sw_bls12381.G1Affine, Q []*sw_bls12381.G2A pair.AssertMillerLoopAndFinalExpIsOne(P[n-1], Q[n-1], ml) } -// ECPairBLSIsOnG1 implements the fixed circuit for checking G1 membership and non-membership. -func ECPairBLSIsOnG1(api frontend.API, Q *sw_bls12381.G1Affine, expectedIsOnG1 frontend.Variable) error { - pairing, err := sw_bls12381.NewPairing(api) - if err != nil { - return err - } - isOnG1 := pairing.IsOnG1(Q) - api.AssertIsEqual(expectedIsOnG1, isOnG1) - return nil -} - // ECPairBLSIsOnG2 implements the fixed circuit for checking G2 membership and non-membership. func ECPairBLSIsOnG2(api frontend.API, Q *sw_bls12381.G2Affine, expectedIsOnG2 frontend.Variable) error { pairing, err := sw_bls12381.NewPairing(api) From e5886ea6e41a3236ef486af6d9e3fbac1c5d9971 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 14 Mar 2025 14:51:32 -0400 Subject: [PATCH 015/105] feat(pectra/precompiles): BLS12_G1MSM --- std/evmprecompiles/07-bnmul.go | 2 +- std/evmprecompiles/12-blsg1msm.go | 26 +++++++ std/evmprecompiles/bls_test.go | 121 +++++++++++++++++++++++++++++- 3 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 std/evmprecompiles/12-blsg1msm.go diff --git a/std/evmprecompiles/07-bnmul.go b/std/evmprecompiles/07-bnmul.go index eb1d0889f6..8aa32060d3 100644 --- a/std/evmprecompiles/07-bnmul.go +++ b/std/evmprecompiles/07-bnmul.go @@ -15,7 +15,7 @@ func ECMul(api frontend.API, P *sw_emulated.AffinePoint[emulated.BN254Fp], u *em if err != nil { panic(err) } - // Check that P is on the curve (done in the zkEVM ⚠️ ) + // Check that P is on G1 (done in the zkEVM ⚠️ ) res := curve.ScalarMul(P, u, algopts.WithCompleteArithmetic()) return res } diff --git a/std/evmprecompiles/12-blsg1msm.go b/std/evmprecompiles/12-blsg1msm.go new file mode 100644 index 0000000000..100976fff3 --- /dev/null +++ b/std/evmprecompiles/12-blsg1msm.go @@ -0,0 +1,26 @@ +package evmprecompiles + +import ( + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/algopts" + "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" + "github.com/consensys/gnark/std/math/emulated" +) + +// ECMSMG1BLS implements [BLS12_G1MSM] precompile contract at address 0x0c. +// +// [BLS12_G1MSM]: https://eips.ethereum.org/EIPS/eip-2537 +func ECMSMG1BLS(api frontend.API, P []*sw_emulated.AffinePoint[emulated.BLS12381Fp], s []*emulated.Element[emulated.BLS12381Fr]) *sw_emulated.AffinePoint[emulated.BLS12381Fp] { + curve, err := sw_emulated.New[emulated.BLS12381Fp, emulated.BLS12381Fr](api, sw_emulated.GetBLS12381Params()) + if err != nil { + panic(err) + } + + // Check that all P's are on G1 (done in the zkEVM ⚠️ ) + res, err := curve.MultiScalarMul(P, s, algopts.WithCompleteArithmetic()) + if err != nil { + panic(err) + } + + return res +} diff --git a/std/evmprecompiles/bls_test.go b/std/evmprecompiles/bls_test.go index d922bfee13..75673fa458 100644 --- a/std/evmprecompiles/bls_test.go +++ b/std/evmprecompiles/bls_test.go @@ -8,6 +8,7 @@ import ( "github.com/consensys/gnark-crypto/ecc" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + fr_bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" @@ -61,8 +62,8 @@ func testRoutineECAddBLS() (circ, wit frontend.Circuit) { func TestECAddBLSCircuitShort(t *testing.T) { assert := test.NewAssert(t) - circuit, witness := testRoutineECAdd() - err := test.IsSolved(circuit, witness, ecc.BLS12_381.ScalarField()) + circuit, witness := testRoutineECAddBLS() + err := test.IsSolved(circuit, witness, ecc.BN254.ScalarField()) assert.NoError(err) } @@ -101,7 +102,7 @@ func testRoutineECAddG2BLS() (circ, wit frontend.Circuit) { func TestECAddG2BLSCircuitShort(t *testing.T) { assert := test.NewAssert(t) circuit, witness := testRoutineECAddG2BLS() - err := test.IsSolved(circuit, witness, ecc.BLS12_381.ScalarField()) + err := test.IsSolved(circuit, witness, ecc.BN254.ScalarField()) assert.NoError(err) } @@ -116,6 +117,55 @@ func TestECAddBLSCircuitFull(t *testing.T) { assert.CheckCircuit(circuit, test.WithValidAssignment(witness)) } +type ecmulBLSCircuit struct { + X0 sw_emulated.AffinePoint[emulated.BLS12381Fp] + U emulated.Element[emulated.BLS12381Fr] + Expected sw_emulated.AffinePoint[emulated.BLS12381Fp] +} + +func (c *ecmulBLSCircuit) Define(api frontend.API) error { + curve, err := sw_emulated.New[emulated.BLS12381Fp, emulated.BLS12381Fr](api, sw_emulated.GetBLS12381Params()) + if err != nil { + return err + } + res := ECMSMG1BLS(api, + []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.X0}, + []*emulated.Element[emulated.BLS12381Fr]{&c.U}, + ) + curve.AssertIsEqual(res, &c.Expected) + return nil +} + +func testRoutineECMulBLS(t *testing.T) (circ, wit frontend.Circuit) { + _, _, G, _ := bls12381.Generators() + var u, v fr.Element + u.SetRandom() + v.SetRandom() + var P bls12381.G1Affine + P.ScalarMultiplication(&G, u.BigInt(new(big.Int))) + var expected bls12381.G1Affine + expected.ScalarMultiplication(&P, v.BigInt(new(big.Int))) + circuit := ecmulBLSCircuit{} + witness := ecmulBLSCircuit{ + X0: sw_emulated.AffinePoint[emulated.BLS12381Fp]{ + X: emulated.ValueOf[emulated.BLS12381Fp](P.X), + Y: emulated.ValueOf[emulated.BLS12381Fp](P.Y), + }, + U: emulated.ValueOf[emulated.BLS12381Fr](v), + Expected: sw_emulated.AffinePoint[emulated.BLS12381Fp]{ + X: emulated.ValueOf[emulated.BLS12381Fp](expected.X), + Y: emulated.ValueOf[emulated.BLS12381Fp](expected.Y), + }, + } + return &circuit, &witness +} + +func TestECMulBLSCircuitFull(t *testing.T) { + assert := test.NewAssert(t) + circuit, witness := testRoutineECMulBLS(t) + assert.CheckCircuit(circuit, test.WithValidAssignment(witness), test.WithCurves(ecc.BN254)) +} + type ecPairBLSBatchCircuit struct { P sw_bls12381.G1Affine NP sw_bls12381.G1Affine @@ -174,7 +224,70 @@ func TestECPairBLSBLSMulBatch(t *testing.T) { NP: sw_bls12381.NewG1Affine(np), DP: sw_bls12381.NewG1Affine(dp), Q: sw_bls12381.NewG2Affine(q), - }, ecc.BLS12_381.ScalarField()) + }, ecc.BN254.ScalarField()) assert.NoError(err) } } + +type ecmsmg1BLSCircuit struct { + Points []sw_emulated.AffinePoint[emulated.BLS12381Fp] + Scalars []emulated.Element[emulated.BLS12381Fr] + Res sw_emulated.AffinePoint[emulated.BLS12381Fp] +} + +func (c *ecmsmg1BLSCircuit) Define(api frontend.API) error { + curve, err := sw_emulated.New[emulated.BLS12381Fp, emulated.BLS12381Fr](api, sw_emulated.GetBLS12381Params()) + if err != nil { + return err + } + ps := make([]*sw_emulated.AffinePoint[emulated.BLS12381Fp], len(c.Points)) + for i := range c.Points { + ps[i] = &c.Points[i] + } + ss := make([]*emulated.Element[emulated.BLS12381Fr], len(c.Scalars)) + for i := range c.Scalars { + ss[i] = &c.Scalars[i] + } + res := ECMSMG1BLS(api, ps, ss) + curve.AssertIsEqual(res, &c.Res) + return nil +} + +func TestECMSMG1BLSCircuit(t *testing.T) { + assert := test.NewAssert(t) + nbLen := 4 + P := make([]bls12381.G1Affine, nbLen) + S := make([]fr_bls12381.Element, nbLen) + for i := 0; i < nbLen; i++ { + S[i].SetRandom() + P[i].ScalarMultiplicationBase(S[i].BigInt(new(big.Int))) + } + var res bls12381.G1Affine + _, err := res.MultiExp(P, S, ecc.MultiExpConfig{}) + + assert.NoError(err) + cP := make([]sw_emulated.AffinePoint[emulated.BLS12381Fp], len(P)) + for i := range cP { + cP[i] = sw_emulated.AffinePoint[emulated.BLS12381Fp]{ + X: emulated.ValueOf[emulated.BLS12381Fp](P[i].X), + Y: emulated.ValueOf[emulated.BLS12381Fp](P[i].Y), + } + } + cS := make([]emulated.Element[emulated.BLS12381Fr], len(S)) + for i := range cS { + cS[i] = emulated.ValueOf[emulated.BLS12381Fr](S[i]) + } + assignment := ecmsmg1BLSCircuit{ + Points: cP, + Scalars: cS, + Res: sw_emulated.AffinePoint[emulated.BLS12381Fp]{ + X: emulated.ValueOf[emulated.BLS12381Fp](res.X), + Y: emulated.ValueOf[emulated.BLS12381Fp](res.Y), + }, + } + err = test.IsSolved(&ecmsmg1BLSCircuit{ + Points: make([]sw_emulated.AffinePoint[emulated.BLS12381Fp], nbLen), + Scalars: make([]emulated.Element[emulated.BLS12381Fr], nbLen), + }, &assignment, ecc.BN254.ScalarField()) + assert.NoError(err) +} From 13f23c31db1a1fb2e170350436d4acde0287228e Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Mon, 17 Mar 2025 15:03:22 -0400 Subject: [PATCH 016/105] refactor: BLS12 precompiles to include G1/2 membership checks --- std/algebra/emulated/sw_bls12381/g1.go | 51 +++++++++++++++++++ std/algebra/emulated/sw_bls12381/g2.go | 10 ++-- std/algebra/emulated/sw_bls12381/g2_test.go | 20 ++++++-- std/algebra/emulated/sw_bls12381/pairing.go | 56 +++++++++------------ std/algebra/emulated/sw_bn254/g2.go | 10 ++-- std/algebra/emulated/sw_bn254/g2_test.go | 25 +++++++-- std/algebra/emulated/sw_bn254/pairing.go | 6 ++- std/evmprecompiles/08-bnpairing.go | 2 +- std/evmprecompiles/11-blsg1add.go | 6 ++- std/evmprecompiles/12-blsg1msm.go | 12 ++++- std/evmprecompiles/13-blsg2add.go | 13 +++-- std/evmprecompiles/15-blspairing.go | 16 +++++- std/evmprecompiles/bls_test.go | 5 +- 13 files changed, 170 insertions(+), 62 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/g1.go b/std/algebra/emulated/sw_bls12381/g1.go index 29f392d482..48aef851a1 100644 --- a/std/algebra/emulated/sw_bls12381/g1.go +++ b/std/algebra/emulated/sw_bls12381/g1.go @@ -40,11 +40,21 @@ func NewG1(api frontend.API) (*G1, error) { } w := emulated.ValueOf[BaseField]("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939436") return &G1{ + api: api, curveF: ba, w: &w, }, nil } +func (g1 G1) neg(p *G1Affine) *G1Affine { + xr := &p.X + yr := g1.curveF.Neg(&p.Y) + return &G1Affine{ + X: *xr, + Y: *yr, + } +} + func (g1 *G1) phi(q *G1Affine) *G1Affine { x := g1.curveF.Mul(&q.X, g1.w) @@ -158,6 +168,47 @@ func (g1 *G1) scalarMulBySeedSquare(q *G1Affine) *G1Affine { return z } +func (g1 *G1) computeCurveEquation(P *G1Affine) (left, right *baseEl) { + // Curve: Y² == X³ + aX + b, where a=0 and b=4 + // (X,Y) ∈ {Y² == X³ + aX + b} U (0,0) + + // if P=(0,0) we assign b=0 otherwise 4, and continue + selector := g1.api.And(g1.curveF.IsZero(&P.X), g1.curveF.IsZero(&P.Y)) + four := emulated.ValueOf[BaseField]("4") + b := g1.curveF.Select(selector, g1.curveF.Zero(), &four) + + left = g1.curveF.Mul(&P.Y, &P.Y) + right = g1.curveF.Mul(&P.X, &P.X) + right = g1.curveF.Mul(right, &P.X) + right = g1.curveF.Add(right, b) + return left, right +} + +func (g1 *G1) AssertIsOnCurve(P *G1Affine) { + left, right := g1.computeCurveEquation(P) + g1.curveF.AssertIsEqual(left, right) +} + +func (g1 *G1) AssertIsOnG1(P *G1Affine) { + // 1- Check P is on the curve + g1.AssertIsOnCurve(P) + + // 2- Check P has the right subgroup order + // [x²]ϕ(P) + phiP := g1.phi(P) + _P := g1.scalarMulBySeedSquare(phiP) + _P = g1.neg(_P) + + // [r]Q == 0 <==> P = -[x²]ϕ(P) + g1.AssertIsEqual(_P, P) +} + +// AssertIsEqual asserts that p and q are the same point. +func (g1 *G1) AssertIsEqual(p, q *G1Affine) { + g1.curveF.AssertIsEqual(&p.X, &q.X) + g1.curveF.AssertIsEqual(&p.Y, &q.Y) +} + func (g1 *G1) IsEqual(p, q *G1Affine) frontend.Variable { xDiff := g1.curveF.Sub(&p.X, &q.X) yDiff := g1.curveF.Sub(&p.Y, &q.Y) diff --git a/std/algebra/emulated/sw_bls12381/g2.go b/std/algebra/emulated/sw_bls12381/g2.go index 2a20cbee1d..505b8c78b1 100644 --- a/std/algebra/emulated/sw_bls12381/g2.go +++ b/std/algebra/emulated/sw_bls12381/g2.go @@ -1,6 +1,7 @@ package sw_bls12381 import ( + "fmt" "math/big" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" @@ -40,11 +41,10 @@ func newG2AffP(v bls12381.G2Affine) g2AffP { } } -func NewG2(api frontend.API) *G2 { - fp, err := emulated.NewField[emulated.BLS12381Fp](api) +func NewG2(api frontend.API) (*G2, error) { + fp, err := emulated.NewField[BaseField](api) if err != nil { - // TODO: we start returning errors when generifying - panic(err) + return nil, fmt.Errorf("new base api: %w", err) } w := emulated.ValueOf[BaseField]("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939436") u1 := emulated.ValueOf[BaseField]("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939437") @@ -59,7 +59,7 @@ func NewG2(api frontend.API) *G2 { w: &w, u1: &u1, v: &v, - } + }, nil } func NewG2Affine(v bls12381.G2Affine) G2Affine { diff --git a/std/algebra/emulated/sw_bls12381/g2_test.go b/std/algebra/emulated/sw_bls12381/g2_test.go index 9d4a90d0e4..7fae55bf8e 100644 --- a/std/algebra/emulated/sw_bls12381/g2_test.go +++ b/std/algebra/emulated/sw_bls12381/g2_test.go @@ -16,7 +16,10 @@ type addG2Circuit struct { } func (c *addG2Circuit) Define(api frontend.API) error { - g2 := NewG2(api) + g2, err := NewG2(api) + if err != nil { + panic(err) + } res := g2.add(&c.In1, &c.In2) g2.AssertIsEqual(res, &c.Res) return nil @@ -43,7 +46,10 @@ type doubleG2Circuit struct { } func (c *doubleG2Circuit) Define(api frontend.API) error { - g2 := NewG2(api) + g2, err := NewG2(api) + if err != nil { + panic(err) + } res := g2.double(&c.In1) g2.AssertIsEqual(res, &c.Res) return nil @@ -71,7 +77,10 @@ type doubleAndAddG2Circuit struct { } func (c *doubleAndAddG2Circuit) Define(api frontend.API) error { - g2 := NewG2(api) + g2, err := NewG2(api) + if err != nil { + panic(err) + } res := g2.doubleAndAdd(&c.In1, &c.In2) g2.AssertIsEqual(res, &c.Res) return nil @@ -99,7 +108,10 @@ type scalarMulG2BySeedCircuit struct { } func (c *scalarMulG2BySeedCircuit) Define(api frontend.API) error { - g2 := NewG2(api) + g2, err := NewG2(api) + if err != nil { + panic(err) + } res := g2.scalarMulBySeed(&c.In1) g2.AssertIsEqual(res, &c.Res) return nil diff --git a/std/algebra/emulated/sw_bls12381/pairing.go b/std/algebra/emulated/sw_bls12381/pairing.go index d3a1133ff8..bf0abfb0ac 100644 --- a/std/algebra/emulated/sw_bls12381/pairing.go +++ b/std/algebra/emulated/sw_bls12381/pairing.go @@ -65,6 +65,10 @@ func NewPairing(api frontend.API) (*Pairing, error) { if err != nil { return nil, fmt.Errorf("new G1 struct: %w", err) } + g2, err := NewG2(api) + if err != nil { + return nil, fmt.Errorf("new G2 struct: %w", err) + } return &Pairing{ api: api, Ext12: fields_bls12381.NewExt12(api), @@ -72,7 +76,7 @@ func NewPairing(api frontend.API) (*Pairing, error) { curveF: ba, curve: curve, g1: g1, - g2: NewG2(api), + g2: g2, }, nil } @@ -187,29 +191,31 @@ func (pr Pairing) AssertIsOnCurve(P *G1Affine) { pr.curve.AssertIsOnCurve(P) } -func (pr Pairing) computeCurveEquation(P *G1Affine) (left, right *baseEl) { - // Curve: Y² == X³ + aX + b, where a=0 and b=4 - // (X,Y) ∈ {Y² == X³ + aX + b} U (0,0) - - // if P=(0,0) we assign b=0 otherwise 4, and continue - selector := pr.api.And(pr.curveF.IsZero(&P.X), pr.curveF.IsZero(&P.Y)) - four := emulated.ValueOf[BaseField]("4") - b := pr.curveF.Select(selector, pr.curveF.Zero(), &four) - - left = pr.curveF.Mul(&P.Y, &P.Y) - right = pr.curveF.Mul(&P.X, &P.X) - right = pr.curveF.Mul(right, &P.X) - right = pr.curveF.Add(right, b) - return left, right -} - // IsOnCurve returns a boolean indicating if the G1 point is in the curve. func (pr Pairing) IsOnCurve(P *G1Affine) frontend.Variable { - left, right := pr.computeCurveEquation(P) + left, right := pr.g1.computeCurveEquation(P) diff := pr.curveF.Sub(left, right) return pr.curveF.IsZero(diff) } +func (pr Pairing) AssertIsOnG1(P *G1Affine) { + pr.g1.AssertIsOnG1(P) +} + +// IsOnG1 returns a boolean indicating if the G1 point is in the subgroup. The +// method assumes that the point is already on the curve. Call +// [Pairing.AssertIsOnTwist] before to ensure point is on the curve. +func (pr Pairing) IsOnG1(P *G1Affine) frontend.Variable { + // 1 - is Q on curve + isOnCurve := pr.IsOnCurve(P) + // 2 - is Q in the subgroup + phiP := pr.g1.phi(P) + _P := pr.g1.scalarMulBySeedSquare(phiP) + _P = pr.curve.Neg(_P) + isInSubgroup := pr.g1.IsEqual(_P, phiP) + return pr.api.And(isOnCurve, isInSubgroup) +} + func (pr Pairing) AssertIsOnTwist(Q *G2Affine) { pr.g2.AssertIsOnTwist(Q) } @@ -221,20 +227,6 @@ func (pr Pairing) IsOnTwist(Q *G2Affine) frontend.Variable { return pr.Ext2.IsZero(diff) } -func (pr Pairing) AssertIsOnG1(P *G1Affine) { - // 1- Check P is on the curve - pr.AssertIsOnCurve(P) - - // 2- Check P has the right subgroup order - // [x²]ϕ(P) - phiP := pr.g1.phi(P) - _P := pr.g1.scalarMulBySeedSquare(phiP) - _P = pr.curve.Neg(_P) - - // [r]Q == 0 <==> P = -[x²]ϕ(P) - pr.curve.AssertIsEqual(_P, P) -} - func (pr Pairing) AssertIsOnG2(Q *G2Affine) { pr.g2.AssertIsOnG2(Q) } diff --git a/std/algebra/emulated/sw_bn254/g2.go b/std/algebra/emulated/sw_bn254/g2.go index c7017d6fe4..e9108a17a0 100644 --- a/std/algebra/emulated/sw_bn254/g2.go +++ b/std/algebra/emulated/sw_bn254/g2.go @@ -1,6 +1,7 @@ package sw_bn254 import ( + "fmt" "math/big" "github.com/consensys/gnark-crypto/ecc/bn254" @@ -40,11 +41,10 @@ func newG2AffP(v bn254.G2Affine) g2AffP { } } -func NewG2(api frontend.API) *G2 { - fp, err := emulated.NewField[emulated.BN254Fp](api) +func NewG2(api frontend.API) (*G2, error) { + fp, err := emulated.NewField[BaseField](api) if err != nil { - // TODO: we start returning errors when generifying - panic(err) + return nil, fmt.Errorf("new base api: %w", err) } w := emulated.ValueOf[BaseField]("21888242871839275220042445260109153167277707414472061641714758635765020556616") u := fields_bn254.E2{ @@ -62,7 +62,7 @@ func NewG2(api frontend.API) *G2 { w: &w, u: &u, v: &v, - } + }, nil } func NewG2Affine(v bn254.G2Affine) G2Affine { diff --git a/std/algebra/emulated/sw_bn254/g2_test.go b/std/algebra/emulated/sw_bn254/g2_test.go index 4d48796200..812e21e0e0 100644 --- a/std/algebra/emulated/sw_bn254/g2_test.go +++ b/std/algebra/emulated/sw_bn254/g2_test.go @@ -16,7 +16,10 @@ type addG2Circuit struct { } func (c *addG2Circuit) Define(api frontend.API) error { - g2 := NewG2(api) + g2, err := NewG2(api) + if err != nil { + panic(err) + } res := g2.add(&c.In1, &c.In2) g2.AssertIsEqual(res, &c.Res) return nil @@ -43,7 +46,10 @@ type doubleG2Circuit struct { } func (c *doubleG2Circuit) Define(api frontend.API) error { - g2 := NewG2(api) + g2, err := NewG2(api) + if err != nil { + panic(err) + } res := g2.double(&c.In1) g2.AssertIsEqual(res, &c.Res) return nil @@ -71,7 +77,10 @@ type doubleAndAddG2Circuit struct { } func (c *doubleAndAddG2Circuit) Define(api frontend.API) error { - g2 := NewG2(api) + g2, err := NewG2(api) + if err != nil { + panic(err) + } res := g2.doubleAndAdd(&c.In1, &c.In2) g2.AssertIsEqual(res, &c.Res) return nil @@ -99,7 +108,10 @@ type scalarMulG2BySeedCircuit struct { } func (c *scalarMulG2BySeedCircuit) Define(api frontend.API) error { - g2 := NewG2(api) + g2, err := NewG2(api) + if err != nil { + panic(err) + } res := g2.scalarMulBySeed(&c.In1) g2.AssertIsEqual(res, &c.Res) return nil @@ -124,7 +136,10 @@ type endomorphismG2Circuit struct { } func (c *endomorphismG2Circuit) Define(api frontend.API) error { - g2 := NewG2(api) + g2, err := NewG2(api) + if err != nil { + panic(err) + } res1 := g2.phi(&c.In1) res2 := g2.psi(&c.In1) res2 = g2.psi(res2) diff --git a/std/algebra/emulated/sw_bn254/pairing.go b/std/algebra/emulated/sw_bn254/pairing.go index 4e66488b97..0255564501 100644 --- a/std/algebra/emulated/sw_bn254/pairing.go +++ b/std/algebra/emulated/sw_bn254/pairing.go @@ -70,13 +70,17 @@ func NewPairing(api frontend.API) (*Pairing, error) { A0: emulated.ValueOf[BaseField]("19485874751759354771024239261021720505790618469301721065564631296452457478373"), A1: emulated.ValueOf[BaseField]("266929791119991161246907387137283842545076965332900288569378510910307636690"), } + g2, err := NewG2(api) + if err != nil { + return nil, fmt.Errorf("new g2: %w", err) + } return &Pairing{ api: api, Ext12: fields_bn254.NewExt12(api), Ext2: fields_bn254.NewExt2(api), curveF: ba, curve: curve, - g2: NewG2(api), + g2: g2, bTwist: &bTwist, }, nil } diff --git a/std/evmprecompiles/08-bnpairing.go b/std/evmprecompiles/08-bnpairing.go index f800e1c2b4..c93c38d889 100644 --- a/std/evmprecompiles/08-bnpairing.go +++ b/std/evmprecompiles/08-bnpairing.go @@ -41,7 +41,7 @@ func ECPair(api frontend.API, P []*sw_bn254.G1Affine, Q []*sw_bn254.G2Affine) { panic(err) } // 1- Check that Pᵢ are on G1 (done in the zkEVM ⚠️) - // 2- Check that Qᵢ are on G2 (done in `computeLines` in `MillerLoopAndMul` and `MillerLoopAndFinalExpCheck) + // 2- Check that Qᵢ are on G2 (done in `computeLines` in `MillerLoopAndMul` and `MillerLoopAndFinalExpCheck`) // 3- Check that ∏ᵢ e(Pᵢ, Qᵢ) == 1 ml := pair.Ext12.One() diff --git a/std/evmprecompiles/11-blsg1add.go b/std/evmprecompiles/11-blsg1add.go index 95aa99ecb1..ea6df4714f 100644 --- a/std/evmprecompiles/11-blsg1add.go +++ b/std/evmprecompiles/11-blsg1add.go @@ -14,7 +14,11 @@ func ECAddBLS(api frontend.API, P, Q *sw_emulated.AffinePoint[emulated.BLS12381F if err != nil { panic(err) } - // Check that P and Q are on G1 (done in the zkEVM ⚠️ ) + // Check that P and Q are on curve + // N.B.: There is no subgroup check for the G1 addition precompile. + curve.AssertIsOnCurve(P) + curve.AssertIsOnCurve(Q) + // We use AddUnified because P can be equal to Q, -Q and either or both can be (0,0) res := curve.AddUnified(P, Q) return res diff --git a/std/evmprecompiles/12-blsg1msm.go b/std/evmprecompiles/12-blsg1msm.go index 100976fff3..8508487c1b 100644 --- a/std/evmprecompiles/12-blsg1msm.go +++ b/std/evmprecompiles/12-blsg1msm.go @@ -3,6 +3,7 @@ package evmprecompiles import ( "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/algebra/algopts" + "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" "github.com/consensys/gnark/std/math/emulated" ) @@ -15,8 +16,17 @@ func ECMSMG1BLS(api frontend.API, P []*sw_emulated.AffinePoint[emulated.BLS12381 if err != nil { panic(err) } + g1, err := sw_bls12381.NewG1(api) + if err != nil { + panic(err) + } + + // Check that Pᵢ are on G1 + for _, p := range P { + g1.AssertIsOnG1(p) + } - // Check that all P's are on G1 (done in the zkEVM ⚠️ ) + // Compute the MSM res, err := curve.MultiScalarMul(P, s, algopts.WithCompleteArithmetic()) if err != nil { panic(err) diff --git a/std/evmprecompiles/13-blsg2add.go b/std/evmprecompiles/13-blsg2add.go index 100e784f82..776f1aa1e3 100644 --- a/std/evmprecompiles/13-blsg2add.go +++ b/std/evmprecompiles/13-blsg2add.go @@ -9,10 +9,15 @@ import ( // // [BLS12_G2ADD]: https://eips.ethereum.org/EIPS/eip-2537 func ECAddG2BLS(api frontend.API, P, Q *sw_bls12381.G2Affine) *sw_bls12381.G2Affine { - g2 := sw_bls12381.NewG2(api) - // Check that P and Q are on G2 - g2.AssertIsOnG2(P) - g2.AssertIsOnG2(Q) + g2, err := sw_bls12381.NewG2(api) + if err != nil { + panic(err) + } + + // Check that P and Q are on curve + // N.B.: There is no subgroup check for the G2 addition precompile. + g2.AssertIsOnTwist(P) + g2.AssertIsOnTwist(Q) // We use AddUnified because P can be equal to Q, -Q and either or both can be (0,0) res := g2.AddUnified(P, Q) diff --git a/std/evmprecompiles/15-blspairing.go b/std/evmprecompiles/15-blspairing.go index d7db74f31e..c99ab050a7 100644 --- a/std/evmprecompiles/15-blspairing.go +++ b/std/evmprecompiles/15-blspairing.go @@ -41,8 +41,9 @@ func ECPairBLS(api frontend.API, P []*sw_bls12381.G1Affine, Q []*sw_bls12381.G2A panic(err) } for i := 0; i < n; i++ { - // 1- Check that Pᵢ are on G1 (done in the zkEVM ⚠️) - // 2- Check that Qᵢ are on G2 (done in `computeLines` in `MillerLoopAndMul` and `MillerLoopAndFinalExpCheck) + // 1- Check that Pᵢ are on G1 + pair.AssertIsOnG1(P[i]) + // 2- Check that Qᵢ are on G2 (done in `computeLines` in `MillerLoopAndMul` and `MillerLoopAndFinalExpCheck`) } // 3- Check that ∏ᵢ e(Pᵢ, Qᵢ) == 1 @@ -70,6 +71,17 @@ func ECPairBLSIsOnG2(api frontend.API, Q *sw_bls12381.G2Affine, expectedIsOnG2 f return nil } +// ECPairBLSIsOnG1 implements the fixed circuit for checking G1 membership and non-membership. +func ECPairBLSIsOnG1(api frontend.API, Q *sw_bls12381.G1Affine, expectedIsOnG1 frontend.Variable) error { + pairing, err := sw_bls12381.NewPairing(api) + if err != nil { + return err + } + isOnG1 := pairing.IsOnG1(Q) + api.AssertIsEqual(expectedIsOnG1, isOnG1) + return nil +} + // ECPairMillerLoopAndMul implements the fixed circuit for a Miller loop of // fixed size 1 followed by a multiplication with an accumulator in 𝔽p¹². It // asserts that the result corresponds to the expected result. diff --git a/std/evmprecompiles/bls_test.go b/std/evmprecompiles/bls_test.go index 75673fa458..1323d0e3b9 100644 --- a/std/evmprecompiles/bls_test.go +++ b/std/evmprecompiles/bls_test.go @@ -74,7 +74,10 @@ type ecaddG2BLSCircuit struct { } func (c *ecaddG2BLSCircuit) Define(api frontend.API) error { - g2 := sw_bls12381.NewG2(api) + g2, err := sw_bls12381.NewG2(api) + if err != nil { + panic(err) + } res := ECAddG2BLS(api, &c.X0, &c.X1) g2.AssertIsEqual(res, &c.Expected) return nil From 225d6f630fedc3d0bc8d4daba171d27001025b0a Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Mon, 17 Mar 2025 16:30:16 -0400 Subject: [PATCH 017/105] perf(pectra): optimize MSM G1 --- std/algebra/emulated/sw_emulated/point.go | 8 ++-- std/evmprecompiles/bls_test.go | 54 +++++++++++------------ 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index aef54a3915..33f8c2c3b6 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -851,8 +851,8 @@ func (c *Curve[B, S]) jointScalarMulGLV(p1, p2 *AffinePoint[B], s1, s2 *emulated panic(fmt.Sprintf("parse opts: %v", err)) } if cfg.CompleteArithmetic { - res1 := c.scalarMulGLV(p1, s1, opts...) - res2 := c.scalarMulGLV(p2, s2, opts...) + res1 := c.scalarMulGLVAndFakeGLV(p1, s1, opts...) + res2 := c.scalarMulGLVAndFakeGLV(p2, s2, opts...) return c.AddUnified(res1, res2) } else { return c.jointScalarMulGLVUnsafe(p1, p2, s1, s2) @@ -1094,10 +1094,10 @@ func (c *Curve[B, S]) jointScalarMulGLVUnsafe(Q, R *AffinePoint[B], s, t *emulat // ScalarMulBase computes [s]g and returns it where g is the fixed curve generator. It doesn't modify p nor s. // -// ScalarMul calls scalarMulBaseGeneric or scalarMulGLV depending on whether an efficient endomorphism is available. +// ScalarMul calls scalarMulBaseGeneric or scalarMulGLVAndFakeGLV depending on whether an efficient endomorphism is available. func (c *Curve[B, S]) ScalarMulBase(s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] { if c.eigenvalue != nil && c.thirdRootOne != nil { - return c.scalarMulGLV(c.Generator(), s, opts...) + return c.scalarMulGLVAndFakeGLV(c.Generator(), s, opts...) } else { return c.scalarMulBaseGeneric(s, opts...) diff --git a/std/evmprecompiles/bls_test.go b/std/evmprecompiles/bls_test.go index 1323d0e3b9..81507ca612 100644 --- a/std/evmprecompiles/bls_test.go +++ b/std/evmprecompiles/bls_test.go @@ -233,9 +233,10 @@ func TestECPairBLSBLSMulBatch(t *testing.T) { } type ecmsmg1BLSCircuit struct { - Points []sw_emulated.AffinePoint[emulated.BLS12381Fp] - Scalars []emulated.Element[emulated.BLS12381Fr] + Points [10]sw_emulated.AffinePoint[emulated.BLS12381Fp] + Scalars [10]emulated.Element[emulated.BLS12381Fr] Res sw_emulated.AffinePoint[emulated.BLS12381Fp] + n int } func (c *ecmsmg1BLSCircuit) Define(api frontend.API) error { @@ -243,12 +244,12 @@ func (c *ecmsmg1BLSCircuit) Define(api frontend.API) error { if err != nil { return err } - ps := make([]*sw_emulated.AffinePoint[emulated.BLS12381Fp], len(c.Points)) - for i := range c.Points { + ps := make([]*sw_emulated.AffinePoint[emulated.BLS12381Fp], c.n) + for i := range c.n { ps[i] = &c.Points[i] } - ss := make([]*emulated.Element[emulated.BLS12381Fr], len(c.Scalars)) - for i := range c.Scalars { + ss := make([]*emulated.Element[emulated.BLS12381Fr], c.n) + for i := range c.n { ss[i] = &c.Scalars[i] } res := ECMSMG1BLS(api, ps, ss) @@ -258,39 +259,38 @@ func (c *ecmsmg1BLSCircuit) Define(api frontend.API) error { func TestECMSMG1BLSCircuit(t *testing.T) { assert := test.NewAssert(t) - nbLen := 4 - P := make([]bls12381.G1Affine, nbLen) - S := make([]fr_bls12381.Element, nbLen) - for i := 0; i < nbLen; i++ { + P := make([]bls12381.G1Affine, 10) + S := make([]fr_bls12381.Element, 10) + for i := 0; i < 10; i++ { S[i].SetRandom() P[i].ScalarMultiplicationBase(S[i].BigInt(new(big.Int))) } - var res bls12381.G1Affine - _, err := res.MultiExp(P, S, ecc.MultiExpConfig{}) - assert.NoError(err) - cP := make([]sw_emulated.AffinePoint[emulated.BLS12381Fp], len(P)) + var cP [10]sw_emulated.AffinePoint[emulated.BLS12381Fp] for i := range cP { cP[i] = sw_emulated.AffinePoint[emulated.BLS12381Fp]{ X: emulated.ValueOf[emulated.BLS12381Fp](P[i].X), Y: emulated.ValueOf[emulated.BLS12381Fp](P[i].Y), } } - cS := make([]emulated.Element[emulated.BLS12381Fr], len(S)) + var cS [10]emulated.Element[emulated.BLS12381Fr] for i := range cS { cS[i] = emulated.ValueOf[emulated.BLS12381Fr](S[i]) } - assignment := ecmsmg1BLSCircuit{ - Points: cP, - Scalars: cS, - Res: sw_emulated.AffinePoint[emulated.BLS12381Fp]{ - X: emulated.ValueOf[emulated.BLS12381Fp](res.X), - Y: emulated.ValueOf[emulated.BLS12381Fp](res.Y), - }, + + for i := 1; i < 11; i++ { + var res bls12381.G1Affine + _, err := res.MultiExp(P[:i], S[:i], ecc.MultiExpConfig{}) + assert.NoError(err) + err = test.IsSolved(&ecmsmg1BLSCircuit{n: i}, &ecmsmg1BLSCircuit{ + n: i, + Points: cP, + Scalars: cS, + Res: sw_emulated.AffinePoint[emulated.BLS12381Fp]{ + X: emulated.ValueOf[emulated.BLS12381Fp](res.X), + Y: emulated.ValueOf[emulated.BLS12381Fp](res.Y), + }, + }, ecc.BN254.ScalarField()) + assert.NoError(err) } - err = test.IsSolved(&ecmsmg1BLSCircuit{ - Points: make([]sw_emulated.AffinePoint[emulated.BLS12381Fp], nbLen), - Scalars: make([]emulated.Element[emulated.BLS12381Fr], nbLen), - }, &assignment, ecc.BN254.ScalarField()) - assert.NoError(err) } From ee8f46acc470735c1205e75ffac5ab9e93a4137c Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Tue, 18 Mar 2025 10:30:06 +0100 Subject: [PATCH 018/105] feat: constants isogeny ok --- std/evmprecompiles/16-blsmaptog1.go | 121 ++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 std/evmprecompiles/16-blsmaptog1.go diff --git a/std/evmprecompiles/16-blsmaptog1.go b/std/evmprecompiles/16-blsmaptog1.go new file mode 100644 index 0000000000..9a2539871d --- /dev/null +++ b/std/evmprecompiles/16-blsmaptog1.go @@ -0,0 +1,121 @@ +package evmprecompiles + +import ( + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/emulated" +) + +type FpElement = emulated.Element[emulated.BLS12381Fp] + +func g1IsogenyXNumerator(api frontend.API, x FpElement) (FpElement, error) { + + return g1EvalPolynomial( + api, + false, + []FpElement{ + emulated.ValueOf[emulated.BLS12381Fp]("0x11a05f2b1e833340b809101dd99815856b303e88a2d7005ff2627b56cdb4e2c85610c2d5f2e62d6eaeac1662734649b7"), + emulated.ValueOf[emulated.BLS12381Fp]("0x17294ed3e943ab2f0588bab22147a81c7c17e75b2f6a8417f565e33c70d1e86b4838f2a6f318c356e834eef1b3cb83bb"), + emulated.ValueOf[emulated.BLS12381Fp]("0xd54005db97678ec1d1048c5d10a9a1bce032473295983e56878e501ec68e25c958c3e3d2a09729fe0179f9dac9edcb0"), + emulated.ValueOf[emulated.BLS12381Fp]("0x1778e7166fcc6db74e0609d307e55412d7f5e4656a8dbf25f1b33289f1b330835336e25ce3107193c5b388641d9b6861"), + emulated.ValueOf[emulated.BLS12381Fp]("0xe99726a3199f4436642b4b3e4118e5499db995a1257fb3f086eeb65982fac18985a286f301e77c451154ce9ac8895d9"), + emulated.ValueOf[emulated.BLS12381Fp]("0x1630c3250d7313ff01d1201bf7a74ab5db3cb17dd952799b9ed3ab9097e68f90a0870d2dcae73d19cd13c1c66f652983"), + emulated.ValueOf[emulated.BLS12381Fp]("0xd6ed6553fe44d296a3726c38ae652bfb11586264f0f8ce19008e218f9c86b2a8da25128c1052ecaddd7f225a139ed84"), + emulated.ValueOf[emulated.BLS12381Fp]("0x17b81e7701abdbe2e8743884d1117e53356de5ab275b4db1a682c62ef0f2753339b7c8f8c8f475af9ccb5618e3f0c88e"), + emulated.ValueOf[emulated.BLS12381Fp]("0x80d3cf1f9a78fc47b90b33563be990dc43b756ce79f5574a2c596c928c5d1de4fa295f296b74e956d71986a8497e317"), + emulated.ValueOf[emulated.BLS12381Fp]("0x169b1f8e1bcfa7c42e0c37515d138f22dd2ecb803a0c5c99676314baf4bb1b7fa3190b2edc0327797f241067be390c9e"), + emulated.ValueOf[emulated.BLS12381Fp](" 0x10321da079ce07e272d8ec09d2565b0dfa7dccdde6787f96d50af36003b14866f69b771f8c285decca67df3f1605fb7b"), + emulated.ValueOf[emulated.BLS12381Fp](" 0x6e08c248e260e70bd1e962381edee3d31d79d7e22c837bc23c0bf1bc24c6b68c24b1b80b64d391fa9c8ba2e8ba2d229"), + }, + x) +} + +func g1IsogenyXDenominator(api frontend.API, x FpElement) (FpElement, error) { + + return g1EvalPolynomial( + api, + true, + []FpElement{ + emulated.ValueOf[emulated.BLS12381Fp]("0x8ca8d548cff19ae18b2e62f4bd3fa6f01d5ef4ba35b48ba9c9588617fc8ac62b558d681be343df8993cf9fa40d21b1c"), + emulated.ValueOf[emulated.BLS12381Fp]("0x12561a5deb559c4348b4711298e536367041e8ca0cf0800c0126c2588c48bf5713daa8846cb026e9e5c8276ec82b3bff"), + emulated.ValueOf[emulated.BLS12381Fp]("0xb2962fe57a3225e8137e629bff2991f6f89416f5a718cd1fca64e00b11aceacd6a3d0967c94fedcfcc239ba5cb83e19"), + emulated.ValueOf[emulated.BLS12381Fp]("0x3425581a58ae2fec83aafef7c40eb545b08243f16b1655154cca8abc28d6fd04976d5243eecf5c4130de8938dc62cd8"), + emulated.ValueOf[emulated.BLS12381Fp]("0x13a8e162022914a80a6f1d5f43e7a07dffdfc759a12062bb8d6b44e833b306da9bd29ba81f35781d539d395b3532a21e"), + emulated.ValueOf[emulated.BLS12381Fp]("0xe7355f8e4e667b955390f7f0506c6e9395735e9ce9cad4d0a43bcef24b8982f7400d24bc4228f11c02df9a29f6304a5"), + emulated.ValueOf[emulated.BLS12381Fp]("0x772caacf16936190f3e0c63e0596721570f5799af53a1894e2e073062aede9cea73b3538f0de06cec2574496ee84a3a"), + emulated.ValueOf[emulated.BLS12381Fp]("0x14a7ac2a9d64a8b230b3f5b074cf01996e7f63c21bca68a81996e1cdf9822c580fa5b9489d11e2d311f7d99bbdcc5a5e"), + emulated.ValueOf[emulated.BLS12381Fp]("0xa10ecf6ada54f825e920b3dafc7a3cce07f8d1d7161366b74100da67f39883503826692abba43704776ec3a79a1d641"), + emulated.ValueOf[emulated.BLS12381Fp]("0x95fc13ab9e92ad4476d6e3eb3a56680f682b4ee96f7d03776df533978f31c1593174e4b4b7865002d6384d168ecdd0a"), + }, + x) +} + +func g1IsogenyYNumerator(api frontend.API, x FpElement) (FpElement, error) { + + return g1EvalPolynomial( + api, + false, + []FpElement{ + emulated.ValueOf[emulated.BLS12381Fp]("0x90d97c81ba24ee0259d1f094980dcfa11ad138e48a869522b52af6c956543d3cd0c7aee9b3ba3c2be9845719707bb33"), + emulated.ValueOf[emulated.BLS12381Fp]("0x134996a104ee5811d51036d776fb46831223e96c254f383d0f906343eb67ad34d6c56711962fa8bfe097e75a2e41c696"), + emulated.ValueOf[emulated.BLS12381Fp]("0xcc786baa966e66f4a384c86a3b49942552e2d658a31ce2c344be4b91400da7d26d521628b00523b8dfe240c72de1f6"), + emulated.ValueOf[emulated.BLS12381Fp]("0x1f86376e8981c217898751ad8746757d42aa7b90eeb791c09e4a3ec03251cf9de405aba9ec61deca6355c77b0e5f4cb"), + emulated.ValueOf[emulated.BLS12381Fp]("0x8cc03fdefe0ff135caf4fe2a21529c4195536fbe3ce50b879833fd221351adc2ee7f8dc099040a841b6daecf2e8fedb"), + emulated.ValueOf[emulated.BLS12381Fp]("0x16603fca40634b6a2211e11db8f0a6a074a7d0d4afadb7bd76505c3d3ad5544e203f6326c95a807299b23ab13633a5f0"), + emulated.ValueOf[emulated.BLS12381Fp]("0x4ab0b9bcfac1bbcb2c977d027796b3ce75bb8ca2be184cb5231413c4d634f3747a87ac2460f415ec961f8855fe9d6f2"), + emulated.ValueOf[emulated.BLS12381Fp]("0x987c8d5333ab86fde9926bd2ca6c674170a05bfe3bdd81ffd038da6c26c842642f64550fedfe935a15e4ca31870fb29"), + emulated.ValueOf[emulated.BLS12381Fp]("0x9fc4018bd96684be88c9e221e4da1bb8f3abd16679dc26c1e8b6e6a1f20cabe69d65201c78607a360370e577bdba587"), + emulated.ValueOf[emulated.BLS12381Fp]("0xe1bba7a1186bdb5223abde7ada14a23c42a0ca7915af6fe06985e7ed1e4d43b9b3f7055dd4eba6f2bafaaebca731c30"), + emulated.ValueOf[emulated.BLS12381Fp]("0x19713e47937cd1be0dfd0b8f1d43fb93cd2fcbcb6caf493fd1183e416389e61031bf3a5cce3fbafce813711ad011c132"), + emulated.ValueOf[emulated.BLS12381Fp]("0x18b46a908f36f6deb918c143fed2edcc523559b8aaf0c2462e6bfe7f911f643249d9cdf41b44d606ce07c8a4d0074d8e"), + emulated.ValueOf[emulated.BLS12381Fp]("0xb182cac101b9399d155096004f53f447aa7b12a3426b08ec02710e807b4633f06c851c1919211f20d4c04f00b971ef8"), + emulated.ValueOf[emulated.BLS12381Fp]("0x245a394ad1eca9b72fc00ae7be315dc757b3b080d4c158013e6632d3c40659cc6cf90ad1c232a6442d9d3f5db980133"), + emulated.ValueOf[emulated.BLS12381Fp]("0x5c129645e44cf1102a159f748c4a3fc5e673d81d7e86568d9ab0f5d396a7ce46ba1049b6579afb7866b1e715475224b"), + emulated.ValueOf[emulated.BLS12381Fp]("0x15e6be4e990f03ce4ea50b3b42df2eb5cb181d8f84965a3957add4fa95af01b2b665027efec01c7704b456be69c8b604"), + }, + x) +} + +func g1IsogenyYDenominator(api frontend.API, x FpElement) (FpElement, error) { + + return g1EvalPolynomial( + api, + true, + []FpElement{ + emulated.ValueOf[emulated.BLS12381Fp]("0x16112c4c3a9c98b252181140fad0eae9601a6de578980be6eec3232b5be72e7a07f3688ef60c206d01479253b03663c1"), + emulated.ValueOf[emulated.BLS12381Fp]("0x1962d75c2381201e1a0cbd6c43c348b885c84ff731c4d59ca4a10356f453e01f78a4260763529e3532f6102c2e49a03d"), + emulated.ValueOf[emulated.BLS12381Fp]("0x58df3306640da276faaae7d6e8eb15778c4855551ae7f310c35a5dd279cd2eca6757cd636f96f891e2538b53dbf67f2"), + emulated.ValueOf[emulated.BLS12381Fp]("0x16b7d288798e5395f20d23bf89edb4d1d115c5dbddbcd30e123da489e726af41727364f2c28297ada8d26d98445f5416"), + emulated.ValueOf[emulated.BLS12381Fp]("0xbe0e079545f43e4b00cc912f8228ddcc6d19c9f0f69bbb0542eda0fc9dec916a20b15dc0fd2ededda39142311a5001d"), + emulated.ValueOf[emulated.BLS12381Fp]("0x8d9e5297186db2d9fb266eaac783182b70152c65550d881c5ecd87b6f0f5a6449f38db9dfa9cce202c6477faaf9b7ac"), + emulated.ValueOf[emulated.BLS12381Fp]("0x166007c08a99db2fc3ba8734ace9824b5eecfdfa8d0cf8ef5dd365bc400a0051d5fa9c01a58b1fb93d1a1399126a775c"), + emulated.ValueOf[emulated.BLS12381Fp]("0x16a3ef08be3ea7ea03bcddfabba6ff6ee5a4375efa1f4fd7feb34fd206357132b920f5b00801dee460ee415a15812ed9"), + emulated.ValueOf[emulated.BLS12381Fp]("0x1866c8ed336c61231a1be54fd1d74cc4f9fb0ce4c6af5920abc5750c4bf39b4852cfe2f7bb9248836b233d9d55535d4a"), + emulated.ValueOf[emulated.BLS12381Fp]("0x167a55cda70a6e1cea820597d94a84903216f763e13d87bb5308592e7ea7d4fbc7385ea3d529b35e346ef48bb8913f55"), + emulated.ValueOf[emulated.BLS12381Fp]("0x4d2f259eea405bd48f010a01ad2911d9c6dd039bb61a6290e591b36e636a5c871a5c29f4f83060400f8b49cba8f6aa8"), + emulated.ValueOf[emulated.BLS12381Fp]("0xaccbb67481d033ff5852c1e48c50c477f94ff8aefce42d28c0f9a88cea7913516f968986f7ebbea9684b529e2561092"), + emulated.ValueOf[emulated.BLS12381Fp]("0xad6b9514c767fe3c3613144b45f1496543346d98adf02267d5ceef9a00d9b8693000763e3b90ac11e99b138573345cc"), + emulated.ValueOf[emulated.BLS12381Fp]("0x2660400eb2e4f3b628bdd0d53cd76f2bf565b94e72927c1cb748df27942480e420517bd8714cc80d1fadc1326ed06f7"), + emulated.ValueOf[emulated.BLS12381Fp]("0xe0fa1d816ddc03e6b24255e0d7819c171c40f65e273b853324efcd6356caa205ca2f570f13497804415473a1d634b8f"), + }, + x) +} + +func g1EvalPolynomial(api frontend.API, monic bool, coefficients []FpElement, x FpElement) (FpElement, error) { + + res := coefficients[len(coefficients)-1] + fp, e := emulated.NewField[emulated.BLS12381Fp](api) + if e != nil { + return res, e + } + + if monic { + res = *fp.Add(&res, &x) + } + + for i := len(coefficients) - 2; i >= 0; i-- { + res = *fp.Mul(&res, &x) + res = *fp.Add(&res, &coefficients[i]) + } + return res, nil + +} From f8eef374eac700e2734ed92f565eeaeeb971b144 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 18 Mar 2025 09:43:17 -0400 Subject: [PATCH 019/105] feat(pectra): add BLS12_G2MSM --- std/algebra/emulated/sw_bls12381/g2.go | 177 ++++++++++++++++++++ std/algebra/emulated/sw_bls12381/g2_test.go | 109 ++++++++++++ std/evmprecompiles/14-blsg2msm.go | 31 ++++ std/evmprecompiles/bls_test.go | 143 ++++++++++++++++ 4 files changed, 460 insertions(+) create mode 100644 std/evmprecompiles/14-blsg2msm.go diff --git a/std/algebra/emulated/sw_bls12381/g2.go b/std/algebra/emulated/sw_bls12381/g2.go index 505b8c78b1..44c2684304 100644 --- a/std/algebra/emulated/sw_bls12381/g2.go +++ b/std/algebra/emulated/sw_bls12381/g2.go @@ -6,6 +6,7 @@ import ( bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/algebra/emulated/fields_bls12381" "github.com/consensys/gnark/std/math/emulated" ) @@ -13,6 +14,7 @@ import ( type G2 struct { api frontend.API fp *emulated.Field[BaseField] + fr *emulated.Field[ScalarField] *fields_bls12381.Ext2 u1, w *emulated.Element[BaseField] v *fields_bls12381.E2 @@ -46,6 +48,10 @@ func NewG2(api frontend.API) (*G2, error) { if err != nil { return nil, fmt.Errorf("new base api: %w", err) } + fr, err := emulated.NewField[ScalarField](api) + if err != nil { + return nil, fmt.Errorf("new scalar api: %w", err) + } w := emulated.ValueOf[BaseField]("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939436") u1 := emulated.ValueOf[BaseField]("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939437") v := fields_bls12381.E2{ @@ -55,6 +61,7 @@ func NewG2(api frontend.API) (*G2, error) { return &G2{ api: api, fp: fp, + fr: fr, Ext2: fields_bls12381.NewExt2(api), w: &w, u1: &u1, @@ -342,6 +349,56 @@ func (g2 G2) doubleAndAdd(p, q *G2Affine) *G2Affine { } } +// doubleAndAddSelect is the same as doubleAndAdd but computes either: +// +// 2p+q if b=1 or +// 2q+p if b=0 +// +// It first computes the x-coordinate of p+q via the slope(p,q) +// and then based on a Select adds either p or q. +func (g2 G2) doubleAndAddSelect(b frontend.Variable, p, q *G2Affine) *G2Affine { + mone := g2.fp.NewElement(-1) + + // compute λ1 = (q.y-p.y)/(q.x-p.x) + yqyp := g2.Ext2.Sub(&q.P.Y, &p.P.Y) + xqxp := g2.Ext2.Sub(&q.P.X, &p.P.X) + λ1 := g2.Ext2.DivUnchecked(yqyp, xqxp) + + // compute x2 = λ1²-p.x-q.x + x20 := g2.fp.Eval([][]*baseEl{{&λ1.A0, &λ1.A0}, {mone, &λ1.A1, &λ1.A1}, {mone, &p.P.X.A0}, {mone, &q.P.X.A0}}, []int{1, 1, 1, 1}) + x21 := g2.fp.Eval([][]*baseEl{{&λ1.A0, &λ1.A1}, {mone, &p.P.X.A1}, {mone, &q.P.X.A1}}, []int{2, 1, 1}) + x2 := &fields_bls12381.E2{A0: *x20, A1: *x21} + + // omit y2 computation + + // conditional second addition + t := g2.Select(b, p, q) + + // compute -λ2 = λ1+2*t.y/(x2-t.x) + ypyp := g2.Ext2.Add(&t.P.Y, &t.P.Y) + x2xp := g2.Ext2.Sub(x2, &t.P.X) + λ2 := g2.Ext2.DivUnchecked(ypyp, x2xp) + λ2 = g2.Ext2.Add(λ1, λ2) + + // compute x3 = (-λ2)²-t.x-x2 + x30 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &λ2.A0}, {mone, &λ2.A1, &λ2.A1}, {mone, &t.P.X.A0}, {mone, x20}}, []int{1, 1, 1, 1}) + x31 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &λ2.A1}, {mone, &t.P.X.A1}, {mone, x21}}, []int{2, 1, 1}) + x3 := &fields_bls12381.E2{A0: *x30, A1: *x31} + + // compute y3 = -λ2*(x3 - t.x)-t.y + y3 := g2.Ext2.Sub(x3, &t.P.X) + y30 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &y3.A0}, {mone, &λ2.A1, &y3.A1}, {mone, &t.P.Y.A0}}, []int{1, 1, 1}) + y31 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &y3.A1}, {&λ2.A1, &y3.A0}, {mone, &t.P.Y.A1}}, []int{1, 1, 1}) + y3 = &fields_bls12381.E2{A0: *y30, A1: *y31} + + return &G2Affine{ + P: g2AffP{ + X: *x3, + Y: *y3, + }, + } +} + func (g2 *G2) computeTwistEquation(Q *G2Affine) (left, right *fields_bls12381.E2) { // Twist: Y² == X³ + aX + b, where a=0 and b=4(1+u) // (X,Y) ∈ {Y² == X³ + aX + b} U (0,0) @@ -401,3 +458,123 @@ func (g2 *G2) IsEqual(p, q *G2Affine) frontend.Variable { yEqual := g2.Ext2.IsEqual(&p.P.Y, &q.P.Y) return g2.api.And(xEqual, yEqual) } + +// scalarMulGeneric computes [s]p and returns it. It doesn't modify p nor s. +// This function doesn't check that the p is on the curve. See AssertIsOnCurve. +// +// ⚠️ p must not be (0,0) and s must not be 0, unless [algopts.WithCompleteArithmetic] option is set. +// (0,0) is not on the curve but we conventionally take it as the +// neutral/infinity point as per the [EVM]. +// +// It computes the right-to-left variable-base double-and-add algorithm ([Joye07], Alg.1). +// +// Since we use incomplete formulas for the addition law, we need to start with +// a non-zero accumulator point (R0). To do this, we skip the LSB (bit at +// position 0) and proceed assuming it was 1. At the end, we conditionally +// subtract the initial value (p) if LSB is 1. We also handle the bits at +// positions 1 and n-1 outside of the loop to optimize the number of +// constraints using [ELM03] (Section 3.1) +// +// [ELM03]: https://arxiv.org/pdf/math/0208038.pdf +// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf +// [Joye07]: https://www.iacr.org/archive/ches2007/47270135/47270135.pdf +func (g2 *G2) scalarMulGeneric(p *G2Affine, s *Scalar, opts ...algopts.AlgebraOption) *G2Affine { + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(fmt.Sprintf("parse opts: %v", err)) + } + var selector frontend.Variable + if cfg.CompleteArithmetic { + // if p=(0,0) we assign a dummy (0,1) to p and continue + selector = g2.api.And(g2.Ext2.IsZero(&p.P.X), g2.Ext2.IsZero(&p.P.Y)) + one := g2.Ext2.One() + p = g2.Select(selector, &G2Affine{P: g2AffP{X: *one, Y: *one}, Lines: nil}, p) + } + + var st ScalarField + sr := g2.fr.Reduce(s) + sBits := g2.fr.ToBits(sr) + n := st.Modulus().BitLen() + if cfg.NbScalarBits > 2 && cfg.NbScalarBits < n { + n = cfg.NbScalarBits + } + + // i = 1 + Rb := g2.triple(p) + R0 := g2.Select(sBits[1], Rb, p) + R1 := g2.Select(sBits[1], p, Rb) + + for i := 2; i < n-1; i++ { + Rb = g2.doubleAndAddSelect(sBits[i], R0, R1) + R0 = g2.Select(sBits[i], Rb, R0) + R1 = g2.Select(sBits[i], R1, Rb) + } + + // i = n-1 + Rb = g2.doubleAndAddSelect(sBits[n-1], R0, R1) + R0 = g2.Select(sBits[n-1], Rb, R0) + + // i = 0 + // we use AddUnified instead of Add. This is because: + // - when s=0 then R0=P and AddUnified(P, -P) = (0,0). We return (0,0). + // - when s=1 then R0=P AddUnified(Q, -Q) is well defined. We return R0=P. + R0 = g2.Select(sBits[0], R0, g2.AddUnified(R0, g2.neg(p))) + + if cfg.CompleteArithmetic { + // if p=(0,0), return (0,0) + zero := g2.Ext2.Zero() + R0 = g2.Select(selector, &G2Affine{P: g2AffP{X: *zero, Y: *zero}, Lines: nil}, R0) + } + + return R0 +} + +// MultiScalarMul computes the multi scalar multiplication of the points P and +// scalars s. It returns an error if the length of the slices mismatch. If the +// input slices are empty, then returns point at infinity. +func (g2 *G2) MultiScalarMul(p []*G2Affine, s []*Scalar, opts ...algopts.AlgebraOption) (*G2Affine, error) { + + if len(p) == 0 { + return &G2Affine{ + P: g2AffP{ + X: *g2.Ext2.Zero(), + Y: *g2.Ext2.Zero(), + }, + Lines: nil, + }, nil + } + cfg, err := algopts.NewConfig(opts...) + if err != nil { + return nil, fmt.Errorf("new config: %w", err) + } + addFn := g2.add + if cfg.CompleteArithmetic { + addFn = g2.AddUnified + } + if !cfg.FoldMulti { + // the scalars are unique + if len(p) != len(s) { + return nil, fmt.Errorf("mismatching points and scalars slice lengths") + } + n := len(p) + res := g2.scalarMulGeneric(p[0], s[0], opts...) + for i := 1; i < n; i++ { + q := g2.scalarMulGeneric(p[i], s[i], opts...) + res = addFn(res, q) + } + return res, nil + } else { + // scalars are powers + if len(s) == 0 { + return nil, fmt.Errorf("need scalar for folding") + } + gamma := s[0] + res := g2.scalarMulGeneric(p[len(p)-1], gamma, opts...) + for i := len(p) - 2; i > 0; i-- { + res = addFn(p[i], res) + res = g2.scalarMulGeneric(res, gamma, opts...) + } + res = addFn(p[0], res) + return res, nil + } +} diff --git a/std/algebra/emulated/sw_bls12381/g2_test.go b/std/algebra/emulated/sw_bls12381/g2_test.go index 7fae55bf8e..b619084241 100644 --- a/std/algebra/emulated/sw_bls12381/g2_test.go +++ b/std/algebra/emulated/sw_bls12381/g2_test.go @@ -6,10 +6,47 @@ import ( "github.com/consensys/gnark-crypto/ecc" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + fr_bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/emulated/fields_bls12381" + "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/test" ) +type mulG2Circuit struct { + In, Res G2Affine + S Scalar +} + +func (c *mulG2Circuit) Define(api frontend.API) error { + g2, err := NewG2(api) + if err != nil { + panic(err) + } + res := g2.scalarMulGeneric(&c.In, &c.S) + g2.AssertIsEqual(res, &c.Res) + return nil +} + +func TestScalarMulG2TestSolve(t *testing.T) { + assert := test.NewAssert(t) + var r fr_bls12381.Element + _, _ = r.SetRandom() + s := new(big.Int) + r.BigInt(s) + var res bls12381.G2Affine + _, _, _, gen := bls12381.Generators() + res.ScalarMultiplication(&gen, s) + + witness := mulG2Circuit{ + In: NewG2Affine(gen), + S: NewScalar(r), + Res: NewG2Affine(res), + } + err := test.IsSolved(&mulG2Circuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} + type addG2Circuit struct { In1, In2 G2Affine Res G2Affine @@ -130,3 +167,75 @@ func TestScalarMulG2BySeedTestSolve(t *testing.T) { err := test.IsSolved(&scalarMulG2BySeedCircuit{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } + +type MultiScalarMulTest struct { + Points []G2Affine + Scalars []Scalar + Res G2Affine +} + +func (c *MultiScalarMulTest) Define(api frontend.API) error { + g2, err := NewG2(api) + if err != nil { + panic(err) + } + ps := make([]*G2Affine, len(c.Points)) + for i := range c.Points { + ps[i] = &c.Points[i] + } + ss := make([]*Scalar, len(c.Scalars)) + for i := range c.Scalars { + ss[i] = &c.Scalars[i] + } + res, err := g2.MultiScalarMul(ps, ss) + if err != nil { + return err + } + g2.AssertIsEqual(res, &c.Res) + return nil +} + +func TestMultiScalarMul(t *testing.T) { + assert := test.NewAssert(t) + nbLen := 4 + P := make([]bls12381.G2Affine, nbLen) + S := make([]fr_bls12381.Element, nbLen) + for i := 0; i < nbLen; i++ { + S[i].SetRandom() + P[i].ScalarMultiplicationBase(S[i].BigInt(new(big.Int))) + } + var res bls12381.G2Affine + _, err := res.MultiExp(P, S, ecc.MultiExpConfig{}) + + assert.NoError(err) + cP := make([]G2Affine, len(P)) + for i := range cP { + cP[i] = G2Affine{ + P: g2AffP{ + X: fields_bls12381.E2{A0: emulated.ValueOf[emulated.BLS12381Fp](P[i].X.A0), A1: emulated.ValueOf[emulated.BLS12381Fp](P[i].X.A1)}, + Y: fields_bls12381.E2{A0: emulated.ValueOf[emulated.BLS12381Fp](P[i].Y.A0), A1: emulated.ValueOf[emulated.BLS12381Fp](P[i].Y.A1)}, + }, + Lines: nil, + } + } + cS := make([]Scalar, len(S)) + for i := range cS { + cS[i] = emulated.ValueOf[emulated.BLS12381Fr](S[i]) + } + assignment := MultiScalarMulTest{ + Points: cP, + Scalars: cS, + Res: G2Affine{ + P: g2AffP{ + X: fields_bls12381.E2{A0: emulated.ValueOf[emulated.BLS12381Fp](res.X.A0), A1: emulated.ValueOf[emulated.BLS12381Fp](res.X.A1)}, + Y: fields_bls12381.E2{A0: emulated.ValueOf[emulated.BLS12381Fp](res.Y.A0), A1: emulated.ValueOf[emulated.BLS12381Fp](res.Y.A1)}, + }, + Lines: nil, + }, + } + err = test.IsSolved(&MultiScalarMulTest{ + Points: make([]G2Affine, nbLen), + Scalars: make([]Scalar, nbLen), + }, &assignment, ecc.BN254.ScalarField()) + assert.NoError(err) +} diff --git a/std/evmprecompiles/14-blsg2msm.go b/std/evmprecompiles/14-blsg2msm.go new file mode 100644 index 0000000000..e502e32955 --- /dev/null +++ b/std/evmprecompiles/14-blsg2msm.go @@ -0,0 +1,31 @@ +package evmprecompiles + +import ( + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/algopts" + "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" + "github.com/consensys/gnark/std/math/emulated" +) + +// ECMSMG2BLS implements [BLS12_G2MSM] precompile contract at address 0x0e. +// +// [BLS12_G2MSM]: https://eips.ethereum.org/EIPS/eip-2537 +func ECMSMG2BLS(api frontend.API, P []*sw_bls12381.G2Affine, s []*emulated.Element[emulated.BLS12381Fr]) *sw_bls12381.G2Affine { + g2, err := sw_bls12381.NewG2(api) + if err != nil { + panic(err) + } + + // Check that Pᵢ are on G2 + for _, p := range P { + g2.AssertIsOnG2(p) + } + + // Compute the MSM + res, err := g2.MultiScalarMul(P, s, algopts.WithCompleteArithmetic()) + if err != nil { + panic(err) + } + + return res +} diff --git a/std/evmprecompiles/bls_test.go b/std/evmprecompiles/bls_test.go index 81507ca612..ce15b63e26 100644 --- a/std/evmprecompiles/bls_test.go +++ b/std/evmprecompiles/bls_test.go @@ -10,6 +10,8 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" fr_bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/scs" + "github.com/consensys/gnark/profile" "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" "github.com/consensys/gnark/std/math/emulated" @@ -294,3 +296,144 @@ func TestECMSMG1BLSCircuit(t *testing.T) { assert.NoError(err) } } + +type ecmulBLSG2Circuit struct { + X0 sw_bls12381.G2Affine + U sw_bls12381.Scalar + Expected sw_bls12381.G2Affine +} + +func (c *ecmulBLSG2Circuit) Define(api frontend.API) error { + g2, err := sw_bls12381.NewG2(api) + if err != nil { + return err + } + res := ECMSMG2BLS(api, + []*sw_bls12381.G2Affine{&c.X0}, + []*sw_bls12381.Scalar{&c.U}, + ) + g2.AssertIsEqual(res, &c.Expected) + return nil +} + +func testRoutineECMulG2BLS(t *testing.T) (circ, wit frontend.Circuit) { + _, _, _, G := bls12381.Generators() + var u, v fr.Element + u.SetRandom() + v.SetRandom() + var P bls12381.G2Affine + P.ScalarMultiplication(&G, u.BigInt(new(big.Int))) + var expected bls12381.G2Affine + expected.ScalarMultiplication(&P, v.BigInt(new(big.Int))) + circuit := ecmulBLSG2Circuit{} + witness := ecmulBLSG2Circuit{ + X0: sw_bls12381.NewG2Affine(P), + U: sw_bls12381.NewScalar(v), + Expected: sw_bls12381.NewG2Affine(expected), + } + return &circuit, &witness +} + +func TestECMulBLSG2CircuitFull(t *testing.T) { + assert := test.NewAssert(t) + circuit, witness := testRoutineECMulG2BLS(t) + assert.CheckCircuit(circuit, test.WithValidAssignment(witness), test.WithCurves(ecc.BN254)) +} + +type ecmsmg2BLSCircuit struct { + Points [10]sw_bls12381.G2Affine + Scalars [10]sw_bls12381.Scalar + Res sw_bls12381.G2Affine + n int +} + +func (c *ecmsmg2BLSCircuit) Define(api frontend.API) error { + g2, err := sw_bls12381.NewG2(api) + if err != nil { + panic(err) + } + ps := make([]*sw_bls12381.G2Affine, c.n) + for i := range c.n { + ps[i] = &c.Points[i] + } + ss := make([]*sw_bls12381.Scalar, c.n) + for i := range c.n { + ss[i] = &c.Scalars[i] + } + res := ECMSMG2BLS(api, ps, ss) + g2.AssertIsEqual(res, &c.Res) + return nil +} + +func TestECMSMG2BLSCircuit(t *testing.T) { + assert := test.NewAssert(t) + P := make([]bls12381.G2Affine, 10) + S := make([]fr_bls12381.Element, 10) + for i := 0; i < 10; i++ { + S[i].SetRandom() + P[i].ScalarMultiplicationBase(S[i].BigInt(new(big.Int))) + } + + var cP [10]sw_bls12381.G2Affine + for i := range cP { + cP[i] = sw_bls12381.NewG2Affine(P[i]) + } + var cS [10]emulated.Element[emulated.BLS12381Fr] + for i := range cS { + cS[i] = emulated.ValueOf[emulated.BLS12381Fr](S[i]) + } + + for i := 1; i < 11; i++ { + var res bls12381.G2Affine + _, err := res.MultiExp(P[:i], S[:i], ecc.MultiExpConfig{}) + assert.NoError(err) + err = test.IsSolved(&ecmsmg2BLSCircuit{n: i}, &ecmsmg2BLSCircuit{ + n: i, + Points: cP, + Scalars: cS, + Res: sw_bls12381.NewG2Affine(res), + }, ecc.BN254.ScalarField()) + assert.NoError(err) + } +} + +// bench +func BenchmarkG2MSM1(b *testing.B) { + c := ecmsmg2BLSCircuit{n: 1} + p := profile.Start() + _, _ = frontend.Compile(ecc.BLS12_377.ScalarField(), scs.NewBuilder, &c) + p.Stop() + fmt.Println("BLS12_G2MSM (1 pair): ", p.NbConstraints()) +} + +func BenchmarkG2MSM2(b *testing.B) { + c := ecmsmg2BLSCircuit{n: 2} + p := profile.Start() + _, _ = frontend.Compile(ecc.BLS12_377.ScalarField(), scs.NewBuilder, &c) + p.Stop() + fmt.Println("BLS12_G2MSM (2 pair): ", p.NbConstraints()) +} + +func BenchmarkG2MSM3(b *testing.B) { + c := ecmsmg2BLSCircuit{n: 3} + p := profile.Start() + _, _ = frontend.Compile(ecc.BLS12_377.ScalarField(), scs.NewBuilder, &c) + p.Stop() + fmt.Println("BLS12_G2MSM (3 pair): ", p.NbConstraints()) +} + +func BenchmarkG2MSM5(b *testing.B) { + c := ecmsmg2BLSCircuit{n: 5} + p := profile.Start() + _, _ = frontend.Compile(ecc.BLS12_377.ScalarField(), scs.NewBuilder, &c) + p.Stop() + fmt.Println("BLS12_G2MSM (5 pair): ", p.NbConstraints()) +} + +func BenchmarkG2MSM10(b *testing.B) { + c := ecmsmg2BLSCircuit{n: 10} + p := profile.Start() + _, _ = frontend.Compile(ecc.BLS12_377.ScalarField(), scs.NewBuilder, &c) + p.Stop() + fmt.Println("BLS12_G2MSM (10 pair): ", p.NbConstraints()) +} From 7ea03d5736b7b51c9db79927e90921a34ce4ed18 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 18 Mar 2025 10:24:29 -0400 Subject: [PATCH 020/105] refactor: clean code --- std/evmprecompiles/bls_test.go | 43 ---------------------------------- 1 file changed, 43 deletions(-) diff --git a/std/evmprecompiles/bls_test.go b/std/evmprecompiles/bls_test.go index ce15b63e26..80237684fd 100644 --- a/std/evmprecompiles/bls_test.go +++ b/std/evmprecompiles/bls_test.go @@ -10,8 +10,6 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" fr_bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/frontend/cs/scs" - "github.com/consensys/gnark/profile" "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" "github.com/consensys/gnark/std/math/emulated" @@ -396,44 +394,3 @@ func TestECMSMG2BLSCircuit(t *testing.T) { assert.NoError(err) } } - -// bench -func BenchmarkG2MSM1(b *testing.B) { - c := ecmsmg2BLSCircuit{n: 1} - p := profile.Start() - _, _ = frontend.Compile(ecc.BLS12_377.ScalarField(), scs.NewBuilder, &c) - p.Stop() - fmt.Println("BLS12_G2MSM (1 pair): ", p.NbConstraints()) -} - -func BenchmarkG2MSM2(b *testing.B) { - c := ecmsmg2BLSCircuit{n: 2} - p := profile.Start() - _, _ = frontend.Compile(ecc.BLS12_377.ScalarField(), scs.NewBuilder, &c) - p.Stop() - fmt.Println("BLS12_G2MSM (2 pair): ", p.NbConstraints()) -} - -func BenchmarkG2MSM3(b *testing.B) { - c := ecmsmg2BLSCircuit{n: 3} - p := profile.Start() - _, _ = frontend.Compile(ecc.BLS12_377.ScalarField(), scs.NewBuilder, &c) - p.Stop() - fmt.Println("BLS12_G2MSM (3 pair): ", p.NbConstraints()) -} - -func BenchmarkG2MSM5(b *testing.B) { - c := ecmsmg2BLSCircuit{n: 5} - p := profile.Start() - _, _ = frontend.Compile(ecc.BLS12_377.ScalarField(), scs.NewBuilder, &c) - p.Stop() - fmt.Println("BLS12_G2MSM (5 pair): ", p.NbConstraints()) -} - -func BenchmarkG2MSM10(b *testing.B) { - c := ecmsmg2BLSCircuit{n: 10} - p := profile.Start() - _, _ = frontend.Compile(ecc.BLS12_377.ScalarField(), scs.NewBuilder, &c) - p.Stop() - fmt.Println("BLS12_G2MSM (10 pair): ", p.NbConstraints()) -} From b9e729e1cd69e71c1e51df08655b6f042e0f5b77 Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Tue, 18 Mar 2025 19:42:29 +0100 Subject: [PATCH 021/105] feat: mul by z ok --- std/evmprecompiles/16-blsmaptog1.go | 102 +++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 8 deletions(-) diff --git a/std/evmprecompiles/16-blsmaptog1.go b/std/evmprecompiles/16-blsmaptog1.go index 9a2539871d..2df7294959 100644 --- a/std/evmprecompiles/16-blsmaptog1.go +++ b/std/evmprecompiles/16-blsmaptog1.go @@ -2,10 +2,13 @@ package evmprecompiles import ( "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" "github.com/consensys/gnark/std/math/emulated" ) +type FpApi = emulated.Field[emulated.BLS12381Fp] type FpElement = emulated.Element[emulated.BLS12381Fp] +type G1Affine = sw_emulated.AffinePoint[emulated.BLS12381Fp] func g1IsogenyXNumerator(api frontend.API, x FpElement) (FpElement, error) { @@ -100,22 +103,105 @@ func g1IsogenyYDenominator(api frontend.API, x FpElement) (FpElement, error) { x) } -func g1EvalPolynomial(api frontend.API, monic bool, coefficients []FpElement, x FpElement) (FpElement, error) { +func g1EvalPolynomial(api *FpApi, monic bool, coefficients []FpElement, x FpElement) (FpElement, error) { res := coefficients[len(coefficients)-1] - fp, e := emulated.NewField[emulated.BLS12381Fp](api) - if e != nil { - return res, e - } if monic { - res = *fp.Add(&res, &x) + res = *api.Add(&res, &x) } for i := len(coefficients) - 2; i >= 0; i-- { - res = *fp.Mul(&res, &x) - res = *fp.Add(&res, &coefficients[i]) + res = *api.Mul(&res, &x) + res = *api.Add(&res, &coefficients[i]) } return res, nil } + +// g1MulByZ multiplies x by [11] and stores the result in z +func g1MulByZ(api *FpApi, z, x *FpElement) { + eleven := emulated.ValueOf[emulated.BLS12381Fp]("11") + *z = *api.Mul(&eleven, x) +} + +// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-simplified-swu-method +// MapToCurve1 implements the SSWU map +// No cofactor clearing or isogeny +func MapToCurve1(api frontend.API, u *FpElement) (G1Affine, error) { + + var res G1Affine + + fpApi, err := emulated.NewField[emulated.BLS12381Fp](api) + if err != nil { + return res, err + } + + sswuIsoCurveCoeffA := emulated.ValueOf[emulated.BLS12381Fp]("0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d") + sswuIsoCurveCoeffB := emulated.ValueOf[emulated.BLS12381Fp]("0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0") + + tv1 := fpApi.Mul(u, u) // 1. tv1 = u² + + //mul tv1 by Z + g1MulByZ(&tv1, &tv1) // 2. tv1 = Z * tv1 + + // var tv2 fp.Element + // tv2.Square(&tv1) // 3. tv2 = tv1² + // tv2.Add(&tv2, &tv1) // 4. tv2 = tv2 + tv1 + + // var tv3 fp.Element + // var tv4 fp.Element + // tv4.SetOne() + // tv3.Add(&tv2, &tv4) // 5. tv3 = tv2 + 1 + // tv3.Mul(&tv3, &sswuIsoCurveCoeffB) // 6. tv3 = B * tv3 + + // tv2NZero := g1NotZero(&tv2) + + // // tv4 = Z + // tv4 = fp.Element{9830232086645309404, 1112389714365644829, 8603885298299447491, 11361495444721768256, 5788602283869803809, 543934104870762216} + + // tv2.Neg(&tv2) + // tv4.Select(int(tv2NZero), &tv4, &tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) + // tv4.Mul(&tv4, &sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 + + // tv2.Square(&tv3) // 9. tv2 = tv3² + + // var tv6 fp.Element + // tv6.Square(&tv4) // 10. tv6 = tv4² + + // var tv5 fp.Element + // tv5.Mul(&tv6, &sswuIsoCurveCoeffA) // 11. tv5 = A * tv6 + + // tv2.Add(&tv2, &tv5) // 12. tv2 = tv2 + tv5 + // tv2.Mul(&tv2, &tv3) // 13. tv2 = tv2 * tv3 + // tv6.Mul(&tv6, &tv4) // 14. tv6 = tv6 * tv4 + + // tv5.Mul(&tv6, &sswuIsoCurveCoeffB) // 15. tv5 = B * tv6 + // tv2.Add(&tv2, &tv5) // 16. tv2 = tv2 + tv5 + + // var x fp.Element + // x.Mul(&tv1, &tv3) // 17. x = tv1 * tv3 + + // var y1 fp.Element + // gx1NSquare := g1SqrtRatio(&y1, &tv2, &tv6) // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) + + // var y fp.Element + // y.Mul(&tv1, u) // 19. y = tv1 * u + + // y.Mul(&y, &y1) // 20. y = y * y1 + + // x.Select(int(gx1NSquare), &tv3, &x) // 21. x = CMOV(x, tv3, is_gx1_square) + // y.Select(int(gx1NSquare), &y1, &y) // 22. y = CMOV(y, y1, is_gx1_square) + + // y1.Neg(&y) + // y.Select(int(g1Sgn0(u)^g1Sgn0(&y)), &y, &y1) + + // // 23. e1 = sgn0(u) == sgn0(y) + // // 24. y = CMOV(-y, y, e1) + + // x.Div(&x, &tv4) // 25. x = x / tv4 + + // return G1Affine{x, y} + + return res, nil +} From c9f6df4b9b13a38e75fb4bced260a9e8219a62ae Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 18 Mar 2025 15:59:47 -0400 Subject: [PATCH 022/105] style(pectra): rename methods --- std/evmprecompiles/11-blsg1add.go | 4 +- std/evmprecompiles/bls_test.go | 284 +++++++++++------------------- 2 files changed, 101 insertions(+), 187 deletions(-) diff --git a/std/evmprecompiles/11-blsg1add.go b/std/evmprecompiles/11-blsg1add.go index ea6df4714f..20c4a19e9b 100644 --- a/std/evmprecompiles/11-blsg1add.go +++ b/std/evmprecompiles/11-blsg1add.go @@ -6,10 +6,10 @@ import ( "github.com/consensys/gnark/std/math/emulated" ) -// ECAddBLS implements [BLS12_G1ADD] precompile contract at address 0x0b. +// ECAddG1BLS implements [BLS12_G1ADD] precompile contract at address 0x0b. // // [BLS12_G1ADD]: https://eips.ethereum.org/EIPS/eip-2537 -func ECAddBLS(api frontend.API, P, Q *sw_emulated.AffinePoint[emulated.BLS12381Fp]) *sw_emulated.AffinePoint[emulated.BLS12381Fp] { +func ECAddG1BLS(api frontend.API, P, Q *sw_emulated.AffinePoint[emulated.BLS12381Fp]) *sw_emulated.AffinePoint[emulated.BLS12381Fp] { curve, err := sw_emulated.New[emulated.BLS12381Fp, emulated.BLS12381Fr](api, sw_emulated.GetBLS12381Params()) if err != nil { panic(err) diff --git a/std/evmprecompiles/bls_test.go b/std/evmprecompiles/bls_test.go index 80237684fd..b5cee23c6f 100644 --- a/std/evmprecompiles/bls_test.go +++ b/std/evmprecompiles/bls_test.go @@ -16,23 +16,24 @@ import ( "github.com/consensys/gnark/test" ) -type ecaddBLSCircuit struct { +// 11: G1 Add +type ecaddG1BLSCircuit struct { X0 sw_emulated.AffinePoint[emulated.BLS12381Fp] X1 sw_emulated.AffinePoint[emulated.BLS12381Fp] Expected sw_emulated.AffinePoint[emulated.BLS12381Fp] } -func (c *ecaddBLSCircuit) Define(api frontend.API) error { +func (c *ecaddG1BLSCircuit) Define(api frontend.API) error { curve, err := sw_emulated.New[emulated.BLS12381Fp, emulated.BLS12381Fr](api, sw_emulated.GetBLS12381Params()) if err != nil { return err } - res := ECAddBLS(api, &c.X0, &c.X1) + res := ECAddG1BLS(api, &c.X0, &c.X1) curve.AssertIsEqual(res, &c.Expected) return nil } -func testRoutineECAddBLS() (circ, wit frontend.Circuit) { +func testRoutineECAddG1BLS() (circ, wit frontend.Circuit) { _, _, G, _ := bls12381.Generators() var u, v fr.Element u.SetRandom() @@ -42,8 +43,8 @@ func testRoutineECAddBLS() (circ, wit frontend.Circuit) { Q.ScalarMultiplication(&G, v.BigInt(new(big.Int))) var expected bls12381.G1Affine expected.Add(&P, &Q) - circuit := ecaddBLSCircuit{} - witness := ecaddBLSCircuit{ + circuit := ecaddG1BLSCircuit{} + witness := ecaddG1BLSCircuit{ X0: sw_emulated.AffinePoint[emulated.BLS12381Fp]{ X: emulated.ValueOf[emulated.BLS12381Fp](P.X), Y: emulated.ValueOf[emulated.BLS12381Fp](P.Y), @@ -60,178 +61,20 @@ func testRoutineECAddBLS() (circ, wit frontend.Circuit) { return &circuit, &witness } -func TestECAddBLSCircuitShort(t *testing.T) { +func TestECAddG1BLSCircuitShort(t *testing.T) { assert := test.NewAssert(t) - circuit, witness := testRoutineECAddBLS() + circuit, witness := testRoutineECAddG1BLS() err := test.IsSolved(circuit, witness, ecc.BN254.ScalarField()) assert.NoError(err) } -type ecaddG2BLSCircuit struct { - X0 sw_bls12381.G2Affine - X1 sw_bls12381.G2Affine - Expected sw_bls12381.G2Affine -} - -func (c *ecaddG2BLSCircuit) Define(api frontend.API) error { - g2, err := sw_bls12381.NewG2(api) - if err != nil { - panic(err) - } - res := ECAddG2BLS(api, &c.X0, &c.X1) - g2.AssertIsEqual(res, &c.Expected) - return nil -} - -func testRoutineECAddG2BLS() (circ, wit frontend.Circuit) { - _, _, _, G := bls12381.Generators() - var u, v fr.Element - u.SetRandom() - v.SetRandom() - var P, Q bls12381.G2Affine - P.ScalarMultiplication(&G, u.BigInt(new(big.Int))) - Q.ScalarMultiplication(&G, v.BigInt(new(big.Int))) - var expected bls12381.G2Affine - expected.Add(&P, &Q) - circuit := ecaddG2BLSCircuit{} - witness := ecaddG2BLSCircuit{ - X0: sw_bls12381.NewG2Affine(P), - X1: sw_bls12381.NewG2Affine(Q), - Expected: sw_bls12381.NewG2Affine(expected), - } - return &circuit, &witness -} - -func TestECAddG2BLSCircuitShort(t *testing.T) { - assert := test.NewAssert(t) - circuit, witness := testRoutineECAddG2BLS() - err := test.IsSolved(circuit, witness, ecc.BN254.ScalarField()) - assert.NoError(err) -} - -func TestECAddG2BLSCircuitFull(t *testing.T) { - assert := test.NewAssert(t) - circuit, witness := testRoutineECAddG2BLS() - assert.CheckCircuit(circuit, test.WithValidAssignment(witness)) -} -func TestECAddBLSCircuitFull(t *testing.T) { +func TestECAddG1BLSCircuitFull(t *testing.T) { assert := test.NewAssert(t) circuit, witness := testRoutineECAdd() assert.CheckCircuit(circuit, test.WithValidAssignment(witness)) } -type ecmulBLSCircuit struct { - X0 sw_emulated.AffinePoint[emulated.BLS12381Fp] - U emulated.Element[emulated.BLS12381Fr] - Expected sw_emulated.AffinePoint[emulated.BLS12381Fp] -} - -func (c *ecmulBLSCircuit) Define(api frontend.API) error { - curve, err := sw_emulated.New[emulated.BLS12381Fp, emulated.BLS12381Fr](api, sw_emulated.GetBLS12381Params()) - if err != nil { - return err - } - res := ECMSMG1BLS(api, - []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.X0}, - []*emulated.Element[emulated.BLS12381Fr]{&c.U}, - ) - curve.AssertIsEqual(res, &c.Expected) - return nil -} - -func testRoutineECMulBLS(t *testing.T) (circ, wit frontend.Circuit) { - _, _, G, _ := bls12381.Generators() - var u, v fr.Element - u.SetRandom() - v.SetRandom() - var P bls12381.G1Affine - P.ScalarMultiplication(&G, u.BigInt(new(big.Int))) - var expected bls12381.G1Affine - expected.ScalarMultiplication(&P, v.BigInt(new(big.Int))) - circuit := ecmulBLSCircuit{} - witness := ecmulBLSCircuit{ - X0: sw_emulated.AffinePoint[emulated.BLS12381Fp]{ - X: emulated.ValueOf[emulated.BLS12381Fp](P.X), - Y: emulated.ValueOf[emulated.BLS12381Fp](P.Y), - }, - U: emulated.ValueOf[emulated.BLS12381Fr](v), - Expected: sw_emulated.AffinePoint[emulated.BLS12381Fp]{ - X: emulated.ValueOf[emulated.BLS12381Fp](expected.X), - Y: emulated.ValueOf[emulated.BLS12381Fp](expected.Y), - }, - } - return &circuit, &witness -} - -func TestECMulBLSCircuitFull(t *testing.T) { - assert := test.NewAssert(t) - circuit, witness := testRoutineECMulBLS(t) - assert.CheckCircuit(circuit, test.WithValidAssignment(witness), test.WithCurves(ecc.BN254)) -} - -type ecPairBLSBatchCircuit struct { - P sw_bls12381.G1Affine - NP sw_bls12381.G1Affine - DP sw_bls12381.G1Affine - Q sw_bls12381.G2Affine - n int -} - -func (c *ecPairBLSBatchCircuit) Define(api frontend.API) error { - Q := make([]*sw_bls12381.G2Affine, c.n) - for i := range Q { - Q[i] = &c.Q - } - switch c.n { - case 2: - ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP}, Q) - case 3: - ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.NP, &c.NP, &c.DP}, Q) - case 4: - ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP}, Q) - case 5: - ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.NP, &c.NP, &c.DP}, Q) - case 6: - ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP}, Q) - case 7: - ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.NP, &c.NP, &c.DP}, Q) - case 8: - ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP}, Q) - case 9: - ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP, &c.NP, &c.NP, &c.DP}, Q) - default: - return fmt.Errorf("not handled %d", c.n) - } - return nil -} - -func TestECPairBLSBLSMulBatch(t *testing.T) { - assert := test.NewAssert(t) - _, _, p, q := bls12381.Generators() - - var u, v fr.Element - u.SetRandom() - v.SetRandom() - - p.ScalarMultiplication(&p, u.BigInt(new(big.Int))) - q.ScalarMultiplication(&q, v.BigInt(new(big.Int))) - - var dp, np bls12381.G1Affine - dp.Double(&p) - np.Neg(&p) - - for i := 2; i < 10; i++ { - err := test.IsSolved(&ecPairBLSBatchCircuit{n: i}, &ecPairBLSBatchCircuit{ - n: i, - P: sw_bls12381.NewG1Affine(p), - NP: sw_bls12381.NewG1Affine(np), - DP: sw_bls12381.NewG1Affine(dp), - Q: sw_bls12381.NewG2Affine(q), - }, ecc.BN254.ScalarField()) - assert.NoError(err) - } -} - +// 12: G1 MSM type ecmsmg1BLSCircuit struct { Points [10]sw_emulated.AffinePoint[emulated.BLS12381Fp] Scalars [10]emulated.Element[emulated.BLS12381Fr] @@ -295,49 +138,56 @@ func TestECMSMG1BLSCircuit(t *testing.T) { } } -type ecmulBLSG2Circuit struct { +// 13: G2 Add +type ecaddG2BLSCircuit struct { X0 sw_bls12381.G2Affine - U sw_bls12381.Scalar + X1 sw_bls12381.G2Affine Expected sw_bls12381.G2Affine } -func (c *ecmulBLSG2Circuit) Define(api frontend.API) error { +func (c *ecaddG2BLSCircuit) Define(api frontend.API) error { g2, err := sw_bls12381.NewG2(api) if err != nil { - return err + panic(err) } - res := ECMSMG2BLS(api, - []*sw_bls12381.G2Affine{&c.X0}, - []*sw_bls12381.Scalar{&c.U}, - ) + res := ECAddG2BLS(api, &c.X0, &c.X1) g2.AssertIsEqual(res, &c.Expected) return nil } -func testRoutineECMulG2BLS(t *testing.T) (circ, wit frontend.Circuit) { +func testRoutineECAddG2BLS() (circ, wit frontend.Circuit) { _, _, _, G := bls12381.Generators() var u, v fr.Element u.SetRandom() v.SetRandom() - var P bls12381.G2Affine + var P, Q bls12381.G2Affine P.ScalarMultiplication(&G, u.BigInt(new(big.Int))) + Q.ScalarMultiplication(&G, v.BigInt(new(big.Int))) var expected bls12381.G2Affine - expected.ScalarMultiplication(&P, v.BigInt(new(big.Int))) - circuit := ecmulBLSG2Circuit{} - witness := ecmulBLSG2Circuit{ + expected.Add(&P, &Q) + circuit := ecaddG2BLSCircuit{} + witness := ecaddG2BLSCircuit{ X0: sw_bls12381.NewG2Affine(P), - U: sw_bls12381.NewScalar(v), + X1: sw_bls12381.NewG2Affine(Q), Expected: sw_bls12381.NewG2Affine(expected), } return &circuit, &witness } -func TestECMulBLSG2CircuitFull(t *testing.T) { +func TestECAddG2BLSCircuitShort(t *testing.T) { + assert := test.NewAssert(t) + circuit, witness := testRoutineECAddG2BLS() + err := test.IsSolved(circuit, witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} + +func TestECAddG2BLSCircuitFull(t *testing.T) { assert := test.NewAssert(t) - circuit, witness := testRoutineECMulG2BLS(t) - assert.CheckCircuit(circuit, test.WithValidAssignment(witness), test.WithCurves(ecc.BN254)) + circuit, witness := testRoutineECAddG2BLS() + assert.CheckCircuit(circuit, test.WithValidAssignment(witness)) } +// 14: G2 MSM type ecmsmg2BLSCircuit struct { Points [10]sw_bls12381.G2Affine Scalars [10]sw_bls12381.Scalar @@ -394,3 +244,67 @@ func TestECMSMG2BLSCircuit(t *testing.T) { assert.NoError(err) } } + +// 15: multi-pairing check +type ecPairBLSBatchCircuit struct { + P sw_bls12381.G1Affine + NP sw_bls12381.G1Affine + DP sw_bls12381.G1Affine + Q sw_bls12381.G2Affine + n int +} + +func (c *ecPairBLSBatchCircuit) Define(api frontend.API) error { + Q := make([]*sw_bls12381.G2Affine, c.n) + for i := range Q { + Q[i] = &c.Q + } + switch c.n { + case 2: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP}, Q) + case 3: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.NP, &c.NP, &c.DP}, Q) + case 4: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP}, Q) + case 5: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.NP, &c.NP, &c.DP}, Q) + case 6: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP}, Q) + case 7: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.NP, &c.NP, &c.DP}, Q) + case 8: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP}, Q) + case 9: + ECPairBLS(api, []*sw_emulated.AffinePoint[emulated.BLS12381Fp]{&c.P, &c.NP, &c.P, &c.NP, &c.P, &c.NP, &c.NP, &c.NP, &c.DP}, Q) + default: + return fmt.Errorf("not handled %d", c.n) + } + return nil +} + +func TestECPairBLSBLSMulBatch(t *testing.T) { + assert := test.NewAssert(t) + _, _, p, q := bls12381.Generators() + + var u, v fr.Element + u.SetRandom() + v.SetRandom() + + p.ScalarMultiplication(&p, u.BigInt(new(big.Int))) + q.ScalarMultiplication(&q, v.BigInt(new(big.Int))) + + var dp, np bls12381.G1Affine + dp.Double(&p) + np.Neg(&p) + + for i := 2; i < 10; i++ { + err := test.IsSolved(&ecPairBLSBatchCircuit{n: i}, &ecPairBLSBatchCircuit{ + n: i, + P: sw_bls12381.NewG1Affine(p), + NP: sw_bls12381.NewG1Affine(np), + DP: sw_bls12381.NewG1Affine(dp), + Q: sw_bls12381.NewG2Affine(q), + }, ecc.BN254.ScalarField()) + assert.NoError(err) + } +} From 0cd0f623e82a3f3dd1af80cfabe388d43d5d8fdc Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Wed, 19 Mar 2025 11:54:48 +0100 Subject: [PATCH 023/105] feat: hint ok --- std/evmprecompiles/16-blsmaptog1.go | 131 ++++++++++++++++++++-------- 1 file changed, 97 insertions(+), 34 deletions(-) diff --git a/std/evmprecompiles/16-blsmaptog1.go b/std/evmprecompiles/16-blsmaptog1.go index 2df7294959..44cc45cf74 100644 --- a/std/evmprecompiles/16-blsmaptog1.go +++ b/std/evmprecompiles/16-blsmaptog1.go @@ -1,16 +1,24 @@ package evmprecompiles import ( + "math/big" + + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" "github.com/consensys/gnark/std/math/emulated" ) +func init() { + solver.RegisterHint(g1SqrtRatioHint) +} + type FpApi = emulated.Field[emulated.BLS12381Fp] type FpElement = emulated.Element[emulated.BLS12381Fp] type G1Affine = sw_emulated.AffinePoint[emulated.BLS12381Fp] -func g1IsogenyXNumerator(api frontend.API, x FpElement) (FpElement, error) { +func g1IsogenyXNumerator(api *FpApi, x FpElement) (FpElement, error) { return g1EvalPolynomial( api, @@ -32,7 +40,7 @@ func g1IsogenyXNumerator(api frontend.API, x FpElement) (FpElement, error) { x) } -func g1IsogenyXDenominator(api frontend.API, x FpElement) (FpElement, error) { +func g1IsogenyXDenominator(api *FpApi, x FpElement) (FpElement, error) { return g1EvalPolynomial( api, @@ -52,7 +60,7 @@ func g1IsogenyXDenominator(api frontend.API, x FpElement) (FpElement, error) { x) } -func g1IsogenyYNumerator(api frontend.API, x FpElement) (FpElement, error) { +func g1IsogenyYNumerator(api *FpApi, x FpElement) (FpElement, error) { return g1EvalPolynomial( api, @@ -78,7 +86,7 @@ func g1IsogenyYNumerator(api frontend.API, x FpElement) (FpElement, error) { x) } -func g1IsogenyYDenominator(api frontend.API, x FpElement) (FpElement, error) { +func g1IsogenyYDenominator(api *FpApi, x FpElement) (FpElement, error) { return g1EvalPolynomial( api, @@ -125,6 +133,55 @@ func g1MulByZ(api *FpApi, z, x *FpElement) { *z = *api.Mul(&eleven, x) } +// g1SqrtRatio computes the square root of u/v and returns 0 iff u/v was indeed a quadratic residue +// if not, we get sqrt(Z * u / v). Recall that Z is non-residue +// If v = 0, u/v is meaningless and the output is unspecified, without raising an error. +// The main idea is that since the computation of the square root involves taking large powers of u/v, the inversion of v can be avoided. +// +// nativeInputs[0] = u, nativeInputs[1]=v +// nativeOutput[0] = 1 if u/v is a QR, 0 otherwise, nativeOutput[1]=sqrt(u/v) or sqrt(Z u/v) +func g1SqrtRatioHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { + return emulated.UnwrapHint(nativeInputs, nativeOutputs, + func(mod *big.Int, inputs, outputs []*big.Int) error { + + var z, u, v fp.Element + + u.SetBigInt(inputs[0]) + v.SetBigInt(inputs[1]) + + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-optimized-sqrt_ratio-for-q- (3 mod 4) + var tv1 fp.Element + tv1.Square(&v) // 1. tv1 = v² + var tv2 fp.Element + tv2.Mul(&u, &v) // 2. tv2 = u * v + tv1.Mul(&tv1, &tv2) // 3. tv1 = tv1 * tv2 + + var y1 fp.Element + { + var c1 big.Int + // c1 = 1000602388805416848354447456433976039139220704984751971333014534031007912622709466110671907282253916009473568139946 + c1.SetBytes([]byte{6, 128, 68, 122, 142, 95, 249, 166, 146, 198, 233, 237, 144, 210, 235, 53, 217, 29, 210, 225, 60, 225, 68, 175, 217, 204, 52, 168, 61, 172, 61, 137, 7, 170, 255, 255, 172, 84, 255, 255, 238, 127, 191, 255, 255, 255, 234, 170}) // c1 = (q - 3) / 4 # Integer arithmetic + + y1.Exp(tv1, &c1) // 4. y1 = tv1ᶜ¹ + } + + y1.Mul(&y1, &tv2) // 5. y1 = y1 * tv2 + + var y2 fp.Element + // c2 = sqrt(-Z) + tv3 := fp.Element{17544630987809824292, 17306709551153317753, 8299808889594647786, 5930295261504720397, 675038575008112577, 167386374569371918} + y2.Mul(&y1, &tv3) // 6. y2 = y1 * c2 + tv3.Square(&y1) // 7. tv3 = y1² + tv3.Mul(&tv3, &v) // 8. tv3 = tv3 * v + isQNr := tv3.NotEqual(&u) // 9. isQR = tv3 == u + z.Select(int(isQNr), &y1, &y2) // 10. y = CMOV(y2, y1, isQR) + z.BigInt(outputs[0]) + y1.BigInt(outputs[1]) + + return nil + }) +} + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-simplified-swu-method // MapToCurve1 implements the SSWU map // No cofactor clearing or isogeny @@ -143,58 +200,64 @@ func MapToCurve1(api frontend.API, u *FpElement) (G1Affine, error) { tv1 := fpApi.Mul(u, u) // 1. tv1 = u² //mul tv1 by Z - g1MulByZ(&tv1, &tv1) // 2. tv1 = Z * tv1 + g1MulByZ(fpApi, tv1, tv1) // 2. tv1 = Z * tv1 // var tv2 fp.Element - // tv2.Square(&tv1) // 3. tv2 = tv1² - // tv2.Add(&tv2, &tv1) // 4. tv2 = tv2 + tv1 + tv2 := fpApi.Mul(tv1, tv1) // 3. tv2 = tv1² + tv2 = fpApi.Add(tv2, tv1) // 4. tv2 = tv2 + tv1 // var tv3 fp.Element // var tv4 fp.Element - // tv4.SetOne() - // tv3.Add(&tv2, &tv4) // 5. tv3 = tv2 + 1 - // tv3.Mul(&tv3, &sswuIsoCurveCoeffB) // 6. tv3 = B * tv3 + tv4 := emulated.ValueOf[emulated.BLS12381Fp]("1") + tv3 := fpApi.Add(tv2, &tv4) // 5. tv3 = tv2 + 1 + tv3 = fpApi.Mul(tv3, &sswuIsoCurveCoeffB) // 6. tv3 = B * tv3 // tv2NZero := g1NotZero(&tv2) + tv2IsZero := fpApi.IsZero(tv2) - // // tv4 = Z - // tv4 = fp.Element{9830232086645309404, 1112389714365644829, 8603885298299447491, 11361495444721768256, 5788602283869803809, 543934104870762216} + // tv4 = Z + tv4 = emulated.ValueOf[emulated.BLS12381Fp]("11") - // tv2.Neg(&tv2) - // tv4.Select(int(tv2NZero), &tv4, &tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) - // tv4.Mul(&tv4, &sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 + tv2 = fpApi.Neg(tv2) // tv2.Neg(&tv2) + tv4 = *fpApi.Select(tv2IsZero, &tv4, tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) + tv4 = *fpApi.Mul(&tv4, &sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 - // tv2.Square(&tv3) // 9. tv2 = tv3² + tv2 = fpApi.Mul(tv3, tv3) // 9. tv2 = tv3² - // var tv6 fp.Element - // tv6.Square(&tv4) // 10. tv6 = tv4² + tv6 := fpApi.Mul(&tv4, &tv4) // 10. tv6 = tv4² - // var tv5 fp.Element - // tv5.Mul(&tv6, &sswuIsoCurveCoeffA) // 11. tv5 = A * tv6 + tv5 := *fpApi.Mul(tv6, &sswuIsoCurveCoeffA) // 11. tv5 = A * tv6 - // tv2.Add(&tv2, &tv5) // 12. tv2 = tv2 + tv5 - // tv2.Mul(&tv2, &tv3) // 13. tv2 = tv2 * tv3 - // tv6.Mul(&tv6, &tv4) // 14. tv6 = tv6 * tv4 + tv2 = fpApi.Add(tv2, &tv5) // 12. tv2 = tv2 + tv5 + tv2 = fpApi.Mul(tv2, tv3) // 13. tv2 = tv2 * tv3 + tv6 = fpApi.Mul(tv6, &tv4) // 14. tv6 = tv6 * tv4 - // tv5.Mul(&tv6, &sswuIsoCurveCoeffB) // 15. tv5 = B * tv6 - // tv2.Add(&tv2, &tv5) // 16. tv2 = tv2 + tv5 + tv5 = *fpApi.Mul(tv6, &sswuIsoCurveCoeffB) // 15. tv5 = B * tv6 + tv2 = fpApi.Add(tv2, &tv5) // 16. tv2 = tv2 + tv5 // var x fp.Element - // x.Mul(&tv1, &tv3) // 17. x = tv1 * tv3 + x := fpApi.Mul(tv1, tv3) // 17. x = tv1 * tv3 + + hint, err := fpApi.NewHint(g1SqrtRatioHint, 2, tv2, tv6) + if err != nil { + return res, err + } - // var y1 fp.Element - // gx1NSquare := g1SqrtRatio(&y1, &tv2, &tv6) // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) + // TODO constrain gx1NSquare and y1 + // (gx1NSquare==1 AND (u/v) QNR ) OR (gx1NSquare==0 AND (u/v) QR ) + gx1NSquare := hint[0] + y1 := hint[1] // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) // var y fp.Element - // y.Mul(&tv1, u) // 19. y = tv1 * u + y := fpApi.Mul(tv1, u) // 19. y = tv1 * u - // y.Mul(&y, &y1) // 20. y = y * y1 + y = fpApi.Mul(y, y1) // 20. y = y * y1 - // x.Select(int(gx1NSquare), &tv3, &x) // 21. x = CMOV(x, tv3, is_gx1_square) - // y.Select(int(gx1NSquare), &y1, &y) // 22. y = CMOV(y, y1, is_gx1_square) + x = fpApi.Select(gx1NSquare, x, tv3) // 21. x = CMOV(x, tv3, is_gx1_square) + y = fpApi.Select(gx1NSquare, y, y1) // 22. y = CMOV(y, y1, is_gx1_square) - // y1.Neg(&y) - // y.Select(int(g1Sgn0(u)^g1Sgn0(&y)), &y, &y1) + y1 = fpApi.Neg(y) + // y = fpApi.Select() // y.Select(int(g1Sgn0(u)^g1Sgn0(&y)), &y, &y1) // // 23. e1 = sgn0(u) == sgn0(y) // // 24. y = CMOV(-y, y, e1) From 61d3d5f0718a9c42759cd7c669d29a6f5bdfc50a Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Thu, 20 Mar 2025 09:37:35 +0100 Subject: [PATCH 024/105] feat: move map-to-g1 to swbls12381 package --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 268 +++++++++++++++++ std/evmprecompiles/16-blsmaptog1.go | 269 +----------------- 2 files changed, 269 insertions(+), 268 deletions(-) create mode 100644 std/algebra/emulated/sw_bls12381/map_to_g1.go diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go new file mode 100644 index 0000000000..0d90cb55b5 --- /dev/null +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -0,0 +1,268 @@ +package sw_bls12381 + +import ( + "math/big" + + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark/constraint/solver" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/emulated" +) + +func init() { + solver.RegisterHint(g1SqrtRatioHint) +} + +type FpApi = emulated.Field[emulated.BLS12381Fp] +type FpElement = emulated.Element[emulated.BLS12381Fp] + +func g1IsogenyXNumerator(api *FpApi, x FpElement) (FpElement, error) { + + return g1EvalPolynomial( + api, + false, + []FpElement{ + emulated.ValueOf[emulated.BLS12381Fp]("0x11a05f2b1e833340b809101dd99815856b303e88a2d7005ff2627b56cdb4e2c85610c2d5f2e62d6eaeac1662734649b7"), + emulated.ValueOf[emulated.BLS12381Fp]("0x17294ed3e943ab2f0588bab22147a81c7c17e75b2f6a8417f565e33c70d1e86b4838f2a6f318c356e834eef1b3cb83bb"), + emulated.ValueOf[emulated.BLS12381Fp]("0xd54005db97678ec1d1048c5d10a9a1bce032473295983e56878e501ec68e25c958c3e3d2a09729fe0179f9dac9edcb0"), + emulated.ValueOf[emulated.BLS12381Fp]("0x1778e7166fcc6db74e0609d307e55412d7f5e4656a8dbf25f1b33289f1b330835336e25ce3107193c5b388641d9b6861"), + emulated.ValueOf[emulated.BLS12381Fp]("0xe99726a3199f4436642b4b3e4118e5499db995a1257fb3f086eeb65982fac18985a286f301e77c451154ce9ac8895d9"), + emulated.ValueOf[emulated.BLS12381Fp]("0x1630c3250d7313ff01d1201bf7a74ab5db3cb17dd952799b9ed3ab9097e68f90a0870d2dcae73d19cd13c1c66f652983"), + emulated.ValueOf[emulated.BLS12381Fp]("0xd6ed6553fe44d296a3726c38ae652bfb11586264f0f8ce19008e218f9c86b2a8da25128c1052ecaddd7f225a139ed84"), + emulated.ValueOf[emulated.BLS12381Fp]("0x17b81e7701abdbe2e8743884d1117e53356de5ab275b4db1a682c62ef0f2753339b7c8f8c8f475af9ccb5618e3f0c88e"), + emulated.ValueOf[emulated.BLS12381Fp]("0x80d3cf1f9a78fc47b90b33563be990dc43b756ce79f5574a2c596c928c5d1de4fa295f296b74e956d71986a8497e317"), + emulated.ValueOf[emulated.BLS12381Fp]("0x169b1f8e1bcfa7c42e0c37515d138f22dd2ecb803a0c5c99676314baf4bb1b7fa3190b2edc0327797f241067be390c9e"), + emulated.ValueOf[emulated.BLS12381Fp](" 0x10321da079ce07e272d8ec09d2565b0dfa7dccdde6787f96d50af36003b14866f69b771f8c285decca67df3f1605fb7b"), + emulated.ValueOf[emulated.BLS12381Fp](" 0x6e08c248e260e70bd1e962381edee3d31d79d7e22c837bc23c0bf1bc24c6b68c24b1b80b64d391fa9c8ba2e8ba2d229"), + }, + x) +} + +func g1IsogenyXDenominator(api *FpApi, x FpElement) (FpElement, error) { + + return g1EvalPolynomial( + api, + true, + []FpElement{ + emulated.ValueOf[emulated.BLS12381Fp]("0x8ca8d548cff19ae18b2e62f4bd3fa6f01d5ef4ba35b48ba9c9588617fc8ac62b558d681be343df8993cf9fa40d21b1c"), + emulated.ValueOf[emulated.BLS12381Fp]("0x12561a5deb559c4348b4711298e536367041e8ca0cf0800c0126c2588c48bf5713daa8846cb026e9e5c8276ec82b3bff"), + emulated.ValueOf[emulated.BLS12381Fp]("0xb2962fe57a3225e8137e629bff2991f6f89416f5a718cd1fca64e00b11aceacd6a3d0967c94fedcfcc239ba5cb83e19"), + emulated.ValueOf[emulated.BLS12381Fp]("0x3425581a58ae2fec83aafef7c40eb545b08243f16b1655154cca8abc28d6fd04976d5243eecf5c4130de8938dc62cd8"), + emulated.ValueOf[emulated.BLS12381Fp]("0x13a8e162022914a80a6f1d5f43e7a07dffdfc759a12062bb8d6b44e833b306da9bd29ba81f35781d539d395b3532a21e"), + emulated.ValueOf[emulated.BLS12381Fp]("0xe7355f8e4e667b955390f7f0506c6e9395735e9ce9cad4d0a43bcef24b8982f7400d24bc4228f11c02df9a29f6304a5"), + emulated.ValueOf[emulated.BLS12381Fp]("0x772caacf16936190f3e0c63e0596721570f5799af53a1894e2e073062aede9cea73b3538f0de06cec2574496ee84a3a"), + emulated.ValueOf[emulated.BLS12381Fp]("0x14a7ac2a9d64a8b230b3f5b074cf01996e7f63c21bca68a81996e1cdf9822c580fa5b9489d11e2d311f7d99bbdcc5a5e"), + emulated.ValueOf[emulated.BLS12381Fp]("0xa10ecf6ada54f825e920b3dafc7a3cce07f8d1d7161366b74100da67f39883503826692abba43704776ec3a79a1d641"), + emulated.ValueOf[emulated.BLS12381Fp]("0x95fc13ab9e92ad4476d6e3eb3a56680f682b4ee96f7d03776df533978f31c1593174e4b4b7865002d6384d168ecdd0a"), + }, + x) +} + +func g1IsogenyYNumerator(api *FpApi, x FpElement) (FpElement, error) { + + return g1EvalPolynomial( + api, + false, + []FpElement{ + emulated.ValueOf[emulated.BLS12381Fp]("0x90d97c81ba24ee0259d1f094980dcfa11ad138e48a869522b52af6c956543d3cd0c7aee9b3ba3c2be9845719707bb33"), + emulated.ValueOf[emulated.BLS12381Fp]("0x134996a104ee5811d51036d776fb46831223e96c254f383d0f906343eb67ad34d6c56711962fa8bfe097e75a2e41c696"), + emulated.ValueOf[emulated.BLS12381Fp]("0xcc786baa966e66f4a384c86a3b49942552e2d658a31ce2c344be4b91400da7d26d521628b00523b8dfe240c72de1f6"), + emulated.ValueOf[emulated.BLS12381Fp]("0x1f86376e8981c217898751ad8746757d42aa7b90eeb791c09e4a3ec03251cf9de405aba9ec61deca6355c77b0e5f4cb"), + emulated.ValueOf[emulated.BLS12381Fp]("0x8cc03fdefe0ff135caf4fe2a21529c4195536fbe3ce50b879833fd221351adc2ee7f8dc099040a841b6daecf2e8fedb"), + emulated.ValueOf[emulated.BLS12381Fp]("0x16603fca40634b6a2211e11db8f0a6a074a7d0d4afadb7bd76505c3d3ad5544e203f6326c95a807299b23ab13633a5f0"), + emulated.ValueOf[emulated.BLS12381Fp]("0x4ab0b9bcfac1bbcb2c977d027796b3ce75bb8ca2be184cb5231413c4d634f3747a87ac2460f415ec961f8855fe9d6f2"), + emulated.ValueOf[emulated.BLS12381Fp]("0x987c8d5333ab86fde9926bd2ca6c674170a05bfe3bdd81ffd038da6c26c842642f64550fedfe935a15e4ca31870fb29"), + emulated.ValueOf[emulated.BLS12381Fp]("0x9fc4018bd96684be88c9e221e4da1bb8f3abd16679dc26c1e8b6e6a1f20cabe69d65201c78607a360370e577bdba587"), + emulated.ValueOf[emulated.BLS12381Fp]("0xe1bba7a1186bdb5223abde7ada14a23c42a0ca7915af6fe06985e7ed1e4d43b9b3f7055dd4eba6f2bafaaebca731c30"), + emulated.ValueOf[emulated.BLS12381Fp]("0x19713e47937cd1be0dfd0b8f1d43fb93cd2fcbcb6caf493fd1183e416389e61031bf3a5cce3fbafce813711ad011c132"), + emulated.ValueOf[emulated.BLS12381Fp]("0x18b46a908f36f6deb918c143fed2edcc523559b8aaf0c2462e6bfe7f911f643249d9cdf41b44d606ce07c8a4d0074d8e"), + emulated.ValueOf[emulated.BLS12381Fp]("0xb182cac101b9399d155096004f53f447aa7b12a3426b08ec02710e807b4633f06c851c1919211f20d4c04f00b971ef8"), + emulated.ValueOf[emulated.BLS12381Fp]("0x245a394ad1eca9b72fc00ae7be315dc757b3b080d4c158013e6632d3c40659cc6cf90ad1c232a6442d9d3f5db980133"), + emulated.ValueOf[emulated.BLS12381Fp]("0x5c129645e44cf1102a159f748c4a3fc5e673d81d7e86568d9ab0f5d396a7ce46ba1049b6579afb7866b1e715475224b"), + emulated.ValueOf[emulated.BLS12381Fp]("0x15e6be4e990f03ce4ea50b3b42df2eb5cb181d8f84965a3957add4fa95af01b2b665027efec01c7704b456be69c8b604"), + }, + x) +} + +func g1IsogenyYDenominator(api *FpApi, x FpElement) (FpElement, error) { + + return g1EvalPolynomial( + api, + true, + []FpElement{ + emulated.ValueOf[emulated.BLS12381Fp]("0x16112c4c3a9c98b252181140fad0eae9601a6de578980be6eec3232b5be72e7a07f3688ef60c206d01479253b03663c1"), + emulated.ValueOf[emulated.BLS12381Fp]("0x1962d75c2381201e1a0cbd6c43c348b885c84ff731c4d59ca4a10356f453e01f78a4260763529e3532f6102c2e49a03d"), + emulated.ValueOf[emulated.BLS12381Fp]("0x58df3306640da276faaae7d6e8eb15778c4855551ae7f310c35a5dd279cd2eca6757cd636f96f891e2538b53dbf67f2"), + emulated.ValueOf[emulated.BLS12381Fp]("0x16b7d288798e5395f20d23bf89edb4d1d115c5dbddbcd30e123da489e726af41727364f2c28297ada8d26d98445f5416"), + emulated.ValueOf[emulated.BLS12381Fp]("0xbe0e079545f43e4b00cc912f8228ddcc6d19c9f0f69bbb0542eda0fc9dec916a20b15dc0fd2ededda39142311a5001d"), + emulated.ValueOf[emulated.BLS12381Fp]("0x8d9e5297186db2d9fb266eaac783182b70152c65550d881c5ecd87b6f0f5a6449f38db9dfa9cce202c6477faaf9b7ac"), + emulated.ValueOf[emulated.BLS12381Fp]("0x166007c08a99db2fc3ba8734ace9824b5eecfdfa8d0cf8ef5dd365bc400a0051d5fa9c01a58b1fb93d1a1399126a775c"), + emulated.ValueOf[emulated.BLS12381Fp]("0x16a3ef08be3ea7ea03bcddfabba6ff6ee5a4375efa1f4fd7feb34fd206357132b920f5b00801dee460ee415a15812ed9"), + emulated.ValueOf[emulated.BLS12381Fp]("0x1866c8ed336c61231a1be54fd1d74cc4f9fb0ce4c6af5920abc5750c4bf39b4852cfe2f7bb9248836b233d9d55535d4a"), + emulated.ValueOf[emulated.BLS12381Fp]("0x167a55cda70a6e1cea820597d94a84903216f763e13d87bb5308592e7ea7d4fbc7385ea3d529b35e346ef48bb8913f55"), + emulated.ValueOf[emulated.BLS12381Fp]("0x4d2f259eea405bd48f010a01ad2911d9c6dd039bb61a6290e591b36e636a5c871a5c29f4f83060400f8b49cba8f6aa8"), + emulated.ValueOf[emulated.BLS12381Fp]("0xaccbb67481d033ff5852c1e48c50c477f94ff8aefce42d28c0f9a88cea7913516f968986f7ebbea9684b529e2561092"), + emulated.ValueOf[emulated.BLS12381Fp]("0xad6b9514c767fe3c3613144b45f1496543346d98adf02267d5ceef9a00d9b8693000763e3b90ac11e99b138573345cc"), + emulated.ValueOf[emulated.BLS12381Fp]("0x2660400eb2e4f3b628bdd0d53cd76f2bf565b94e72927c1cb748df27942480e420517bd8714cc80d1fadc1326ed06f7"), + emulated.ValueOf[emulated.BLS12381Fp]("0xe0fa1d816ddc03e6b24255e0d7819c171c40f65e273b853324efcd6356caa205ca2f570f13497804415473a1d634b8f"), + }, + x) +} + +func g1EvalPolynomial(api *FpApi, monic bool, coefficients []FpElement, x FpElement) (FpElement, error) { + + res := coefficients[len(coefficients)-1] + + if monic { + res = *api.Add(&res, &x) + } + + for i := len(coefficients) - 2; i >= 0; i-- { + res = *api.Mul(&res, &x) + res = *api.Add(&res, &coefficients[i]) + } + return res, nil + +} + +// g1MulByZ multiplies x by [11] and stores the result in z +func g1MulByZ(api *FpApi, z, x *FpElement) { + eleven := emulated.ValueOf[emulated.BLS12381Fp]("11") + *z = *api.Mul(&eleven, x) +} + +// g1SqrtRatio computes the square root of u/v and returns 0 iff u/v was indeed a quadratic residue +// if not, we get sqrt(Z * u / v). Recall that Z is non-residue +// If v = 0, u/v is meaningless and the output is unspecified, without raising an error. +// The main idea is that since the computation of the square root involves taking large powers of u/v, the inversion of v can be avoided. +// +// nativeInputs[0] = u, nativeInputs[1]=v +// nativeOutput[0] = 1 if u/v is a QR, 0 otherwise, nativeOutput[1]=sqrt(u/v) or sqrt(Z u/v) +func g1SqrtRatioHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { + return emulated.UnwrapHint(nativeInputs, nativeOutputs, + func(mod *big.Int, inputs, outputs []*big.Int) error { + + var z, u, v fp.Element + + u.SetBigInt(inputs[0]) + v.SetBigInt(inputs[1]) + + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-optimized-sqrt_ratio-for-q- (3 mod 4) + var tv1 fp.Element + tv1.Square(&v) // 1. tv1 = v² + var tv2 fp.Element + tv2.Mul(&u, &v) // 2. tv2 = u * v + tv1.Mul(&tv1, &tv2) // 3. tv1 = tv1 * tv2 + + var y1 fp.Element + { + var c1 big.Int + // c1 = 1000602388805416848354447456433976039139220704984751971333014534031007912622709466110671907282253916009473568139946 + c1.SetBytes([]byte{6, 128, 68, 122, 142, 95, 249, 166, 146, 198, 233, 237, 144, 210, 235, 53, 217, 29, 210, 225, 60, 225, 68, 175, 217, 204, 52, 168, 61, 172, 61, 137, 7, 170, 255, 255, 172, 84, 255, 255, 238, 127, 191, 255, 255, 255, 234, 170}) // c1 = (q - 3) / 4 # Integer arithmetic + + y1.Exp(tv1, &c1) // 4. y1 = tv1ᶜ¹ + } + + y1.Mul(&y1, &tv2) // 5. y1 = y1 * tv2 + + var y2 fp.Element + // c2 = sqrt(-Z) + tv3 := fp.Element{17544630987809824292, 17306709551153317753, 8299808889594647786, 5930295261504720397, 675038575008112577, 167386374569371918} + y2.Mul(&y1, &tv3) // 6. y2 = y1 * c2 + tv3.Square(&y1) // 7. tv3 = y1² + tv3.Mul(&tv3, &v) // 8. tv3 = tv3 * v + isQNr := tv3.NotEqual(&u) // 9. isQR = tv3 == u + z.Select(int(isQNr), &y1, &y2) // 10. y = CMOV(y2, y1, isQR) + z.BigInt(outputs[0]) + y1.BigInt(outputs[1]) + + return nil + }) +} + +// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-simplified-swu-method +// MapToCurve1 implements the SSWU map +// No cofactor clearing or isogeny +func MapToCurve1(api frontend.API, u *FpElement) (G1Affine, error) { + + var res G1Affine + + fpApi, err := emulated.NewField[emulated.BLS12381Fp](api) + if err != nil { + return res, err + } + + sswuIsoCurveCoeffA := emulated.ValueOf[emulated.BLS12381Fp]("0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d") + sswuIsoCurveCoeffB := emulated.ValueOf[emulated.BLS12381Fp]("0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0") + + tv1 := fpApi.Mul(u, u) // 1. tv1 = u² + + //mul tv1 by Z + g1MulByZ(fpApi, tv1, tv1) // 2. tv1 = Z * tv1 + + // var tv2 fp.Element + tv2 := fpApi.Mul(tv1, tv1) // 3. tv2 = tv1² + tv2 = fpApi.Add(tv2, tv1) // 4. tv2 = tv2 + tv1 + + // var tv3 fp.Element + // var tv4 fp.Element + tv4 := emulated.ValueOf[emulated.BLS12381Fp]("1") + tv3 := fpApi.Add(tv2, &tv4) // 5. tv3 = tv2 + 1 + tv3 = fpApi.Mul(tv3, &sswuIsoCurveCoeffB) // 6. tv3 = B * tv3 + + // tv2NZero := g1NotZero(&tv2) + tv2IsZero := fpApi.IsZero(tv2) + + // tv4 = Z + tv4 = emulated.ValueOf[emulated.BLS12381Fp]("11") + + tv2 = fpApi.Neg(tv2) // tv2.Neg(&tv2) + tv4 = *fpApi.Select(tv2IsZero, &tv4, tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) + tv4 = *fpApi.Mul(&tv4, &sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 + + tv2 = fpApi.Mul(tv3, tv3) // 9. tv2 = tv3² + + tv6 := fpApi.Mul(&tv4, &tv4) // 10. tv6 = tv4² + + tv5 := *fpApi.Mul(tv6, &sswuIsoCurveCoeffA) // 11. tv5 = A * tv6 + + tv2 = fpApi.Add(tv2, &tv5) // 12. tv2 = tv2 + tv5 + tv2 = fpApi.Mul(tv2, tv3) // 13. tv2 = tv2 * tv3 + tv6 = fpApi.Mul(tv6, &tv4) // 14. tv6 = tv6 * tv4 + + tv5 = *fpApi.Mul(tv6, &sswuIsoCurveCoeffB) // 15. tv5 = B * tv6 + tv2 = fpApi.Add(tv2, &tv5) // 16. tv2 = tv2 + tv5 + + // var x fp.Element + x := fpApi.Mul(tv1, tv3) // 17. x = tv1 * tv3 + + hint, err := fpApi.NewHint(g1SqrtRatioHint, 2, tv2, tv6) + if err != nil { + return res, err + } + + // TODO constrain gx1NSquare and y1 + // (gx1NSquare==1 AND (u/v) QNR ) OR (gx1NSquare==0 AND (u/v) QR ) + gx1NSquare := hint[0] + y1 := hint[1] // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) + + // var y fp.Element + y := fpApi.Mul(tv1, u) // 19. y = tv1 * u + + y = fpApi.Mul(y, y1) // 20. y = y * y1 + + x = fpApi.Select(gx1NSquare, x, tv3) // 21. x = CMOV(x, tv3, is_gx1_square) + y = fpApi.Select(gx1NSquare, y, y1) // 22. y = CMOV(y, y1, is_gx1_square) + + y1 = fpApi.Neg(y) + // y = fpApi.Select() // y.Select(int(g1Sgn0(u)^g1Sgn0(&y)), &y, &y1) + + // // 23. e1 = sgn0(u) == sgn0(y) + // // 24. y = CMOV(-y, y, e1) + + // x.Div(&x, &tv4) // 25. x = x / tv4 + + // return G1Affine{x, y} + + return res, nil +} diff --git a/std/evmprecompiles/16-blsmaptog1.go b/std/evmprecompiles/16-blsmaptog1.go index 44cc45cf74..5a7c62a5de 100644 --- a/std/evmprecompiles/16-blsmaptog1.go +++ b/std/evmprecompiles/16-blsmaptog1.go @@ -1,270 +1,3 @@ package evmprecompiles -import ( - "math/big" - - "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" - "github.com/consensys/gnark/constraint/solver" - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" - "github.com/consensys/gnark/std/math/emulated" -) - -func init() { - solver.RegisterHint(g1SqrtRatioHint) -} - -type FpApi = emulated.Field[emulated.BLS12381Fp] -type FpElement = emulated.Element[emulated.BLS12381Fp] -type G1Affine = sw_emulated.AffinePoint[emulated.BLS12381Fp] - -func g1IsogenyXNumerator(api *FpApi, x FpElement) (FpElement, error) { - - return g1EvalPolynomial( - api, - false, - []FpElement{ - emulated.ValueOf[emulated.BLS12381Fp]("0x11a05f2b1e833340b809101dd99815856b303e88a2d7005ff2627b56cdb4e2c85610c2d5f2e62d6eaeac1662734649b7"), - emulated.ValueOf[emulated.BLS12381Fp]("0x17294ed3e943ab2f0588bab22147a81c7c17e75b2f6a8417f565e33c70d1e86b4838f2a6f318c356e834eef1b3cb83bb"), - emulated.ValueOf[emulated.BLS12381Fp]("0xd54005db97678ec1d1048c5d10a9a1bce032473295983e56878e501ec68e25c958c3e3d2a09729fe0179f9dac9edcb0"), - emulated.ValueOf[emulated.BLS12381Fp]("0x1778e7166fcc6db74e0609d307e55412d7f5e4656a8dbf25f1b33289f1b330835336e25ce3107193c5b388641d9b6861"), - emulated.ValueOf[emulated.BLS12381Fp]("0xe99726a3199f4436642b4b3e4118e5499db995a1257fb3f086eeb65982fac18985a286f301e77c451154ce9ac8895d9"), - emulated.ValueOf[emulated.BLS12381Fp]("0x1630c3250d7313ff01d1201bf7a74ab5db3cb17dd952799b9ed3ab9097e68f90a0870d2dcae73d19cd13c1c66f652983"), - emulated.ValueOf[emulated.BLS12381Fp]("0xd6ed6553fe44d296a3726c38ae652bfb11586264f0f8ce19008e218f9c86b2a8da25128c1052ecaddd7f225a139ed84"), - emulated.ValueOf[emulated.BLS12381Fp]("0x17b81e7701abdbe2e8743884d1117e53356de5ab275b4db1a682c62ef0f2753339b7c8f8c8f475af9ccb5618e3f0c88e"), - emulated.ValueOf[emulated.BLS12381Fp]("0x80d3cf1f9a78fc47b90b33563be990dc43b756ce79f5574a2c596c928c5d1de4fa295f296b74e956d71986a8497e317"), - emulated.ValueOf[emulated.BLS12381Fp]("0x169b1f8e1bcfa7c42e0c37515d138f22dd2ecb803a0c5c99676314baf4bb1b7fa3190b2edc0327797f241067be390c9e"), - emulated.ValueOf[emulated.BLS12381Fp](" 0x10321da079ce07e272d8ec09d2565b0dfa7dccdde6787f96d50af36003b14866f69b771f8c285decca67df3f1605fb7b"), - emulated.ValueOf[emulated.BLS12381Fp](" 0x6e08c248e260e70bd1e962381edee3d31d79d7e22c837bc23c0bf1bc24c6b68c24b1b80b64d391fa9c8ba2e8ba2d229"), - }, - x) -} - -func g1IsogenyXDenominator(api *FpApi, x FpElement) (FpElement, error) { - - return g1EvalPolynomial( - api, - true, - []FpElement{ - emulated.ValueOf[emulated.BLS12381Fp]("0x8ca8d548cff19ae18b2e62f4bd3fa6f01d5ef4ba35b48ba9c9588617fc8ac62b558d681be343df8993cf9fa40d21b1c"), - emulated.ValueOf[emulated.BLS12381Fp]("0x12561a5deb559c4348b4711298e536367041e8ca0cf0800c0126c2588c48bf5713daa8846cb026e9e5c8276ec82b3bff"), - emulated.ValueOf[emulated.BLS12381Fp]("0xb2962fe57a3225e8137e629bff2991f6f89416f5a718cd1fca64e00b11aceacd6a3d0967c94fedcfcc239ba5cb83e19"), - emulated.ValueOf[emulated.BLS12381Fp]("0x3425581a58ae2fec83aafef7c40eb545b08243f16b1655154cca8abc28d6fd04976d5243eecf5c4130de8938dc62cd8"), - emulated.ValueOf[emulated.BLS12381Fp]("0x13a8e162022914a80a6f1d5f43e7a07dffdfc759a12062bb8d6b44e833b306da9bd29ba81f35781d539d395b3532a21e"), - emulated.ValueOf[emulated.BLS12381Fp]("0xe7355f8e4e667b955390f7f0506c6e9395735e9ce9cad4d0a43bcef24b8982f7400d24bc4228f11c02df9a29f6304a5"), - emulated.ValueOf[emulated.BLS12381Fp]("0x772caacf16936190f3e0c63e0596721570f5799af53a1894e2e073062aede9cea73b3538f0de06cec2574496ee84a3a"), - emulated.ValueOf[emulated.BLS12381Fp]("0x14a7ac2a9d64a8b230b3f5b074cf01996e7f63c21bca68a81996e1cdf9822c580fa5b9489d11e2d311f7d99bbdcc5a5e"), - emulated.ValueOf[emulated.BLS12381Fp]("0xa10ecf6ada54f825e920b3dafc7a3cce07f8d1d7161366b74100da67f39883503826692abba43704776ec3a79a1d641"), - emulated.ValueOf[emulated.BLS12381Fp]("0x95fc13ab9e92ad4476d6e3eb3a56680f682b4ee96f7d03776df533978f31c1593174e4b4b7865002d6384d168ecdd0a"), - }, - x) -} - -func g1IsogenyYNumerator(api *FpApi, x FpElement) (FpElement, error) { - - return g1EvalPolynomial( - api, - false, - []FpElement{ - emulated.ValueOf[emulated.BLS12381Fp]("0x90d97c81ba24ee0259d1f094980dcfa11ad138e48a869522b52af6c956543d3cd0c7aee9b3ba3c2be9845719707bb33"), - emulated.ValueOf[emulated.BLS12381Fp]("0x134996a104ee5811d51036d776fb46831223e96c254f383d0f906343eb67ad34d6c56711962fa8bfe097e75a2e41c696"), - emulated.ValueOf[emulated.BLS12381Fp]("0xcc786baa966e66f4a384c86a3b49942552e2d658a31ce2c344be4b91400da7d26d521628b00523b8dfe240c72de1f6"), - emulated.ValueOf[emulated.BLS12381Fp]("0x1f86376e8981c217898751ad8746757d42aa7b90eeb791c09e4a3ec03251cf9de405aba9ec61deca6355c77b0e5f4cb"), - emulated.ValueOf[emulated.BLS12381Fp]("0x8cc03fdefe0ff135caf4fe2a21529c4195536fbe3ce50b879833fd221351adc2ee7f8dc099040a841b6daecf2e8fedb"), - emulated.ValueOf[emulated.BLS12381Fp]("0x16603fca40634b6a2211e11db8f0a6a074a7d0d4afadb7bd76505c3d3ad5544e203f6326c95a807299b23ab13633a5f0"), - emulated.ValueOf[emulated.BLS12381Fp]("0x4ab0b9bcfac1bbcb2c977d027796b3ce75bb8ca2be184cb5231413c4d634f3747a87ac2460f415ec961f8855fe9d6f2"), - emulated.ValueOf[emulated.BLS12381Fp]("0x987c8d5333ab86fde9926bd2ca6c674170a05bfe3bdd81ffd038da6c26c842642f64550fedfe935a15e4ca31870fb29"), - emulated.ValueOf[emulated.BLS12381Fp]("0x9fc4018bd96684be88c9e221e4da1bb8f3abd16679dc26c1e8b6e6a1f20cabe69d65201c78607a360370e577bdba587"), - emulated.ValueOf[emulated.BLS12381Fp]("0xe1bba7a1186bdb5223abde7ada14a23c42a0ca7915af6fe06985e7ed1e4d43b9b3f7055dd4eba6f2bafaaebca731c30"), - emulated.ValueOf[emulated.BLS12381Fp]("0x19713e47937cd1be0dfd0b8f1d43fb93cd2fcbcb6caf493fd1183e416389e61031bf3a5cce3fbafce813711ad011c132"), - emulated.ValueOf[emulated.BLS12381Fp]("0x18b46a908f36f6deb918c143fed2edcc523559b8aaf0c2462e6bfe7f911f643249d9cdf41b44d606ce07c8a4d0074d8e"), - emulated.ValueOf[emulated.BLS12381Fp]("0xb182cac101b9399d155096004f53f447aa7b12a3426b08ec02710e807b4633f06c851c1919211f20d4c04f00b971ef8"), - emulated.ValueOf[emulated.BLS12381Fp]("0x245a394ad1eca9b72fc00ae7be315dc757b3b080d4c158013e6632d3c40659cc6cf90ad1c232a6442d9d3f5db980133"), - emulated.ValueOf[emulated.BLS12381Fp]("0x5c129645e44cf1102a159f748c4a3fc5e673d81d7e86568d9ab0f5d396a7ce46ba1049b6579afb7866b1e715475224b"), - emulated.ValueOf[emulated.BLS12381Fp]("0x15e6be4e990f03ce4ea50b3b42df2eb5cb181d8f84965a3957add4fa95af01b2b665027efec01c7704b456be69c8b604"), - }, - x) -} - -func g1IsogenyYDenominator(api *FpApi, x FpElement) (FpElement, error) { - - return g1EvalPolynomial( - api, - true, - []FpElement{ - emulated.ValueOf[emulated.BLS12381Fp]("0x16112c4c3a9c98b252181140fad0eae9601a6de578980be6eec3232b5be72e7a07f3688ef60c206d01479253b03663c1"), - emulated.ValueOf[emulated.BLS12381Fp]("0x1962d75c2381201e1a0cbd6c43c348b885c84ff731c4d59ca4a10356f453e01f78a4260763529e3532f6102c2e49a03d"), - emulated.ValueOf[emulated.BLS12381Fp]("0x58df3306640da276faaae7d6e8eb15778c4855551ae7f310c35a5dd279cd2eca6757cd636f96f891e2538b53dbf67f2"), - emulated.ValueOf[emulated.BLS12381Fp]("0x16b7d288798e5395f20d23bf89edb4d1d115c5dbddbcd30e123da489e726af41727364f2c28297ada8d26d98445f5416"), - emulated.ValueOf[emulated.BLS12381Fp]("0xbe0e079545f43e4b00cc912f8228ddcc6d19c9f0f69bbb0542eda0fc9dec916a20b15dc0fd2ededda39142311a5001d"), - emulated.ValueOf[emulated.BLS12381Fp]("0x8d9e5297186db2d9fb266eaac783182b70152c65550d881c5ecd87b6f0f5a6449f38db9dfa9cce202c6477faaf9b7ac"), - emulated.ValueOf[emulated.BLS12381Fp]("0x166007c08a99db2fc3ba8734ace9824b5eecfdfa8d0cf8ef5dd365bc400a0051d5fa9c01a58b1fb93d1a1399126a775c"), - emulated.ValueOf[emulated.BLS12381Fp]("0x16a3ef08be3ea7ea03bcddfabba6ff6ee5a4375efa1f4fd7feb34fd206357132b920f5b00801dee460ee415a15812ed9"), - emulated.ValueOf[emulated.BLS12381Fp]("0x1866c8ed336c61231a1be54fd1d74cc4f9fb0ce4c6af5920abc5750c4bf39b4852cfe2f7bb9248836b233d9d55535d4a"), - emulated.ValueOf[emulated.BLS12381Fp]("0x167a55cda70a6e1cea820597d94a84903216f763e13d87bb5308592e7ea7d4fbc7385ea3d529b35e346ef48bb8913f55"), - emulated.ValueOf[emulated.BLS12381Fp]("0x4d2f259eea405bd48f010a01ad2911d9c6dd039bb61a6290e591b36e636a5c871a5c29f4f83060400f8b49cba8f6aa8"), - emulated.ValueOf[emulated.BLS12381Fp]("0xaccbb67481d033ff5852c1e48c50c477f94ff8aefce42d28c0f9a88cea7913516f968986f7ebbea9684b529e2561092"), - emulated.ValueOf[emulated.BLS12381Fp]("0xad6b9514c767fe3c3613144b45f1496543346d98adf02267d5ceef9a00d9b8693000763e3b90ac11e99b138573345cc"), - emulated.ValueOf[emulated.BLS12381Fp]("0x2660400eb2e4f3b628bdd0d53cd76f2bf565b94e72927c1cb748df27942480e420517bd8714cc80d1fadc1326ed06f7"), - emulated.ValueOf[emulated.BLS12381Fp]("0xe0fa1d816ddc03e6b24255e0d7819c171c40f65e273b853324efcd6356caa205ca2f570f13497804415473a1d634b8f"), - }, - x) -} - -func g1EvalPolynomial(api *FpApi, monic bool, coefficients []FpElement, x FpElement) (FpElement, error) { - - res := coefficients[len(coefficients)-1] - - if monic { - res = *api.Add(&res, &x) - } - - for i := len(coefficients) - 2; i >= 0; i-- { - res = *api.Mul(&res, &x) - res = *api.Add(&res, &coefficients[i]) - } - return res, nil - -} - -// g1MulByZ multiplies x by [11] and stores the result in z -func g1MulByZ(api *FpApi, z, x *FpElement) { - eleven := emulated.ValueOf[emulated.BLS12381Fp]("11") - *z = *api.Mul(&eleven, x) -} - -// g1SqrtRatio computes the square root of u/v and returns 0 iff u/v was indeed a quadratic residue -// if not, we get sqrt(Z * u / v). Recall that Z is non-residue -// If v = 0, u/v is meaningless and the output is unspecified, without raising an error. -// The main idea is that since the computation of the square root involves taking large powers of u/v, the inversion of v can be avoided. -// -// nativeInputs[0] = u, nativeInputs[1]=v -// nativeOutput[0] = 1 if u/v is a QR, 0 otherwise, nativeOutput[1]=sqrt(u/v) or sqrt(Z u/v) -func g1SqrtRatioHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { - return emulated.UnwrapHint(nativeInputs, nativeOutputs, - func(mod *big.Int, inputs, outputs []*big.Int) error { - - var z, u, v fp.Element - - u.SetBigInt(inputs[0]) - v.SetBigInt(inputs[1]) - - // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-optimized-sqrt_ratio-for-q- (3 mod 4) - var tv1 fp.Element - tv1.Square(&v) // 1. tv1 = v² - var tv2 fp.Element - tv2.Mul(&u, &v) // 2. tv2 = u * v - tv1.Mul(&tv1, &tv2) // 3. tv1 = tv1 * tv2 - - var y1 fp.Element - { - var c1 big.Int - // c1 = 1000602388805416848354447456433976039139220704984751971333014534031007912622709466110671907282253916009473568139946 - c1.SetBytes([]byte{6, 128, 68, 122, 142, 95, 249, 166, 146, 198, 233, 237, 144, 210, 235, 53, 217, 29, 210, 225, 60, 225, 68, 175, 217, 204, 52, 168, 61, 172, 61, 137, 7, 170, 255, 255, 172, 84, 255, 255, 238, 127, 191, 255, 255, 255, 234, 170}) // c1 = (q - 3) / 4 # Integer arithmetic - - y1.Exp(tv1, &c1) // 4. y1 = tv1ᶜ¹ - } - - y1.Mul(&y1, &tv2) // 5. y1 = y1 * tv2 - - var y2 fp.Element - // c2 = sqrt(-Z) - tv3 := fp.Element{17544630987809824292, 17306709551153317753, 8299808889594647786, 5930295261504720397, 675038575008112577, 167386374569371918} - y2.Mul(&y1, &tv3) // 6. y2 = y1 * c2 - tv3.Square(&y1) // 7. tv3 = y1² - tv3.Mul(&tv3, &v) // 8. tv3 = tv3 * v - isQNr := tv3.NotEqual(&u) // 9. isQR = tv3 == u - z.Select(int(isQNr), &y1, &y2) // 10. y = CMOV(y2, y1, isQR) - z.BigInt(outputs[0]) - y1.BigInt(outputs[1]) - - return nil - }) -} - -// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-simplified-swu-method -// MapToCurve1 implements the SSWU map -// No cofactor clearing or isogeny -func MapToCurve1(api frontend.API, u *FpElement) (G1Affine, error) { - - var res G1Affine - - fpApi, err := emulated.NewField[emulated.BLS12381Fp](api) - if err != nil { - return res, err - } - - sswuIsoCurveCoeffA := emulated.ValueOf[emulated.BLS12381Fp]("0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d") - sswuIsoCurveCoeffB := emulated.ValueOf[emulated.BLS12381Fp]("0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0") - - tv1 := fpApi.Mul(u, u) // 1. tv1 = u² - - //mul tv1 by Z - g1MulByZ(fpApi, tv1, tv1) // 2. tv1 = Z * tv1 - - // var tv2 fp.Element - tv2 := fpApi.Mul(tv1, tv1) // 3. tv2 = tv1² - tv2 = fpApi.Add(tv2, tv1) // 4. tv2 = tv2 + tv1 - - // var tv3 fp.Element - // var tv4 fp.Element - tv4 := emulated.ValueOf[emulated.BLS12381Fp]("1") - tv3 := fpApi.Add(tv2, &tv4) // 5. tv3 = tv2 + 1 - tv3 = fpApi.Mul(tv3, &sswuIsoCurveCoeffB) // 6. tv3 = B * tv3 - - // tv2NZero := g1NotZero(&tv2) - tv2IsZero := fpApi.IsZero(tv2) - - // tv4 = Z - tv4 = emulated.ValueOf[emulated.BLS12381Fp]("11") - - tv2 = fpApi.Neg(tv2) // tv2.Neg(&tv2) - tv4 = *fpApi.Select(tv2IsZero, &tv4, tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) - tv4 = *fpApi.Mul(&tv4, &sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 - - tv2 = fpApi.Mul(tv3, tv3) // 9. tv2 = tv3² - - tv6 := fpApi.Mul(&tv4, &tv4) // 10. tv6 = tv4² - - tv5 := *fpApi.Mul(tv6, &sswuIsoCurveCoeffA) // 11. tv5 = A * tv6 - - tv2 = fpApi.Add(tv2, &tv5) // 12. tv2 = tv2 + tv5 - tv2 = fpApi.Mul(tv2, tv3) // 13. tv2 = tv2 * tv3 - tv6 = fpApi.Mul(tv6, &tv4) // 14. tv6 = tv6 * tv4 - - tv5 = *fpApi.Mul(tv6, &sswuIsoCurveCoeffB) // 15. tv5 = B * tv6 - tv2 = fpApi.Add(tv2, &tv5) // 16. tv2 = tv2 + tv5 - - // var x fp.Element - x := fpApi.Mul(tv1, tv3) // 17. x = tv1 * tv3 - - hint, err := fpApi.NewHint(g1SqrtRatioHint, 2, tv2, tv6) - if err != nil { - return res, err - } - - // TODO constrain gx1NSquare and y1 - // (gx1NSquare==1 AND (u/v) QNR ) OR (gx1NSquare==0 AND (u/v) QR ) - gx1NSquare := hint[0] - y1 := hint[1] // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) - - // var y fp.Element - y := fpApi.Mul(tv1, u) // 19. y = tv1 * u - - y = fpApi.Mul(y, y1) // 20. y = y * y1 - - x = fpApi.Select(gx1NSquare, x, tv3) // 21. x = CMOV(x, tv3, is_gx1_square) - y = fpApi.Select(gx1NSquare, y, y1) // 22. y = CMOV(y, y1, is_gx1_square) - - y1 = fpApi.Neg(y) - // y = fpApi.Select() // y.Select(int(g1Sgn0(u)^g1Sgn0(&y)), &y, &y1) - - // // 23. e1 = sgn0(u) == sgn0(y) - // // 24. y = CMOV(-y, y, e1) - - // x.Div(&x, &tv4) // 25. x = x / tv4 - - // return G1Affine{x, y} - - return res, nil -} +// WIP From f215ee1c0293de1180ffd349fa7488f68b82dcca Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Thu, 20 Mar 2025 12:20:30 +0100 Subject: [PATCH 025/105] feat: main function ok --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 0d90cb55b5..244c68ae19 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -180,6 +180,13 @@ func g1SqrtRatioHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) }) } +// g1Sgn0 returns the parity of a +func g1Sgn0(api *FpApi, a *FpElement) frontend.Variable { + aReduced := api.Reduce(a) + ab := api.ToBits(aReduced) + return ab[0] +} + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-simplified-swu-method // MapToCurve1 implements the SSWU map // No cofactor clearing or isogeny @@ -255,14 +262,14 @@ func MapToCurve1(api frontend.API, u *FpElement) (G1Affine, error) { y = fpApi.Select(gx1NSquare, y, y1) // 22. y = CMOV(y, y1, is_gx1_square) y1 = fpApi.Neg(y) - // y = fpApi.Select() // y.Select(int(g1Sgn0(u)^g1Sgn0(&y)), &y, &y1) + sel := api.IsZero(api.Sub(g1Sgn0(fpApi, u), g1Sgn0(fpApi, y))) + y = fpApi.Select(sel, y1, y) // // 23. e1 = sgn0(u) == sgn0(y) // // 24. y = CMOV(-y, y, e1) - // x.Div(&x, &tv4) // 25. x = x / tv4 + x = fpApi.Div(x, &tv4) // 25. x = x / tv4 - // return G1Affine{x, y} + return G1Affine{X: *x, Y: *y}, nil - return res, nil } From c2dcd1892900bf7ff0c4df8465414e8f32445ff2 Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Thu, 20 Mar 2025 12:31:25 +0100 Subject: [PATCH 026/105] feat: isogeny ok --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 244c68ae19..9fd50d1080 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -58,9 +58,9 @@ func g1IsogenyXDenominator(api *FpApi, x FpElement) (FpElement, error) { x) } -func g1IsogenyYNumerator(api *FpApi, x FpElement) (FpElement, error) { +func g1IsogenyYNumerator(api *FpApi, x, y FpElement) (FpElement, error) { - return g1EvalPolynomial( + ix, err := g1EvalPolynomial( api, false, []FpElement{ @@ -82,6 +82,12 @@ func g1IsogenyYNumerator(api *FpApi, x FpElement) (FpElement, error) { emulated.ValueOf[emulated.BLS12381Fp]("0x15e6be4e990f03ce4ea50b3b42df2eb5cb181d8f84965a3957add4fa95af01b2b665027efec01c7704b456be69c8b604"), }, x) + if err != nil { + return ix, err + } + + ix = *api.Mul(&ix, &y) + return ix, nil } func g1IsogenyYDenominator(api *FpApi, x FpElement) (FpElement, error) { @@ -125,6 +131,36 @@ func g1EvalPolynomial(api *FpApi, monic bool, coefficients []FpElement, x FpElem } +func g1Isogeny(fpApi *FpApi, p *G1Affine) error { + + den := make([]FpElement, 2) + var err error + + den[1], err = g1IsogenyYDenominator(fpApi, p.X) + if err != nil { + return err + } + den[0], err = g1IsogenyXDenominator(fpApi, p.X) + if err != nil { + return err + } + + p.Y, err = g1IsogenyYNumerator(fpApi, p.X, p.Y) + if err != nil { + return err + } + p.X, err = g1IsogenyXNumerator(fpApi, p.X) + if err != nil { + return err + } + + p.X = *fpApi.Div(&p.X, &den[0]) + p.Y = *fpApi.Div(&p.Y, &den[1]) + + return nil + +} + // g1MulByZ multiplies x by [11] and stores the result in z func g1MulByZ(api *FpApi, z, x *FpElement) { eleven := emulated.ValueOf[emulated.BLS12381Fp]("11") @@ -254,7 +290,7 @@ func MapToCurve1(api frontend.API, u *FpElement) (G1Affine, error) { y1 := hint[1] // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) // var y fp.Element - y := fpApi.Mul(tv1, u) // 19. y = tv1 * u + y := fpApi.Mul(tv1, u) // 19. y = tv1 * u y = fpApi.Mul(y, y1) // 20. y = y * y1 From f1566ea7b8dab8f4cda25ea5caa0014028424a94 Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Fri, 21 Mar 2025 11:48:47 +0100 Subject: [PATCH 027/105] feat: fixed api --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 71 ++++++++++++++++--- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 9fd50d1080..3135764628 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -131,33 +131,33 @@ func g1EvalPolynomial(api *FpApi, monic bool, coefficients []FpElement, x FpElem } -func g1Isogeny(fpApi *FpApi, p *G1Affine) error { +func g1Isogeny(fpApi *FpApi, p *G1Affine) (*G1Affine, error) { den := make([]FpElement, 2) var err error den[1], err = g1IsogenyYDenominator(fpApi, p.X) if err != nil { - return err + return nil, err } den[0], err = g1IsogenyXDenominator(fpApi, p.X) if err != nil { - return err + return nil, err } - p.Y, err = g1IsogenyYNumerator(fpApi, p.X, p.Y) + y, err := g1IsogenyYNumerator(fpApi, p.X, p.Y) if err != nil { - return err + return nil, err } - p.X, err = g1IsogenyXNumerator(fpApi, p.X) + x, err := g1IsogenyXNumerator(fpApi, p.X) if err != nil { - return err + return nil, err } - p.X = *fpApi.Div(&p.X, &den[0]) - p.Y = *fpApi.Div(&p.Y, &den[1]) + x = *fpApi.Div(&x, &den[0]) + y = *fpApi.Div(&y, &den[1]) - return nil + return &G1Affine{X: x, Y: y}, nil } @@ -223,6 +223,57 @@ func g1Sgn0(api *FpApi, a *FpElement) frontend.Variable { return ab[0] } +func ClearCofactor(g *G1, q *G1Affine) (*G1Affine, error) { + + // cf https://eprint.iacr.org/2019/403.pdf, 5 + + // mulBySeed + z := g.double(q) + z = g.add(z, q) + z = g.double(z) + z = g.doubleAndAdd(z, q) + z = g.doubleN(z, 2) + z = g.doubleAndAdd(z, q) + z = g.doubleN(z, 8) + z = g.doubleAndAdd(z, q) + z = g.doubleN(z, 31) + z = g.doubleAndAdd(z, q) + z = g.doubleN(z, 16) + + return z, nil + +} + +// MapToG1 invokes the SSWU map, and guarantees that the result is in g1 +func MapToG1(api frontend.API, u *FpElement) (*G1Affine, error) { + + res, err := MapToCurve1(api, u) + if err != nil { + return nil, err + } + //this is in an isogenous curve + fpApi, err := emulated.NewField[emulated.BLS12381Fp](api) + if err != nil { + return nil, err + } + z, err := g1Isogeny(fpApi, &res) + if err != nil { + return nil, err + } + + g1, err := NewG1(api) + if err != nil { + return nil, err + } + + z, err = ClearCofactor(g1, z) + if err != nil { + return nil, err + } + + return z, nil +} + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-simplified-swu-method // MapToCurve1 implements the SSWU map // No cofactor clearing or isogeny From 8a072b819c66b3c74e79c3d3b8020015701ce1ba Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Fri, 21 Mar 2025 15:14:27 +0100 Subject: [PATCH 028/105] fix: missing addAssign clear cofactor --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 3135764628..afb8e532fc 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -240,6 +240,9 @@ func ClearCofactor(g *G1, q *G1Affine) (*G1Affine, error) { z = g.doubleAndAdd(z, q) z = g.doubleN(z, 16) + // Add assign + z = g.add(z, q) + return z, nil } From 00018986701826a0bfe8e26b34302462ff1e688f Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Fri, 21 Mar 2025 17:12:46 +0100 Subject: [PATCH 029/105] fix: Clear cofactor ok --- .../emulated/sw_bls12381/map_to_g1_test.go | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 std/algebra/emulated/sw_bls12381/map_to_g1_test.go diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1_test.go b/std/algebra/emulated/sw_bls12381/map_to_g1_test.go new file mode 100644 index 0000000000..d4e28752a1 --- /dev/null +++ b/std/algebra/emulated/sw_bls12381/map_to_g1_test.go @@ -0,0 +1,48 @@ +package sw_bls12381 + +import ( + "fmt" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/test" +) + +// Test clear cofactor +type ClearCofactorCircuit struct { + Point G1Affine + Res G1Affine +} + +func (circuit *ClearCofactorCircuit) Define(api frontend.API) error { + g, err := NewG1(api) + if err != nil { + return err + } + clearedPoint, err := ClearCofactor(g, &circuit.Point) + if err != nil { + return err + } + g.AssertIsEqual(clearedPoint, &circuit.Res) + return nil +} + +func TestClearCofactor(t *testing.T) { + + assert := test.NewAssert(t) + _, _, g1, _ := bls12381.Generators() + fmt.Println(g1.String()) + var g2 bls12381.G1Affine + g2.ClearCofactor(&g1) + witness := ClearCofactorCircuit{ + Point: NewG1Affine(g1), + Res: NewG1Affine(g2), + } + err := test.IsSolved(&ClearCofactorCircuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + +} + +// Test Isogeny From 16ba9684b3290d9baf6fd7f32a29372b671e9fbc Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Sun, 23 Mar 2025 23:28:44 +0100 Subject: [PATCH 030/105] feat: correct values, but test fails --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 17 ++++--- .../emulated/sw_bls12381/map_to_g1_test.go | 44 ++++++++++++++++++- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index afb8e532fc..369cca1f7e 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -173,7 +173,7 @@ func g1MulByZ(api *FpApi, z, x *FpElement) { // The main idea is that since the computation of the square root involves taking large powers of u/v, the inversion of v can be avoided. // // nativeInputs[0] = u, nativeInputs[1]=v -// nativeOutput[0] = 1 if u/v is a QR, 0 otherwise, nativeOutput[1]=sqrt(u/v) or sqrt(Z u/v) +// nativeOutput[1] = 1 if u/v is a QR, 0 otherwise, nativeOutput[1]=sqrt(u/v) or sqrt(Z u/v) func g1SqrtRatioHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { return emulated.UnwrapHint(nativeInputs, nativeOutputs, func(mod *big.Int, inputs, outputs []*big.Int) error { @@ -186,6 +186,7 @@ func g1SqrtRatioHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-optimized-sqrt_ratio-for-q- (3 mod 4) var tv1 fp.Element tv1.Square(&v) // 1. tv1 = v² + var tv2 fp.Element tv2.Mul(&u, &v) // 2. tv2 = u * v tv1.Mul(&tv1, &tv2) // 3. tv1 = tv1 * tv2 @@ -209,8 +210,12 @@ func g1SqrtRatioHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) tv3.Mul(&tv3, &v) // 8. tv3 = tv3 * v isQNr := tv3.NotEqual(&u) // 9. isQR = tv3 == u z.Select(int(isQNr), &y1, &y2) // 10. y = CMOV(y2, y1, isQR) + + if isQNr != 0 { + isQNr = 1 + } z.BigInt(outputs[0]) - y1.BigInt(outputs[1]) + outputs[1] = big.NewInt(int64(isQNr)) return nil }) @@ -254,6 +259,7 @@ func MapToG1(api frontend.API, u *FpElement) (*G1Affine, error) { if err != nil { return nil, err } + //this is in an isogenous curve fpApi, err := emulated.NewField[emulated.BLS12381Fp](api) if err != nil { @@ -340,8 +346,8 @@ func MapToCurve1(api frontend.API, u *FpElement) (G1Affine, error) { // TODO constrain gx1NSquare and y1 // (gx1NSquare==1 AND (u/v) QNR ) OR (gx1NSquare==0 AND (u/v) QR ) - gx1NSquare := hint[0] - y1 := hint[1] // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) + gx1NSquare := hint[1].Limbs[0] + y1 := hint[0] // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) // var y fp.Element y := fpApi.Mul(tv1, u) // 19. y = tv1 * u @@ -352,8 +358,9 @@ func MapToCurve1(api frontend.API, u *FpElement) (G1Affine, error) { y = fpApi.Select(gx1NSquare, y, y1) // 22. y = CMOV(y, y1, is_gx1_square) y1 = fpApi.Neg(y) + y1 = fpApi.Reduce(y1) sel := api.IsZero(api.Sub(g1Sgn0(fpApi, u), g1Sgn0(fpApi, y))) - y = fpApi.Select(sel, y1, y) + y = fpApi.Select(sel, y, y1) // // 23. e1 = sgn0(u) == sgn0(y) // // 24. y = CMOV(-y, y, e1) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1_test.go b/std/algebra/emulated/sw_bls12381/map_to_g1_test.go index d4e28752a1..a7806e8778 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1_test.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1_test.go @@ -6,7 +6,9 @@ import ( "github.com/consensys/gnark-crypto/ecc" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/test" ) @@ -33,7 +35,6 @@ func TestClearCofactor(t *testing.T) { assert := test.NewAssert(t) _, _, g1, _ := bls12381.Generators() - fmt.Println(g1.String()) var g2 bls12381.G1Affine g2.ClearCofactor(&g1) witness := ClearCofactorCircuit{ @@ -45,4 +46,43 @@ func TestClearCofactor(t *testing.T) { } -// Test Isogeny +// Test MapToCurve +type MapToCurve struct { + U FpElement + Res G1Affine +} + +func (circuit *MapToCurve) Define(api frontend.API) error { + + g, err := NewG1(api) + if err != nil { + return err + } + + r, err := MapToCurve1(api, &circuit.U) + if err != nil { + return err + } + api.Println(r.Y.Limbs...) + + g.AssertIsEqual(&r, &circuit.Res) + + return nil +} + +func TestMapToCurve(t *testing.T) { + + assert := test.NewAssert(t) + var a fp.Element + a.SetRandom() + g := bls12381.MapToCurve1(&a) + fmt.Printf("g.Y = %s\n", g.Y.String()) + + witness := MapToCurve{ + U: emulated.ValueOf[emulated.BLS12381Fp](a.String()), + Res: NewG1Affine(g), + } + err := test.IsSolved(&MapToCurve{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + +} From 28fed0b55cad3e89a486a22138205f31197c6914 Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Mon, 24 Mar 2025 15:05:17 +0100 Subject: [PATCH 031/105] bug: nonnative limb checking fails --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 4 +- .../emulated/sw_bls12381/map_to_g1_test.go | 49 ++++++++++++++++--- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 369cca1f7e..0533b615b6 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -32,8 +32,8 @@ func g1IsogenyXNumerator(api *FpApi, x FpElement) (FpElement, error) { emulated.ValueOf[emulated.BLS12381Fp]("0x17b81e7701abdbe2e8743884d1117e53356de5ab275b4db1a682c62ef0f2753339b7c8f8c8f475af9ccb5618e3f0c88e"), emulated.ValueOf[emulated.BLS12381Fp]("0x80d3cf1f9a78fc47b90b33563be990dc43b756ce79f5574a2c596c928c5d1de4fa295f296b74e956d71986a8497e317"), emulated.ValueOf[emulated.BLS12381Fp]("0x169b1f8e1bcfa7c42e0c37515d138f22dd2ecb803a0c5c99676314baf4bb1b7fa3190b2edc0327797f241067be390c9e"), - emulated.ValueOf[emulated.BLS12381Fp](" 0x10321da079ce07e272d8ec09d2565b0dfa7dccdde6787f96d50af36003b14866f69b771f8c285decca67df3f1605fb7b"), - emulated.ValueOf[emulated.BLS12381Fp](" 0x6e08c248e260e70bd1e962381edee3d31d79d7e22c837bc23c0bf1bc24c6b68c24b1b80b64d391fa9c8ba2e8ba2d229"), + emulated.ValueOf[emulated.BLS12381Fp]("0x10321da079ce07e272d8ec09d2565b0dfa7dccdde6787f96d50af36003b14866f69b771f8c285decca67df3f1605fb7b"), + emulated.ValueOf[emulated.BLS12381Fp]("0x6e08c248e260e70bd1e962381edee3d31d79d7e22c837bc23c0bf1bc24c6b68c24b1b80b64d391fa9c8ba2e8ba2d229"), }, x) } diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1_test.go b/std/algebra/emulated/sw_bls12381/map_to_g1_test.go index a7806e8778..bad34ec063 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1_test.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1_test.go @@ -1,7 +1,6 @@ package sw_bls12381 import ( - "fmt" "testing" "github.com/consensys/gnark-crypto/ecc" @@ -47,12 +46,12 @@ func TestClearCofactor(t *testing.T) { } // Test MapToCurve -type MapToCurve struct { +type MapToCurveCircuit struct { U FpElement Res G1Affine } -func (circuit *MapToCurve) Define(api frontend.API) error { +func (circuit *MapToCurveCircuit) Define(api frontend.API) error { g, err := NewG1(api) if err != nil { @@ -63,7 +62,6 @@ func (circuit *MapToCurve) Define(api frontend.API) error { if err != nil { return err } - api.Println(r.Y.Limbs...) g.AssertIsEqual(&r, &circuit.Res) @@ -76,13 +74,50 @@ func TestMapToCurve(t *testing.T) { var a fp.Element a.SetRandom() g := bls12381.MapToCurve1(&a) - fmt.Printf("g.Y = %s\n", g.Y.String()) - witness := MapToCurve{ + witness := MapToCurveCircuit{ U: emulated.ValueOf[emulated.BLS12381Fp](a.String()), Res: NewG1Affine(g), } - err := test.IsSolved(&MapToCurve{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&MapToCurveCircuit{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } + +// Test Map to G1 +type MapToG1Circuit struct { + A FpElement + R G1Affine +} + +func (circuit *MapToG1Circuit) Define(api frontend.API) error { + + res, err := MapToG1(api, &circuit.A) + if err != nil { + return err + } + + g, err := NewG1(api) + if err != nil { + return err + } + + g.AssertIsEqual(res, &circuit.R) + + return nil +} + +func TestMapToG1(t *testing.T) { + + assert := test.NewAssert(t) + var a fp.Element + a.SetRandom() + g := bls12381.MapToG1(a) + + witness := MapToG1Circuit{ + A: emulated.ValueOf[emulated.BLS12381Fp](a.String()), + R: NewG1Affine(g), + } + err := test.IsSolved(&MapToG1Circuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} From d1bdd70ca170ddb0d9e97f648205d0d5a7b1494c Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Mon, 24 Mar 2025 19:20:48 +0100 Subject: [PATCH 032/105] clean: removed some dereferencing --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 109 +++++++++--------- 1 file changed, 52 insertions(+), 57 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 0533b615b6..5b754fed40 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -161,12 +161,6 @@ func g1Isogeny(fpApi *FpApi, p *G1Affine) (*G1Affine, error) { } -// g1MulByZ multiplies x by [11] and stores the result in z -func g1MulByZ(api *FpApi, z, x *FpElement) { - eleven := emulated.ValueOf[emulated.BLS12381Fp]("11") - *z = *api.Mul(&eleven, x) -} - // g1SqrtRatio computes the square root of u/v and returns 0 iff u/v was indeed a quadratic residue // if not, we get sqrt(Z * u / v). Recall that Z is non-residue // If v = 0, u/v is meaningless and the output is unspecified, without raising an error. @@ -252,47 +246,16 @@ func ClearCofactor(g *G1, q *G1Affine) (*G1Affine, error) { } -// MapToG1 invokes the SSWU map, and guarantees that the result is in g1 -func MapToG1(api frontend.API, u *FpElement) (*G1Affine, error) { - - res, err := MapToCurve1(api, u) - if err != nil { - return nil, err - } - - //this is in an isogenous curve - fpApi, err := emulated.NewField[emulated.BLS12381Fp](api) - if err != nil { - return nil, err - } - z, err := g1Isogeny(fpApi, &res) - if err != nil { - return nil, err - } - - g1, err := NewG1(api) - if err != nil { - return nil, err - } - - z, err = ClearCofactor(g1, z) - if err != nil { - return nil, err - } - - return z, nil -} - // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-simplified-swu-method // MapToCurve1 implements the SSWU map // No cofactor clearing or isogeny -func MapToCurve1(api frontend.API, u *FpElement) (G1Affine, error) { +func MapToCurve1(api frontend.API, u *FpElement) (*G1Affine, error) { var res G1Affine fpApi, err := emulated.NewField[emulated.BLS12381Fp](api) if err != nil { - return res, err + return &res, err } sswuIsoCurveCoeffA := emulated.ValueOf[emulated.BLS12381Fp]("0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d") @@ -300,8 +263,9 @@ func MapToCurve1(api frontend.API, u *FpElement) (G1Affine, error) { tv1 := fpApi.Mul(u, u) // 1. tv1 = u² - //mul tv1 by Z - g1MulByZ(fpApi, tv1, tv1) // 2. tv1 = Z * tv1 + //mul tv1 by Z ( g1MulByZ) + eleven := emulated.ValueOf[emulated.BLS12381Fp]("11") + tv1 = fpApi.Mul(&eleven, tv1) // var tv2 fp.Element tv2 := fpApi.Mul(tv1, tv1) // 3. tv2 = tv1² @@ -309,39 +273,39 @@ func MapToCurve1(api frontend.API, u *FpElement) (G1Affine, error) { // var tv3 fp.Element // var tv4 fp.Element - tv4 := emulated.ValueOf[emulated.BLS12381Fp]("1") - tv3 := fpApi.Add(tv2, &tv4) // 5. tv3 = tv2 + 1 + _tv4 := emulated.ValueOf[emulated.BLS12381Fp]("1") + tv3 := fpApi.Add(tv2, &_tv4) // 5. tv3 = tv2 + 1 tv3 = fpApi.Mul(tv3, &sswuIsoCurveCoeffB) // 6. tv3 = B * tv3 // tv2NZero := g1NotZero(&tv2) tv2IsZero := fpApi.IsZero(tv2) // tv4 = Z - tv4 = emulated.ValueOf[emulated.BLS12381Fp]("11") + _tv4 = emulated.ValueOf[emulated.BLS12381Fp]("11") - tv2 = fpApi.Neg(tv2) // tv2.Neg(&tv2) - tv4 = *fpApi.Select(tv2IsZero, &tv4, tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) - tv4 = *fpApi.Mul(&tv4, &sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 + tv2 = fpApi.Neg(tv2) // tv2.Neg(&tv2) + tv4 := fpApi.Select(tv2IsZero, &_tv4, tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) + tv4 = fpApi.Mul(tv4, &sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 tv2 = fpApi.Mul(tv3, tv3) // 9. tv2 = tv3² - tv6 := fpApi.Mul(&tv4, &tv4) // 10. tv6 = tv4² + tv6 := fpApi.Mul(tv4, tv4) // 10. tv6 = tv4² - tv5 := *fpApi.Mul(tv6, &sswuIsoCurveCoeffA) // 11. tv5 = A * tv6 + tv5 := fpApi.Mul(tv6, &sswuIsoCurveCoeffA) // 11. tv5 = A * tv6 - tv2 = fpApi.Add(tv2, &tv5) // 12. tv2 = tv2 + tv5 - tv2 = fpApi.Mul(tv2, tv3) // 13. tv2 = tv2 * tv3 - tv6 = fpApi.Mul(tv6, &tv4) // 14. tv6 = tv6 * tv4 + tv2 = fpApi.Add(tv2, tv5) // 12. tv2 = tv2 + tv5 + tv2 = fpApi.Mul(tv2, tv3) // 13. tv2 = tv2 * tv3 + tv6 = fpApi.Mul(tv6, tv4) // 14. tv6 = tv6 * tv4 - tv5 = *fpApi.Mul(tv6, &sswuIsoCurveCoeffB) // 15. tv5 = B * tv6 - tv2 = fpApi.Add(tv2, &tv5) // 16. tv2 = tv2 + tv5 + tv5 = fpApi.Mul(tv6, &sswuIsoCurveCoeffB) // 15. tv5 = B * tv6 + tv2 = fpApi.Add(tv2, tv5) // 16. tv2 = tv2 + tv5 // var x fp.Element x := fpApi.Mul(tv1, tv3) // 17. x = tv1 * tv3 hint, err := fpApi.NewHint(g1SqrtRatioHint, 2, tv2, tv6) if err != nil { - return res, err + return &res, err } // TODO constrain gx1NSquare and y1 @@ -365,8 +329,39 @@ func MapToCurve1(api frontend.API, u *FpElement) (G1Affine, error) { // // 23. e1 = sgn0(u) == sgn0(y) // // 24. y = CMOV(-y, y, e1) - x = fpApi.Div(x, &tv4) // 25. x = x / tv4 + x = fpApi.Div(x, tv4) // 25. x = x / tv4 + + return &G1Affine{X: *x, Y: *y}, nil + +} + +// MapToG1 invokes the SSWU map, and guarantees that the result is in g1 +func MapToG1(api frontend.API, u *FpElement) (*G1Affine, error) { + + res, err := MapToCurve1(api, u) + if err != nil { + return nil, err + } + + //this is in an isogenous curve + fpApi, err := emulated.NewField[emulated.BLS12381Fp](api) + if err != nil { + return nil, err + } + z, err := g1Isogeny(fpApi, res) + if err != nil { + return nil, err + } + + g1, err := NewG1(api) + if err != nil { + return nil, err + } - return G1Affine{X: *x, Y: *y}, nil + z, err = ClearCofactor(g1, z) + if err != nil { + return nil, err + } + return z, nil } From 1e617750d8bc2d757b4c8e99bbb6031fbc8ef428 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Mon, 24 Mar 2025 17:42:38 -0400 Subject: [PATCH 033/105] perf(pectra): optimize G2 scalar mul with GLV --- std/algebra/emulated/sw_bls12381/g2.go | 258 +++++++++++++++++++- std/algebra/emulated/sw_bls12381/g2_test.go | 2 +- std/algebra/emulated/sw_bls12381/hints.go | 57 +++++ std/algebra/emulated/sw_emulated/hints.go | 3 - std/evmprecompiles/15-blspairing.go | 2 +- 5 files changed, 304 insertions(+), 18 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/g2.go b/std/algebra/emulated/sw_bls12381/g2.go index 44c2684304..ee808abf3f 100644 --- a/std/algebra/emulated/sw_bls12381/g2.go +++ b/std/algebra/emulated/sw_bls12381/g2.go @@ -16,8 +16,9 @@ type G2 struct { fp *emulated.Field[BaseField] fr *emulated.Field[ScalarField] *fields_bls12381.Ext2 - u1, w *emulated.Element[BaseField] - v *fields_bls12381.E2 + u1, w, w2 *emulated.Element[BaseField] + eigenvalue *emulated.Element[ScalarField] + v *fields_bls12381.E2 } type g2AffP struct { @@ -53,19 +54,23 @@ func NewG2(api frontend.API) (*G2, error) { return nil, fmt.Errorf("new scalar api: %w", err) } w := emulated.ValueOf[BaseField]("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939436") + w2 := emulated.ValueOf[BaseField]("793479390729215512621379701633421447060886740281060493010456487427281649075476305620758731620350") + eigenvalue := emulated.ValueOf[ScalarField]("228988810152649578064853576960394133503") u1 := emulated.ValueOf[BaseField]("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939437") v := fields_bls12381.E2{ A0: emulated.ValueOf[BaseField]("2973677408986561043442465346520108879172042883009249989176415018091420807192182638567116318576472649347015917690530"), A1: emulated.ValueOf[BaseField]("1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257"), } return &G2{ - api: api, - fp: fp, - fr: fr, - Ext2: fields_bls12381.NewExt2(api), - w: &w, - u1: &u1, - v: &v, + api: api, + fp: fp, + fr: fr, + Ext2: fields_bls12381.NewExt2(api), + w: &w, + w2: &w2, + eigenvalue: &eigenvalue, + u1: &u1, + v: &v, }, nil } @@ -529,6 +534,233 @@ func (g2 *G2) scalarMulGeneric(p *G2Affine, s *Scalar, opts ...algopts.AlgebraOp return R0 } +// scalarMulGLV computes [s]Q using an efficient endomorphism and returns it. It doesn't modify Q nor s. +// It implements an optimized version based on algorithm 1 of [Halo] (see Section 6.2 and appendix C). +// +// ⚠️ The scalar s must be nonzero and the point Q different from (0,0) unless [algopts.WithCompleteArithmetic] is set. +// (0,0) is not on the curve but we conventionally take it as the +// neutral/infinity point as per the [EVM]. +// +// [Halo]: https://eprint.iacr.org/2019/1021.pdf +// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf +func (g2 *G2) scalarMulGLV(Q *G2Affine, s *Scalar, opts ...algopts.AlgebraOption) *G2Affine { + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(err) + } + addFn := g2.add + var selector frontend.Variable + if cfg.CompleteArithmetic { + addFn = g2.AddUnified + // if Q=(0,0) we assign a dummy (1,1) to Q and continue + selector = g2.api.And( + g2.api.And(g2.fp.IsZero(&Q.P.X.A0), g2.fp.IsZero(&Q.P.X.A1)), + g2.api.And(g2.fp.IsZero(&Q.P.Y.A0), g2.fp.IsZero(&Q.P.Y.A1)), + ) + one := g2.Ext2.One() + Q = g2.Select(selector, &G2Affine{P: g2AffP{X: *one, Y: *one}, Lines: nil}, Q) + } + + // We use the endomorphism à la GLV to compute [s]Q as + // [s1]Q + [s2]Φ(Q) + // the sub-scalars s1, s2 can be negative (bigints) in the hint. If so, + // they will be reduced in-circuit modulo the SNARK scalar field and not + // the emulated field. So we return in the hint |s1|, |s2| and boolean + // flags sdBits to negate the points Q, Φ(Q) instead of the corresponding + // sub-scalars. + + // decompose s into s1 and s2 + sd, err := g2.fr.NewHint(decomposeScalarG1Subscalars, 2, s, g2.eigenvalue) + if err != nil { + panic(fmt.Sprintf("compute GLV decomposition: %v", err)) + } + s1, s2 := sd[0], sd[1] + sdBits, err := g2.fr.NewHintWithNativeOutput(decomposeScalarG1Signs, 2, s, g2.eigenvalue) + if err != nil { + panic(fmt.Sprintf("compute GLV decomposition bits: %v", err)) + } + selector1, selector2 := sdBits[0], sdBits[1] + s3 := g2.fr.Select(selector1, g2.fr.Neg(s1), s1) + s4 := g2.fr.Select(selector2, g2.fr.Neg(s2), s2) + // s == s3 + [λ]s4 + g2.fr.AssertIsEqual( + g2.fr.Add(s3, g2.fr.Mul(s4, g2.eigenvalue)), + s, + ) + + s1bits := g2.fr.ToBits(s1) + s2bits := g2.fr.ToBits(s2) + nbits := 129 + + // precompute -Q, -Φ(Q), Φ(Q) + var tableQ, tablePhiQ [3]*G2Affine + negQY := g2.Ext2.Neg(&Q.P.Y) + tableQ[1] = &G2Affine{ + P: g2AffP{ + X: Q.P.X, + Y: *g2.Ext2.Select(selector1, negQY, &Q.P.Y), + }, + } + tableQ[0] = g2.neg(tableQ[1]) + tablePhiQ[1] = &G2Affine{ + P: g2AffP{ + X: *g2.Ext2.MulByElement(&Q.P.X, g2.w2), + Y: *g2.Ext2.Select(selector2, negQY, &Q.P.Y), + }, + } + tablePhiQ[0] = g2.neg(tablePhiQ[1]) + tableQ[2] = g2.triple(tableQ[1]) + tablePhiQ[2] = &G2Affine{ + P: g2AffP{ + X: *g2.Ext2.MulByElement(&tableQ[2].P.X, g2.w2), + Y: *g2.Ext2.Select(selector2, g2.Ext2.Neg(&tableQ[2].P.Y), &tableQ[2].P.Y), + }, + } + + // we suppose that the first bits of the sub-scalars are 1 and set: + // Acc = Q + Φ(Q) + Acc := g2.add(tableQ[1], tablePhiQ[1]) + + // At each iteration we need to compute: + // [2]Acc ± Q ± Φ(Q). + // We can compute [2]Acc and look up the (precomputed) point P from: + // B1 = Q+Φ(Q) + // B2 = -Q-Φ(Q) + // B3 = Q-Φ(Q) + // B4 = -Q+Φ(Q) + // + // If we extend this by merging two iterations, we need to look up P and P' + // both from {B1, B2, B3, B4} and compute: + // [2]([2]Acc+P)+P' = [4]Acc + T + // where T = [2]P+P'. So at each (merged) iteration, we can compute [4]Acc + // and look up T from the precomputed list of points: + // + // T = [3](Q + Φ(Q)) + // P = B1 and P' = B1 + T1 := g2.add(tableQ[2], tablePhiQ[2]) + // T = Q + Φ(Q) + // P = B1 and P' = B2 + T2 := Acc + // T = [3]Q + Φ(Q) + // P = B1 and P' = B3 + T3 := g2.add(tableQ[2], tablePhiQ[1]) + // T = Q + [3]Φ(Q) + // P = B1 and P' = B4 + T4 := g2.add(tableQ[1], tablePhiQ[2]) + // T = -Q - Φ(Q) + // P = B2 and P' = B1 + T5 := g2.neg(T2) + // T = -[3](Q + Φ(Q)) + // P = B2 and P' = B2 + T6 := g2.neg(T1) + // T = -Q - [3]Φ(Q) + // P = B2 and P' = B3 + T7 := g2.neg(T4) + // T = -[3]Q - Φ(Q) + // P = B2 and P' = B4 + T8 := g2.neg(T3) + // T = [3]Q - Φ(Q) + // P = B3 and P' = B1 + T9 := g2.add(tableQ[2], tablePhiQ[0]) + // T = Q - [3]Φ(Q) + // P = B3 and P' = B2 + T11 := g2.neg(tablePhiQ[2]) + T10 := g2.add(tableQ[1], T11) + // T = [3](Q - Φ(Q)) + // P = B3 and P' = B3 + T11 = g2.add(tableQ[2], T11) + // T = -Φ(Q) + Q + // P = B3 and P' = B4 + T12 := g2.add(tablePhiQ[0], tableQ[1]) + // T = [3]Φ(Q) - Q + // P = B4 and P' = B1 + T13 := g2.neg(T10) + // T = Φ(Q) - [3]Q + // P = B4 and P' = B2 + T14 := g2.neg(T9) + // T = Φ(Q) - Q + // P = B4 and P' = B3 + T15 := g2.neg(T12) + // T = [3](Φ(Q) - Q) + // P = B4 and P' = B4 + T16 := g2.neg(T11) + // note that half the points are negatives of the other half, + // hence have the same X coordinates. + + // when nbits is odd, we need to handle the first iteration separately + if nbits%2 == 0 { + // Acc = [2]Acc ± Q ± Φ(Q) + T := &G2Affine{ + P: g2AffP{ + X: *g2.Ext2.Select(g2.api.Xor(s1bits[nbits-1], s2bits[nbits-1]), &T12.P.X, &T5.P.X), + Y: *g2.Ext2.Lookup2(s1bits[nbits-1], s2bits[nbits-1], &T5.P.Y, &T12.P.Y, &T15.P.Y, &T2.P.Y), + }, + } + // We don't use doubleAndAdd here as it would involve edge cases + // when bits are 00 (T==-Acc) or 11 (T==Acc). + Acc = g2.double(Acc) + Acc = g2.add(Acc, T) + } else { + // when nbits is even we start the main loop at normally nbits - 1 + nbits++ + } + for i := nbits - 2; i > 0; i -= 2 { + // selectorY takes values in [0,15] + selectorY := g2.api.Add( + s1bits[i], + g2.api.Mul(s2bits[i], 2), + g2.api.Mul(s1bits[i-1], 4), + g2.api.Mul(s2bits[i-1], 8), + ) + // selectorX takes values in [0,7] s.t.: + // - when selectorY < 8: selectorX = selectorY + // - when selectorY >= 8: selectorX = 15 - selectorY + selectorX := g2.api.Add( + g2.api.Mul(selectorY, g2.api.Sub(1, g2.api.Mul(s2bits[i-1], 2))), + g2.api.Mul(s2bits[i-1], 15), + ) + // Bi.Y are distincts so we need a 16-to-1 multiplexer, + // but only half of the Bi.X are distinct so we need a 8-to-1. + T := &G2Affine{ + P: g2AffP{ + X: fields_bls12381.E2{ + A0: *g2.fp.Mux(selectorX, &T6.P.X.A0, &T10.P.X.A0, &T14.P.X.A0, &T2.P.X.A0, &T7.P.X.A0, &T11.P.X.A0, &T15.P.X.A0, &T3.P.X.A0), + A1: *g2.fp.Mux(selectorX, &T6.P.X.A1, &T10.P.X.A1, &T14.P.X.A1, &T2.P.X.A1, &T7.P.X.A1, &T11.P.X.A1, &T15.P.X.A1, &T3.P.X.A1), + }, + Y: fields_bls12381.E2{ + A0: *g2.fp.Mux(selectorY, + &T6.P.Y.A0, &T10.P.Y.A0, &T14.P.Y.A0, &T2.P.Y.A0, &T7.P.Y.A0, &T11.P.Y.A0, &T15.P.Y.A0, &T3.P.Y.A0, + &T8.P.Y.A0, &T12.P.Y.A0, &T16.P.Y.A0, &T4.P.Y.A0, &T5.P.Y.A0, &T9.P.Y.A0, &T13.P.Y.A0, &T1.P.Y.A0, + ), + A1: *g2.fp.Mux(selectorY, + &T6.P.Y.A1, &T10.P.Y.A1, &T14.P.Y.A1, &T2.P.Y.A1, &T7.P.Y.A1, &T11.P.Y.A1, &T15.P.Y.A1, &T3.P.Y.A1, + &T8.P.Y.A1, &T12.P.Y.A1, &T16.P.Y.A1, &T4.P.Y.A1, &T5.P.Y.A1, &T9.P.Y.A1, &T13.P.Y.A1, &T1.P.Y.A1, + ), + }, + }, + } + // Acc = [4]Acc + T + Acc = g2.double(Acc) + Acc = g2.doubleAndAdd(Acc, T) + } + + // i = 0 + // subtract the Q, Φ(Q) if the first bits are 0. + // When cfg.CompleteArithmetic is set, we use AddUnified instead of Add. + // This means when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0). + tableQ[0] = addFn(tableQ[0], Acc) + Acc = g2.Select(s1bits[0], Acc, tableQ[0]) + tablePhiQ[0] = addFn(tablePhiQ[0], Acc) + Acc = g2.Select(s2bits[0], Acc, tablePhiQ[0]) + + if cfg.CompleteArithmetic { + zero := g2.Ext2.Zero() + Acc = g2.Select(selector, &G2Affine{P: g2AffP{X: *zero, Y: *zero}}, Acc) + } + + return Acc +} + // MultiScalarMul computes the multi scalar multiplication of the points P and // scalars s. It returns an error if the length of the slices mismatch. If the // input slices are empty, then returns point at infinity. @@ -557,9 +789,9 @@ func (g2 *G2) MultiScalarMul(p []*G2Affine, s []*Scalar, opts ...algopts.Algebra return nil, fmt.Errorf("mismatching points and scalars slice lengths") } n := len(p) - res := g2.scalarMulGeneric(p[0], s[0], opts...) + res := g2.scalarMulGLV(p[0], s[0], opts...) for i := 1; i < n; i++ { - q := g2.scalarMulGeneric(p[i], s[i], opts...) + q := g2.scalarMulGLV(p[i], s[i], opts...) res = addFn(res, q) } return res, nil @@ -569,10 +801,10 @@ func (g2 *G2) MultiScalarMul(p []*G2Affine, s []*Scalar, opts ...algopts.Algebra return nil, fmt.Errorf("need scalar for folding") } gamma := s[0] - res := g2.scalarMulGeneric(p[len(p)-1], gamma, opts...) + res := g2.scalarMulGLV(p[len(p)-1], gamma, opts...) for i := len(p) - 2; i > 0; i-- { res = addFn(p[i], res) - res = g2.scalarMulGeneric(res, gamma, opts...) + res = g2.scalarMulGLV(res, gamma, opts...) } res = addFn(p[0], res) return res, nil diff --git a/std/algebra/emulated/sw_bls12381/g2_test.go b/std/algebra/emulated/sw_bls12381/g2_test.go index b619084241..9bbb011fad 100644 --- a/std/algebra/emulated/sw_bls12381/g2_test.go +++ b/std/algebra/emulated/sw_bls12381/g2_test.go @@ -23,7 +23,7 @@ func (c *mulG2Circuit) Define(api frontend.API) error { if err != nil { panic(err) } - res := g2.scalarMulGeneric(&c.In, &c.S) + res := g2.scalarMulGLV(&c.In, &c.S) g2.AssertIsEqual(res, &c.Res) return nil } diff --git a/std/algebra/emulated/sw_bls12381/hints.go b/std/algebra/emulated/sw_bls12381/hints.go index 0620406e7d..92dc8d0049 100644 --- a/std/algebra/emulated/sw_bls12381/hints.go +++ b/std/algebra/emulated/sw_bls12381/hints.go @@ -2,8 +2,10 @@ package sw_bls12381 import ( "errors" + "fmt" "math/big" + "github.com/consensys/gnark-crypto/ecc" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark/std/math/emulated" @@ -19,6 +21,8 @@ func GetHints() []solver.Hint { finalExpHint, pairingCheckHint, millerLoopAndCheckFinalExpHint, + decomposeScalarG1Subscalars, + decomposeScalarG1Signs, } } @@ -273,3 +277,56 @@ func millerLoopAndCheckFinalExpHint(nativeMod *big.Int, nativeInputs, nativeOutp return nil }) } + +func decomposeScalarG1Subscalars(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error { + return emulated.UnwrapHint(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 2 { + return fmt.Errorf("expecting two inputs") + } + if len(outputs) != 2 { + return fmt.Errorf("expecting two outputs") + } + glvBasis := new(ecc.Lattice) + ecc.PrecomputeLattice(field, inputs[1], glvBasis) + sp := ecc.SplitScalar(inputs[0], glvBasis) + outputs[0].Set(&(sp[0])) + outputs[1].Set(&(sp[1])) + // we need the absolute values for the in-circuit computations, + // otherwise the negative values will be reduced modulo the SNARK scalar + // field and not the emulated field. + // output0 = |s0| mod r + // output1 = |s1| mod r + if outputs[0].Sign() == -1 { + outputs[0].Neg(outputs[0]) + } + if outputs[1].Sign() == -1 { + outputs[1].Neg(outputs[1]) + } + + return nil + }) +} + +func decomposeScalarG1Signs(mod *big.Int, inputs []*big.Int, outputs []*big.Int) error { + return emulated.UnwrapHintWithNativeOutput(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 2 { + return fmt.Errorf("expecting two inputs") + } + if len(outputs) != 2 { + return fmt.Errorf("expecting two outputs") + } + glvBasis := new(ecc.Lattice) + ecc.PrecomputeLattice(field, inputs[1], glvBasis) + sp := ecc.SplitScalar(inputs[0], glvBasis) + outputs[0].SetUint64(0) + if sp[0].Sign() == -1 { + outputs[0].SetUint64(1) + } + outputs[1].SetUint64(0) + if sp[1].Sign() == -1 { + outputs[1].SetUint64(1) + } + + return nil + }) +} diff --git a/std/algebra/emulated/sw_emulated/hints.go b/std/algebra/emulated/sw_emulated/hints.go index f00ba4c804..687494a501 100644 --- a/std/algebra/emulated/sw_emulated/hints.go +++ b/std/algebra/emulated/sw_emulated/hints.go @@ -97,9 +97,6 @@ func decomposeScalarG1Signs(mod *big.Int, inputs []*big.Int, outputs []*big.Int) // BN254, BLS12-381, BW6-761 and Secp256k1, P256, P384 and STARK curve. func scalarMulHint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { return emulated.UnwrapHintWithNativeInput(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { - if len(outputs) != 2 { - return errors.New("expecting two outputs") - } if len(outputs) != 2 { return errors.New("expecting two outputs") } diff --git a/std/evmprecompiles/15-blspairing.go b/std/evmprecompiles/15-blspairing.go index c99ab050a7..ba007f2c6c 100644 --- a/std/evmprecompiles/15-blspairing.go +++ b/std/evmprecompiles/15-blspairing.go @@ -24,7 +24,7 @@ import ( // logic but we prefer a minimal number of circuits (2). // // See the methods [ECPairMillerLoopAndMul] and [ECPairMillerLoopAndFinalExpCheck] for the fixed circuits. -// See the method [ECPairBLSIsOnG2] for the check that Qᵢ are on G2. +// See the methods [ECPairBLSIsOnG1] and [ECPairBLSIsOnG2] for the check that Pᵢ and Qᵢ are on G1 and resp. G2. // // [BLS12_PAIRING_CHECK]: https://eips.ethereum.org/EIPS/eip-2537 // [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf From 5f36147652f753c5b2343686037dca6abaac5355 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Mon, 24 Mar 2025 17:49:25 -0400 Subject: [PATCH 034/105] fix(pectra): make linter happy --- std/algebra/emulated/sw_bls12381/map_to_g1_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1_test.go b/std/algebra/emulated/sw_bls12381/map_to_g1_test.go index bad34ec063..154ca7b055 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1_test.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1_test.go @@ -63,7 +63,7 @@ func (circuit *MapToCurveCircuit) Define(api frontend.API) error { return err } - g.AssertIsEqual(&r, &circuit.Res) + g.AssertIsEqual(r, &circuit.Res) return nil } From 581ddb45ffc03fa1ad883d9a7ab5f4095043d5f6 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Mon, 24 Mar 2025 17:53:03 -0400 Subject: [PATCH 035/105] fix(pectra): make linter happier --- std/algebra/emulated/sw_bls12381/pairing_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/std/algebra/emulated/sw_bls12381/pairing_test.go b/std/algebra/emulated/sw_bls12381/pairing_test.go index 1685999f6d..a246d1aec1 100644 --- a/std/algebra/emulated/sw_bls12381/pairing_test.go +++ b/std/algebra/emulated/sw_bls12381/pairing_test.go @@ -313,7 +313,10 @@ type MuxesCircuits struct { } func (c *MuxesCircuits) Define(api frontend.API) error { - g2api := NewG2(api) + g2api, err := NewG2(api) + if err != nil { + panic(err) + } pairing, err := NewPairing(api) if err != nil { return fmt.Errorf("new pairing: %w", err) From 97aeba5cd09eaf7c32df7065eea92b87c2c6cf4d Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Mon, 24 Mar 2025 17:55:52 -0400 Subject: [PATCH 036/105] fix(pectra): make linter much happier --- std/algebra/emulated/sw_bn254/pairing_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/std/algebra/emulated/sw_bn254/pairing_test.go b/std/algebra/emulated/sw_bn254/pairing_test.go index 4bb0645d37..345a9514ee 100644 --- a/std/algebra/emulated/sw_bn254/pairing_test.go +++ b/std/algebra/emulated/sw_bn254/pairing_test.go @@ -478,7 +478,10 @@ type MuxesCircuits struct { } func (c *MuxesCircuits) Define(api frontend.API) error { - g2api := NewG2(api) + g2api, err := NewG2(api) + if err != nil { + return fmt.Errorf("new G2: %w", err) + } pairing, err := NewPairing(api) if err != nil { return fmt.Errorf("new pairing: %w", err) From 506a359fbde78b0a873ebe1da5163c8529b31b8d Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 25 Mar 2025 00:16:39 +0000 Subject: [PATCH 037/105] fix: avoid dereferencing non-native values --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 58 +++++++++---------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 5b754fed40..faa246d644 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -16,7 +16,7 @@ func init() { type FpApi = emulated.Field[emulated.BLS12381Fp] type FpElement = emulated.Element[emulated.BLS12381Fp] -func g1IsogenyXNumerator(api *FpApi, x FpElement) (FpElement, error) { +func g1IsogenyXNumerator(api *FpApi, x *FpElement) (*FpElement, error) { return g1EvalPolynomial( api, @@ -38,7 +38,7 @@ func g1IsogenyXNumerator(api *FpApi, x FpElement) (FpElement, error) { x) } -func g1IsogenyXDenominator(api *FpApi, x FpElement) (FpElement, error) { +func g1IsogenyXDenominator(api *FpApi, x *FpElement) (*FpElement, error) { return g1EvalPolynomial( api, @@ -58,7 +58,7 @@ func g1IsogenyXDenominator(api *FpApi, x FpElement) (FpElement, error) { x) } -func g1IsogenyYNumerator(api *FpApi, x, y FpElement) (FpElement, error) { +func g1IsogenyYNumerator(api *FpApi, x, y *FpElement) (*FpElement, error) { ix, err := g1EvalPolynomial( api, @@ -86,11 +86,11 @@ func g1IsogenyYNumerator(api *FpApi, x, y FpElement) (FpElement, error) { return ix, err } - ix = *api.Mul(&ix, &y) + ix = api.Mul(ix, y) return ix, nil } -func g1IsogenyYDenominator(api *FpApi, x FpElement) (FpElement, error) { +func g1IsogenyYDenominator(api *FpApi, x *FpElement) (*FpElement, error) { return g1EvalPolynomial( api, @@ -115,17 +115,18 @@ func g1IsogenyYDenominator(api *FpApi, x FpElement) (FpElement, error) { x) } -func g1EvalPolynomial(api *FpApi, monic bool, coefficients []FpElement, x FpElement) (FpElement, error) { - - res := coefficients[len(coefficients)-1] +func g1EvalPolynomial(api *FpApi, monic bool, coefficients []FpElement, x *FpElement) (*FpElement, error) { + var res *FpElement if monic { - res = *api.Add(&res, &x) + res = api.Add(&coefficients[len(coefficients)-1], x) + } else { + res = &coefficients[len(coefficients)-1] } for i := len(coefficients) - 2; i >= 0; i-- { - res = *api.Mul(&res, &x) - res = *api.Add(&res, &coefficients[i]) + res = api.Mul(res, x) + res = api.Add(res, &coefficients[i]) } return res, nil @@ -133,31 +134,31 @@ func g1EvalPolynomial(api *FpApi, monic bool, coefficients []FpElement, x FpElem func g1Isogeny(fpApi *FpApi, p *G1Affine) (*G1Affine, error) { - den := make([]FpElement, 2) + den := make([]*FpElement, 2) var err error - den[1], err = g1IsogenyYDenominator(fpApi, p.X) + den[1], err = g1IsogenyYDenominator(fpApi, &p.X) if err != nil { return nil, err } - den[0], err = g1IsogenyXDenominator(fpApi, p.X) + den[0], err = g1IsogenyXDenominator(fpApi, &p.X) if err != nil { return nil, err } - y, err := g1IsogenyYNumerator(fpApi, p.X, p.Y) + y, err := g1IsogenyYNumerator(fpApi, &p.X, &p.Y) if err != nil { return nil, err } - x, err := g1IsogenyXNumerator(fpApi, p.X) + x, err := g1IsogenyXNumerator(fpApi, &p.X) if err != nil { return nil, err } - x = *fpApi.Div(&x, &den[0]) - y = *fpApi.Div(&y, &den[1]) + x = fpApi.Div(x, den[0]) + y = fpApi.Div(y, den[1]) - return &G1Affine{X: x, Y: y}, nil + return &G1Affine{X: *x, Y: *y}, nil } @@ -250,12 +251,12 @@ func ClearCofactor(g *G1, q *G1Affine) (*G1Affine, error) { // MapToCurve1 implements the SSWU map // No cofactor clearing or isogeny func MapToCurve1(api frontend.API, u *FpElement) (*G1Affine, error) { - - var res G1Affine + one := emulated.ValueOf[emulated.BLS12381Fp]("1") + eleven := emulated.ValueOf[emulated.BLS12381Fp]("11") fpApi, err := emulated.NewField[emulated.BLS12381Fp](api) if err != nil { - return &res, err + return nil, err } sswuIsoCurveCoeffA := emulated.ValueOf[emulated.BLS12381Fp]("0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d") @@ -264,7 +265,6 @@ func MapToCurve1(api frontend.API, u *FpElement) (*G1Affine, error) { tv1 := fpApi.Mul(u, u) // 1. tv1 = u² //mul tv1 by Z ( g1MulByZ) - eleven := emulated.ValueOf[emulated.BLS12381Fp]("11") tv1 = fpApi.Mul(&eleven, tv1) // var tv2 fp.Element @@ -273,19 +273,17 @@ func MapToCurve1(api frontend.API, u *FpElement) (*G1Affine, error) { // var tv3 fp.Element // var tv4 fp.Element - _tv4 := emulated.ValueOf[emulated.BLS12381Fp]("1") - tv3 := fpApi.Add(tv2, &_tv4) // 5. tv3 = tv2 + 1 + tv3 := fpApi.Add(tv2, &one) // 5. tv3 = tv2 + 1 tv3 = fpApi.Mul(tv3, &sswuIsoCurveCoeffB) // 6. tv3 = B * tv3 // tv2NZero := g1NotZero(&tv2) tv2IsZero := fpApi.IsZero(tv2) // tv4 = Z - _tv4 = emulated.ValueOf[emulated.BLS12381Fp]("11") - tv2 = fpApi.Neg(tv2) // tv2.Neg(&tv2) - tv4 := fpApi.Select(tv2IsZero, &_tv4, tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) - tv4 = fpApi.Mul(tv4, &sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 + tv2 = fpApi.Neg(tv2) // tv2.Neg(&tv2) + tv4 := fpApi.Select(tv2IsZero, &eleven, tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) + tv4 = fpApi.Mul(tv4, &sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 tv2 = fpApi.Mul(tv3, tv3) // 9. tv2 = tv3² @@ -305,7 +303,7 @@ func MapToCurve1(api frontend.API, u *FpElement) (*G1Affine, error) { hint, err := fpApi.NewHint(g1SqrtRatioHint, 2, tv2, tv6) if err != nil { - return &res, err + return nil, err } // TODO constrain gx1NSquare and y1 From fb37104d127932dce59940e1a964f75857de183d Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Tue, 25 Mar 2025 14:21:27 +0100 Subject: [PATCH 038/105] feat: hint is constrained --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index faa246d644..735b2a8f9b 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -298,7 +298,6 @@ func MapToCurve1(api frontend.API, u *FpElement) (*G1Affine, error) { tv5 = fpApi.Mul(tv6, &sswuIsoCurveCoeffB) // 15. tv5 = B * tv6 tv2 = fpApi.Add(tv2, tv5) // 16. tv2 = tv2 + tv5 - // var x fp.Element x := fpApi.Mul(tv1, tv3) // 17. x = tv1 * tv3 hint, err := fpApi.NewHint(g1SqrtRatioHint, 2, tv2, tv6) @@ -306,10 +305,27 @@ func MapToCurve1(api frontend.API, u *FpElement) (*G1Affine, error) { return nil, err } + y1 := hint[0] // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) + // TODO constrain gx1NSquare and y1 // (gx1NSquare==1 AND (u/v) QNR ) OR (gx1NSquare==0 AND (u/v) QR ) gx1NSquare := hint[1].Limbs[0] - y1 := hint[0] // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) + + api.AssertIsBoolean(gx1NSquare) + y1Squarev := fpApi.Mul(y1, y1) + y1Squarev = fpApi.Mul(y1Squarev, tv6) + uz := fpApi.Mul(tv2, &eleven) + ysvMinusuz := fpApi.Sub(y1Squarev, uz) + isQNRWitness := fpApi.IsZero(ysvMinusuz) + cond1 := api.And(isQNRWitness, gx1NSquare) + + ysvMinusu := fpApi.Sub(y1Squarev, tv2) + isQRWitness := fpApi.IsZero(ysvMinusu) + isQR := api.Sub(1, gx1NSquare) + cond2 := api.And(isQR, isQRWitness) + + cond := api.Or(cond1, cond2) + api.AssertIsEqual(cond, 1) // var y fp.Element y := fpApi.Mul(tv1, u) // 19. y = tv1 * u From abe3cbbc88146fe965e3b371d84da381ed895814 Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Tue, 25 Mar 2025 15:01:58 +0100 Subject: [PATCH 039/105] feat: added full precompile --- std/evmprecompiles/16-blsmaptog1.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/std/evmprecompiles/16-blsmaptog1.go b/std/evmprecompiles/16-blsmaptog1.go index 5a7c62a5de..cf4b527de2 100644 --- a/std/evmprecompiles/16-blsmaptog1.go +++ b/std/evmprecompiles/16-blsmaptog1.go @@ -1,3 +1,22 @@ package evmprecompiles -// WIP +import ( + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" + "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" + "github.com/consensys/gnark/std/math/emulated" +) + +// ECMapToG1 implements[BLS12_MAP_FP_TO_G1] precompile contract at adress 0x10. +// +// [ECMapToG1]: https://eips.ethereum.org/EIPS/eip-2537 +func ECMapToG1(api frontend.API, u *emulated.Element[emulated.BLS12381Fp]) *sw_emulated.AffinePoint[emulated.BLS12381Fp] { + + res, err := sw_bls12381.MapToG1(api, u) + if err != nil { + panic(err) + } + + return res + +} From a7ef40d1bd9b4544c826bcaa1a6316fdfdd07912 Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Tue, 25 Mar 2025 15:06:41 +0100 Subject: [PATCH 040/105] feat: corrected name --- std/evmprecompiles/16-blsmaptog1.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/std/evmprecompiles/16-blsmaptog1.go b/std/evmprecompiles/16-blsmaptog1.go index cf4b527de2..ed3c080e60 100644 --- a/std/evmprecompiles/16-blsmaptog1.go +++ b/std/evmprecompiles/16-blsmaptog1.go @@ -7,10 +7,10 @@ import ( "github.com/consensys/gnark/std/math/emulated" ) -// ECMapToG1 implements[BLS12_MAP_FP_TO_G1] precompile contract at adress 0x10. +// ECMapToG1BLS implements[BLS12_MAP_FP_TO_G1] precompile contract at adress 0x10. // -// [ECMapToG1]: https://eips.ethereum.org/EIPS/eip-2537 -func ECMapToG1(api frontend.API, u *emulated.Element[emulated.BLS12381Fp]) *sw_emulated.AffinePoint[emulated.BLS12381Fp] { +// [ECMapToG1BLS]: https://eips.ethereum.org/EIPS/eip-2537 +func ECMapToG1BLS(api frontend.API, u *emulated.Element[emulated.BLS12381Fp]) *sw_emulated.AffinePoint[emulated.BLS12381Fp] { res, err := sw_bls12381.MapToG1(api, u) if err != nil { From c36c352e14a01292275a98895a45971cdee0fdbf Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Tue, 25 Mar 2025 15:27:42 +0100 Subject: [PATCH 041/105] fix: replace OR with XOR --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 735b2a8f9b..e6c5d4c1d0 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -324,7 +324,7 @@ func MapToCurve1(api frontend.API, u *FpElement) (*G1Affine, error) { isQR := api.Sub(1, gx1NSquare) cond2 := api.And(isQR, isQRWitness) - cond := api.Or(cond1, cond2) + cond := api.Xor(cond1, cond2) api.AssertIsEqual(cond, 1) // var y fp.Element From 6cd996aa73600638fe7c62ca07b253ac36488d68 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 4 Apr 2025 14:18:20 +0000 Subject: [PATCH 042/105] refactor: address some of the review comments --- std/algebra/emulated/sw_bls12381/g1.go | 4 +-- std/algebra/emulated/sw_bls12381/g2.go | 33 +++++---------------- std/algebra/emulated/sw_bls12381/g2_test.go | 6 ++-- std/algebra/emulated/sw_bls12381/pairing.go | 30 +++---------------- std/algebra/emulated/sw_bn254/pairing.go | 5 ++-- 5 files changed, 19 insertions(+), 59 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/g1.go b/std/algebra/emulated/sw_bls12381/g1.go index 48aef851a1..1bb96c16a0 100644 --- a/std/algebra/emulated/sw_bls12381/g1.go +++ b/std/algebra/emulated/sw_bls12381/g1.go @@ -178,9 +178,7 @@ func (g1 *G1) computeCurveEquation(P *G1Affine) (left, right *baseEl) { b := g1.curveF.Select(selector, g1.curveF.Zero(), &four) left = g1.curveF.Mul(&P.Y, &P.Y) - right = g1.curveF.Mul(&P.X, &P.X) - right = g1.curveF.Mul(right, &P.X) - right = g1.curveF.Add(right, b) + right = g1.curveF.Eval([][]*emulated.Element[BaseField]{{&P.X, &P.X, &P.X}, {b}}, []int{1, 1}) return left, right } diff --git a/std/algebra/emulated/sw_bls12381/g2.go b/std/algebra/emulated/sw_bls12381/g2.go index ee808abf3f..59ec967e9e 100644 --- a/std/algebra/emulated/sw_bls12381/g2.go +++ b/std/algebra/emulated/sw_bls12381/g2.go @@ -177,7 +177,7 @@ func (g2 *G2) AddUnified(p, q *G2Affine) *G2Affine { yr := g2.Sub(&p.P.X, xr) yr = g2.Mul(yr, λ) yr = g2.Sub(yr, &p.P.Y) - result := G2Affine{ + result := &G2Affine{ P: g2AffP{X: *xr, Y: *yr}, Lines: nil, } @@ -188,13 +188,13 @@ func (g2 *G2) AddUnified(p, q *G2Affine) *G2Affine { Lines: nil, } // if p=([0,0],[0,0]) return q - result = *g2.Select(selector1, q, &result) + result = g2.Select(selector1, q, result) // if q=([0,0],[0,0]) return p - result = *g2.Select(selector2, p, &result) + result = g2.Select(selector2, p, result) // if p.y + q.y = 0, return ([0,0],[0,0]) - result = *g2.Select(selector3, &infinity, &result) + result = g2.Select(selector3, &infinity, result) - return &result + return result } func (g2 G2) add(p, q *G2Affine) *G2Affine { @@ -416,6 +416,7 @@ func (g2 *G2) computeTwistEquation(Q *G2Affine) (left, right *fields_bls12381.E2 b := g2.Ext2.Select(selector, g2.Ext2.Zero(), &bTwist) left = g2.Ext2.Square(&Q.P.Y) + // TODO: use Eval for right right = g2.Ext2.Square(&Q.P.X) right = g2.Ext2.Mul(right, &Q.P.X) right = g2.Ext2.Add(right, b) @@ -497,8 +498,7 @@ func (g2 *G2) scalarMulGeneric(p *G2Affine, s *Scalar, opts ...algopts.AlgebraOp } var st ScalarField - sr := g2.fr.Reduce(s) - sBits := g2.fr.ToBits(sr) + sBits := g2.fr.ToBitsCanonical(s) n := st.Modulus().BitLen() if cfg.NbScalarBits > 2 && cfg.NbScalarBits < n { n = cfg.NbScalarBits @@ -590,7 +590,6 @@ func (g2 *G2) scalarMulGLV(Q *G2Affine, s *Scalar, opts ...algopts.AlgebraOption s1bits := g2.fr.ToBits(s1) s2bits := g2.fr.ToBits(s2) - nbits := 129 // precompute -Q, -Φ(Q), Φ(Q) var tableQ, tablePhiQ [3]*G2Affine @@ -687,23 +686,7 @@ func (g2 *G2) scalarMulGLV(Q *G2Affine, s *Scalar, opts ...algopts.AlgebraOption // note that half the points are negatives of the other half, // hence have the same X coordinates. - // when nbits is odd, we need to handle the first iteration separately - if nbits%2 == 0 { - // Acc = [2]Acc ± Q ± Φ(Q) - T := &G2Affine{ - P: g2AffP{ - X: *g2.Ext2.Select(g2.api.Xor(s1bits[nbits-1], s2bits[nbits-1]), &T12.P.X, &T5.P.X), - Y: *g2.Ext2.Lookup2(s1bits[nbits-1], s2bits[nbits-1], &T5.P.Y, &T12.P.Y, &T15.P.Y, &T2.P.Y), - }, - } - // We don't use doubleAndAdd here as it would involve edge cases - // when bits are 00 (T==-Acc) or 11 (T==Acc). - Acc = g2.double(Acc) - Acc = g2.add(Acc, T) - } else { - // when nbits is even we start the main loop at normally nbits - 1 - nbits++ - } + nbits := 130 for i := nbits - 2; i > 0; i -= 2 { // selectorY takes values in [0,15] selectorY := g2.api.Add( diff --git a/std/algebra/emulated/sw_bls12381/g2_test.go b/std/algebra/emulated/sw_bls12381/g2_test.go index 9bbb011fad..2b9d42f794 100644 --- a/std/algebra/emulated/sw_bls12381/g2_test.go +++ b/std/algebra/emulated/sw_bls12381/g2_test.go @@ -23,8 +23,10 @@ func (c *mulG2Circuit) Define(api frontend.API) error { if err != nil { panic(err) } - res := g2.scalarMulGLV(&c.In, &c.S) - g2.AssertIsEqual(res, &c.Res) + res1 := g2.scalarMulGLV(&c.In, &c.S) + res2 := g2.scalarMulGeneric(&c.In, &c.S) + g2.AssertIsEqual(res1, &c.Res) + g2.AssertIsEqual(res2, &c.Res) return nil } diff --git a/std/algebra/emulated/sw_bls12381/pairing.go b/std/algebra/emulated/sw_bls12381/pairing.go index 9bbf854477..dacffecf44 100644 --- a/std/algebra/emulated/sw_bls12381/pairing.go +++ b/std/algebra/emulated/sw_bls12381/pairing.go @@ -316,9 +316,8 @@ func (pr Pairing) AssertIsOnG1(P *G1Affine) { pr.g1.AssertIsOnG1(P) } -// IsOnG1 returns a boolean indicating if the G1 point is in the subgroup. The -// method assumes that the point is already on the curve. Call -// [Pairing.AssertIsOnTwist] before to ensure point is on the curve. +// IsOnG1 returns a boolean indicating if the G1 point is on the curve and in +// the prime subgroup. func (pr Pairing) IsOnG1(P *G1Affine) frontend.Variable { // 1 - is Q on curve isOnCurve := pr.IsOnCurve(P) @@ -345,9 +344,8 @@ func (pr Pairing) AssertIsOnG2(Q *G2Affine) { pr.g2.AssertIsOnG2(Q) } -// IsOnG2 returns a boolean indicating if the G2 point is in the subgroup. The -// method assumes that the point is already on the curve. Call -// [Pairing.AssertIsOnTwist] before to ensure point is on the curve. +// IsOnG2 returns a boolean indicating if the G2 point is on the curve and in +// the prime subgroup. func (pr Pairing) IsOnG2(Q *G2Affine) frontend.Variable { // 1 - is Q on curve isOnCurve := pr.IsOnTwist(Q) @@ -715,26 +713,6 @@ func (pr Pairing) tripleStep(p1 *g2AffP) (*g2AffP, *lineEvaluation, *lineEvaluat return &res, &line1, &line2 } -// tangentCompute computes the tangent line to p1, but does not compute [2]p1. -func (pr Pairing) tangentCompute(p1 *g2AffP) *lineEvaluation { - - // λ = 3x²/2y - n := pr.Ext2.Square(&p1.X) - three := big.NewInt(3) - n = pr.Ext2.MulByConstElement(n, three) - d := pr.Ext2.Double(&p1.Y) - λ := pr.Ext2.DivUnchecked(n, d) - - var line lineEvaluation - mone := pr.curveF.NewElement(-1) - line.R0 = *λ - line.R1.A0 = *pr.curveF.Eval([][]*baseEl{{&λ.A0, &p1.X.A0}, {mone, &λ.A1, &p1.X.A1}, {mone, &p1.Y.A0}}, []int{1, 1, 1}) - line.R1.A1 = *pr.curveF.Eval([][]*baseEl{{&λ.A0, &p1.X.A1}, {&λ.A1, &p1.X.A0}, {mone, &p1.Y.A1}}, []int{1, 1, 1}) - - return &line - -} - // MillerLoopAndMul computes the Miller loop between P and Q // and multiplies it in 𝔽p¹² by previous. // diff --git a/std/algebra/emulated/sw_bn254/pairing.go b/std/algebra/emulated/sw_bn254/pairing.go index 4c80375eba..20a12b7c15 100644 --- a/std/algebra/emulated/sw_bn254/pairing.go +++ b/std/algebra/emulated/sw_bn254/pairing.go @@ -494,9 +494,8 @@ func (pr Pairing) AssertIsOnG2(Q *G2Affine) { pr.g2.AssertIsEqual(Q, _Q) } -// IsOnG2 returns a boolean indicating if the G2 point is in the subgroup. The -// method assumes that the point is already on the curve. Call -// [Pairing.AssertIsOnTwist] before to ensure point is on the curve. +// IsOnG2 returns a boolean indicating if the G2 point is on the curve and in +// the subgroup. func (pr Pairing) IsOnG2(Q *G2Affine) frontend.Variable { // 1 - is Q on curve isOnCurve := pr.IsOnTwist(Q) From dcada5cb537391cbb2c6ddfd05439ba65169a60b Mon Sep 17 00:00:00 2001 From: Arya Tabaie Date: Wed, 2 Apr 2025 15:45:51 -0500 Subject: [PATCH 043/105] GKR Gate Registry (#1442) --- constraint/bls12-377/gkr.go | 3 +- constraint/bls12-381/gkr.go | 3 +- constraint/bls24-315/gkr.go | 3 +- constraint/bls24-317/gkr.go | 3 +- constraint/bn254/gkr.go | 3 +- constraint/bw6-633/gkr.go | 3 +- constraint/bw6-761/gkr.go | 3 +- frontend/api.go | 2 +- go.mod | 4 +- go.sum | 8 +- .../template/representations/gkr.go.tmpl | 3 +- internal/tinyfield/element.go | 10 ++ internal/tinyfield/element_test.go | 50 +++--- internal/tinyfield/vector.go | 24 +++ internal/tinyfield/vector_test.go | 2 +- std/gkr/api.go | 18 +- std/gkr/api_test.go | 57 +++--- std/gkr/bn254_wrapper_api.go | 111 ++++++++++++ std/gkr/compile.go | 2 +- std/gkr/gkr.go | 122 ++++++------- std/gkr/gkr_test.go | 17 +- std/gkr/registry.go | 147 ++++++++++++++++ .../resources/single_input_two_outs.json | 2 +- .../resources/single_mul_gate.json | 2 +- std/gkr/testing.go | 145 +++++++++------- std/hash/poseidon2/poseidon2.go | 2 +- std/permutation/poseidon2/gkr.go | 163 +++++++----------- std/permutation/poseidon2/gkr_test.go | 8 +- std/permutation/poseidon2/poseidon2.go | 2 +- 29 files changed, 592 insertions(+), 330 deletions(-) create mode 100644 std/gkr/bn254_wrapper_api.go create mode 100644 std/gkr/registry.go diff --git a/constraint/bls12-377/gkr.go b/constraint/bls12-377/gkr.go index 948ed1510d..744f22525c 100644 --- a/constraint/bls12-377/gkr.go +++ b/constraint/bls12-377/gkr.go @@ -29,9 +29,8 @@ type GkrSolvingData struct { func convertCircuit(noPtr constraint.GkrCircuit) (gkr.Circuit, error) { resCircuit := make(gkr.Circuit, len(noPtr)) - var found bool for i := range noPtr { - if resCircuit[i].Gate, found = gkr.Gates[noPtr[i].Gate]; !found && noPtr[i].Gate != "" { + if resCircuit[i].Gate = gkr.GetGate(gkr.GateName(noPtr[i].Gate)); resCircuit[i].Gate == nil && noPtr[i].Gate != "" { return nil, fmt.Errorf("gate \"%s\" not found", noPtr[i].Gate) } resCircuit[i].Inputs = algo_utils.Map(noPtr[i].Inputs, algo_utils.SlicePtrAt(resCircuit)) diff --git a/constraint/bls12-381/gkr.go b/constraint/bls12-381/gkr.go index acf57d9d88..b3b22b9a95 100644 --- a/constraint/bls12-381/gkr.go +++ b/constraint/bls12-381/gkr.go @@ -29,9 +29,8 @@ type GkrSolvingData struct { func convertCircuit(noPtr constraint.GkrCircuit) (gkr.Circuit, error) { resCircuit := make(gkr.Circuit, len(noPtr)) - var found bool for i := range noPtr { - if resCircuit[i].Gate, found = gkr.Gates[noPtr[i].Gate]; !found && noPtr[i].Gate != "" { + if resCircuit[i].Gate = gkr.GetGate(gkr.GateName(noPtr[i].Gate)); resCircuit[i].Gate == nil && noPtr[i].Gate != "" { return nil, fmt.Errorf("gate \"%s\" not found", noPtr[i].Gate) } resCircuit[i].Inputs = algo_utils.Map(noPtr[i].Inputs, algo_utils.SlicePtrAt(resCircuit)) diff --git a/constraint/bls24-315/gkr.go b/constraint/bls24-315/gkr.go index e39d7447c7..ba328c8bb4 100644 --- a/constraint/bls24-315/gkr.go +++ b/constraint/bls24-315/gkr.go @@ -29,9 +29,8 @@ type GkrSolvingData struct { func convertCircuit(noPtr constraint.GkrCircuit) (gkr.Circuit, error) { resCircuit := make(gkr.Circuit, len(noPtr)) - var found bool for i := range noPtr { - if resCircuit[i].Gate, found = gkr.Gates[noPtr[i].Gate]; !found && noPtr[i].Gate != "" { + if resCircuit[i].Gate = gkr.GetGate(gkr.GateName(noPtr[i].Gate)); resCircuit[i].Gate == nil && noPtr[i].Gate != "" { return nil, fmt.Errorf("gate \"%s\" not found", noPtr[i].Gate) } resCircuit[i].Inputs = algo_utils.Map(noPtr[i].Inputs, algo_utils.SlicePtrAt(resCircuit)) diff --git a/constraint/bls24-317/gkr.go b/constraint/bls24-317/gkr.go index 76d080a489..be02e3455c 100644 --- a/constraint/bls24-317/gkr.go +++ b/constraint/bls24-317/gkr.go @@ -29,9 +29,8 @@ type GkrSolvingData struct { func convertCircuit(noPtr constraint.GkrCircuit) (gkr.Circuit, error) { resCircuit := make(gkr.Circuit, len(noPtr)) - var found bool for i := range noPtr { - if resCircuit[i].Gate, found = gkr.Gates[noPtr[i].Gate]; !found && noPtr[i].Gate != "" { + if resCircuit[i].Gate = gkr.GetGate(gkr.GateName(noPtr[i].Gate)); resCircuit[i].Gate == nil && noPtr[i].Gate != "" { return nil, fmt.Errorf("gate \"%s\" not found", noPtr[i].Gate) } resCircuit[i].Inputs = algo_utils.Map(noPtr[i].Inputs, algo_utils.SlicePtrAt(resCircuit)) diff --git a/constraint/bn254/gkr.go b/constraint/bn254/gkr.go index 88dd7905ab..21731b8ac9 100644 --- a/constraint/bn254/gkr.go +++ b/constraint/bn254/gkr.go @@ -29,9 +29,8 @@ type GkrSolvingData struct { func convertCircuit(noPtr constraint.GkrCircuit) (gkr.Circuit, error) { resCircuit := make(gkr.Circuit, len(noPtr)) - var found bool for i := range noPtr { - if resCircuit[i].Gate, found = gkr.Gates[noPtr[i].Gate]; !found && noPtr[i].Gate != "" { + if resCircuit[i].Gate = gkr.GetGate(gkr.GateName(noPtr[i].Gate)); resCircuit[i].Gate == nil && noPtr[i].Gate != "" { return nil, fmt.Errorf("gate \"%s\" not found", noPtr[i].Gate) } resCircuit[i].Inputs = algo_utils.Map(noPtr[i].Inputs, algo_utils.SlicePtrAt(resCircuit)) diff --git a/constraint/bw6-633/gkr.go b/constraint/bw6-633/gkr.go index 81fbe4c52c..125da817df 100644 --- a/constraint/bw6-633/gkr.go +++ b/constraint/bw6-633/gkr.go @@ -29,9 +29,8 @@ type GkrSolvingData struct { func convertCircuit(noPtr constraint.GkrCircuit) (gkr.Circuit, error) { resCircuit := make(gkr.Circuit, len(noPtr)) - var found bool for i := range noPtr { - if resCircuit[i].Gate, found = gkr.Gates[noPtr[i].Gate]; !found && noPtr[i].Gate != "" { + if resCircuit[i].Gate = gkr.GetGate(gkr.GateName(noPtr[i].Gate)); resCircuit[i].Gate == nil && noPtr[i].Gate != "" { return nil, fmt.Errorf("gate \"%s\" not found", noPtr[i].Gate) } resCircuit[i].Inputs = algo_utils.Map(noPtr[i].Inputs, algo_utils.SlicePtrAt(resCircuit)) diff --git a/constraint/bw6-761/gkr.go b/constraint/bw6-761/gkr.go index 0066d302c7..f40856cc36 100644 --- a/constraint/bw6-761/gkr.go +++ b/constraint/bw6-761/gkr.go @@ -29,9 +29,8 @@ type GkrSolvingData struct { func convertCircuit(noPtr constraint.GkrCircuit) (gkr.Circuit, error) { resCircuit := make(gkr.Circuit, len(noPtr)) - var found bool for i := range noPtr { - if resCircuit[i].Gate, found = gkr.Gates[noPtr[i].Gate]; !found && noPtr[i].Gate != "" { + if resCircuit[i].Gate = gkr.GetGate(gkr.GateName(noPtr[i].Gate)); resCircuit[i].Gate == nil && noPtr[i].Gate != "" { return nil, fmt.Errorf("gate \"%s\" not found", noPtr[i].Gate) } resCircuit[i].Inputs = algo_utils.Map(noPtr[i].Inputs, algo_utils.SlicePtrAt(resCircuit)) diff --git a/frontend/api.go b/frontend/api.go index 40a6fdfaba..f548da4929 100644 --- a/frontend/api.go +++ b/frontend/api.go @@ -124,7 +124,7 @@ type API interface { // [github.com/consensys/gnark/std/math/bits]. AssertIsLessOrEqual(v Variable, bound Variable) - // Println behaves like fmt.Println but accepts cd.Variable as parameter + // Println behaves like fmt.Println but accepts frontend.Variable as parameter // whose value will be resolved at runtime when computed by the solver Println(a ...Variable) diff --git a/go.mod b/go.mod index 8296d75b3d..e3aa584208 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ toolchain go1.22.6 require ( github.com/bits-and-blooms/bitset v1.20.0 github.com/blang/semver/v4 v4.0.0 - github.com/consensys/bavard v0.1.29 + github.com/consensys/bavard v0.1.31-0.20250314194434-b30d4344e6d4 github.com/consensys/compress v0.2.5 - github.com/consensys/gnark-crypto v0.16.1-0.20250217214835-5ed804970f85 + github.com/consensys/gnark-crypto v0.17.1-0.20250326164229-5fd6610ac2a1 github.com/fxamacker/cbor/v2 v2.7.0 github.com/google/go-cmp v0.6.0 github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 diff --git a/go.sum b/go.sum index c31ddf141c..7e436d0c47 100644 --- a/go.sum +++ b/go.sum @@ -57,12 +57,12 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/consensys/bavard v0.1.29 h1:fobxIYksIQ+ZSrTJUuQgu+HIJwclrAPcdXqd7H2hh1k= -github.com/consensys/bavard v0.1.29/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= +github.com/consensys/bavard v0.1.31-0.20250314194434-b30d4344e6d4 h1:0J+ppRic2ZXsQE+Y+Lr9miam+RQVcWqwqe3SeiggR6s= +github.com/consensys/bavard v0.1.31-0.20250314194434-b30d4344e6d4/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= github.com/consensys/compress v0.2.5 h1:gJr1hKzbOD36JFsF1AN8lfXz1yevnJi1YolffY19Ntk= github.com/consensys/compress v0.2.5/go.mod h1:pyM+ZXiNUh7/0+AUjUf9RKUM6vSH7T/fsn5LLS0j1Tk= -github.com/consensys/gnark-crypto v0.16.1-0.20250217214835-5ed804970f85 h1:3ht4gGH3smFGVLFhpFTKvDbEdagC6eSaPXnHjCQGh94= -github.com/consensys/gnark-crypto v0.16.1-0.20250217214835-5ed804970f85/go.mod h1:A2URlMHUT81ifJ0UlLzSlm7TmnE3t7VxEThApdMukJw= +github.com/consensys/gnark-crypto v0.17.1-0.20250326164229-5fd6610ac2a1 h1:6cK71BoMAjWHNl+EpvBh2PDDa0PIeoz1KFJ/6R16DjQ= +github.com/consensys/gnark-crypto v0.17.1-0.20250326164229-5fd6610ac2a1/go.mod h1:uV1HwfBwGRj50DGK3LbDLeCvq0RX/vFXST3CRSAu0Fs= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= diff --git a/internal/generator/backend/template/representations/gkr.go.tmpl b/internal/generator/backend/template/representations/gkr.go.tmpl index 9030cc43a7..89b4b8dbc5 100644 --- a/internal/generator/backend/template/representations/gkr.go.tmpl +++ b/internal/generator/backend/template/representations/gkr.go.tmpl @@ -22,9 +22,8 @@ type GkrSolvingData struct { func convertCircuit(noPtr constraint.GkrCircuit) (gkr.Circuit, error) { resCircuit := make(gkr.Circuit, len(noPtr)) - var found bool for i := range noPtr { - if resCircuit[i].Gate, found = gkr.Gates[noPtr[i].Gate]; !found && noPtr[i].Gate != "" { + if resCircuit[i].Gate = gkr.GetGate(gkr.GateName(noPtr[i].Gate)); resCircuit[i].Gate == nil && noPtr[i].Gate != "" { return nil, fmt.Errorf("gate \"%s\" not found", noPtr[i].Gate) } resCircuit[i].Inputs = algo_utils.Map(noPtr[i].Inputs, algo_utils.SlicePtrAt(resCircuit)) diff --git a/internal/tinyfield/element.go b/internal/tinyfield/element.go index dcaa56b23f..cab95f0cbc 100644 --- a/internal/tinyfield/element.go +++ b/internal/tinyfield/element.go @@ -302,6 +302,16 @@ func (z *Element) SetRandom() (*Element, error) { } } +// MustSetRandom sets z to a uniform random value in [0, q). +// +// It panics if reading from crypto/rand.Reader errors. +func (z *Element) MustSetRandom() *Element { + if _, err := z.SetRandom(); err != nil { + panic(err) + } + return z +} + // smallerThanModulus returns true if z < q // This is not constant time func (z *Element) smallerThanModulus() bool { diff --git a/internal/tinyfield/element_test.go b/internal/tinyfield/element_test.go index 7383f4dac3..7d2286ca78 100644 --- a/internal/tinyfield/element_test.go +++ b/internal/tinyfield/element_test.go @@ -30,8 +30,8 @@ var benchResElement Element func BenchmarkElementSelect(b *testing.B) { var x, y Element - x.SetRandom() - y.SetRandom() + x.MustSetRandom() + y.MustSetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { @@ -41,17 +41,17 @@ func BenchmarkElementSelect(b *testing.B) { func BenchmarkElementSetRandom(b *testing.B) { var x Element - x.SetRandom() + x.MustSetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { - _, _ = x.SetRandom() + x.MustSetRandom() } } func BenchmarkElementSetBytes(b *testing.B) { var x Element - x.SetRandom() + x.MustSetRandom() bb := x.Bytes() b.ResetTimer() @@ -63,21 +63,21 @@ func BenchmarkElementSetBytes(b *testing.B) { func BenchmarkElementMulByConstants(b *testing.B) { b.Run("mulBy3", func(b *testing.B) { - benchResElement.SetRandom() + benchResElement.MustSetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { MulBy3(&benchResElement) } }) b.Run("mulBy5", func(b *testing.B) { - benchResElement.SetRandom() + benchResElement.MustSetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { MulBy5(&benchResElement) } }) b.Run("mulBy13", func(b *testing.B) { - benchResElement.SetRandom() + benchResElement.MustSetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { MulBy13(&benchResElement) @@ -87,8 +87,8 @@ func BenchmarkElementMulByConstants(b *testing.B) { func BenchmarkElementInverse(b *testing.B) { var x Element - x.SetRandom() - benchResElement.SetRandom() + x.MustSetRandom() + benchResElement.MustSetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { @@ -99,8 +99,8 @@ func BenchmarkElementInverse(b *testing.B) { func BenchmarkElementButterfly(b *testing.B) { var x Element - x.SetRandom() - benchResElement.SetRandom() + x.MustSetRandom() + benchResElement.MustSetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { Butterfly(&x, &benchResElement) @@ -109,8 +109,8 @@ func BenchmarkElementButterfly(b *testing.B) { func BenchmarkElementExp(b *testing.B) { var x Element - x.SetRandom() - benchResElement.SetRandom() + x.MustSetRandom() + benchResElement.MustSetRandom() b1, _ := rand.Int(rand.Reader, Modulus()) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -119,7 +119,7 @@ func BenchmarkElementExp(b *testing.B) { } func BenchmarkElementDouble(b *testing.B) { - benchResElement.SetRandom() + benchResElement.MustSetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.Double(&benchResElement) @@ -128,8 +128,8 @@ func BenchmarkElementDouble(b *testing.B) { func BenchmarkElementAdd(b *testing.B) { var x Element - x.SetRandom() - benchResElement.SetRandom() + x.MustSetRandom() + benchResElement.MustSetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.Add(&x, &benchResElement) @@ -138,8 +138,8 @@ func BenchmarkElementAdd(b *testing.B) { func BenchmarkElementSub(b *testing.B) { var x Element - x.SetRandom() - benchResElement.SetRandom() + x.MustSetRandom() + benchResElement.MustSetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.Sub(&x, &benchResElement) @@ -147,7 +147,7 @@ func BenchmarkElementSub(b *testing.B) { } func BenchmarkElementNeg(b *testing.B) { - benchResElement.SetRandom() + benchResElement.MustSetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.Neg(&benchResElement) @@ -156,8 +156,8 @@ func BenchmarkElementNeg(b *testing.B) { func BenchmarkElementDiv(b *testing.B) { var x Element - x.SetRandom() - benchResElement.SetRandom() + x.MustSetRandom() + benchResElement.MustSetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.Div(&x, &benchResElement) @@ -165,7 +165,7 @@ func BenchmarkElementDiv(b *testing.B) { } func BenchmarkElementFromMont(b *testing.B) { - benchResElement.SetRandom() + benchResElement.MustSetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.fromMont() @@ -173,7 +173,7 @@ func BenchmarkElementFromMont(b *testing.B) { } func BenchmarkElementSquare(b *testing.B) { - benchResElement.SetRandom() + benchResElement.MustSetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { benchResElement.Square(&benchResElement) @@ -248,7 +248,7 @@ func TestElementNegZero(t *testing.T) { var a, b Element b.SetZero() for a.IsZero() { - a.SetRandom() + a.MustSetRandom() } a.Neg(&b) if !a.IsZero() { diff --git a/internal/tinyfield/vector.go b/internal/tinyfield/vector.go index 0755dabf7e..dbdc94fb7f 100644 --- a/internal/tinyfield/vector.go +++ b/internal/tinyfield/vector.go @@ -185,6 +185,30 @@ func (vector Vector) Swap(i, j int) { vector[i], vector[j] = vector[j], vector[i] } +// SetRandom sets the elements in vector to independent uniform random values in [0, q). +// +// This might error only if reading from crypto/rand.Reader errors, +// in which case the values in vector are undefined. +func (vector Vector) SetRandom() error { + for i := range vector { + if _, err := vector[i].SetRandom(); err != nil { + return err + } + } + return nil +} + +// MustSetRandom sets the elements in vector to independent uniform random values in [0, q). +// +// It panics if reading from crypto/rand.Reader errors. +func (vector Vector) MustSetRandom() { + for i := range vector { + if _, err := vector[i].SetRandom(); err != nil { + panic(err) + } + } +} + func addVecGeneric(res, a, b Vector) { if len(a) != len(b) || len(a) != len(res) { panic("vector.Add: vectors don't have the same length") diff --git a/internal/tinyfield/vector_test.go b/internal/tinyfield/vector_test.go index 36ab4f9fb6..c923c0df53 100644 --- a/internal/tinyfield/vector_test.go +++ b/internal/tinyfield/vector_test.go @@ -234,7 +234,7 @@ func BenchmarkVectorOps(b *testing.B) { b1 := make(Vector, N) c1 := make(Vector, N) var mixer Element - mixer.SetRandom() + mixer.MustSetRandom() for i := 1; i < N; i++ { a1[i-1].SetUint64(uint64(i)). Mul(&a1[i-1], &mixer) diff --git a/std/gkr/api.go b/std/gkr/api.go index eb1acd2afe..0dbba85258 100644 --- a/std/gkr/api.go +++ b/std/gkr/api.go @@ -9,16 +9,16 @@ func frontendVarToInt(a constraint.GkrVariable) int { return int(a) } -func (api *API) NamedGate(gate string, in ...constraint.GkrVariable) constraint.GkrVariable { +func (api *API) NamedGate(gate GateName, in ...constraint.GkrVariable) constraint.GkrVariable { api.toStore.Circuit = append(api.toStore.Circuit, constraint.GkrWire{ - Gate: gate, + Gate: string(gate), Inputs: utils.Map(in, frontendVarToInt), }) api.assignments = append(api.assignments, nil) return constraint.GkrVariable(len(api.toStore.Circuit) - 1) } -func (api *API) namedGate2PlusIn(gate string, in1, in2 constraint.GkrVariable, in ...constraint.GkrVariable) constraint.GkrVariable { +func (api *API) namedGate2PlusIn(gate GateName, in1, in2 constraint.GkrVariable, in ...constraint.GkrVariable) constraint.GkrVariable { inCombined := make([]constraint.GkrVariable, 2+len(in)) inCombined[0] = in1 inCombined[1] = in2 @@ -28,18 +28,18 @@ func (api *API) namedGate2PlusIn(gate string, in1, in2 constraint.GkrVariable, i return api.NamedGate(gate, inCombined...) } -func (api *API) Add(i1, i2 constraint.GkrVariable, in ...constraint.GkrVariable) constraint.GkrVariable { - return api.namedGate2PlusIn("add", i1, i2, in...) +func (api *API) Add(i1, i2 constraint.GkrVariable) constraint.GkrVariable { + return api.namedGate2PlusIn(Add2, i1, i2) } func (api *API) Neg(i1 constraint.GkrVariable) constraint.GkrVariable { return api.NamedGate("neg", i1) } -func (api *API) Sub(i1, i2 constraint.GkrVariable, in ...constraint.GkrVariable) constraint.GkrVariable { - return api.namedGate2PlusIn("sub", i1, i2, in...) +func (api *API) Sub(i1, i2 constraint.GkrVariable) constraint.GkrVariable { + return api.namedGate2PlusIn(Sub2, i1, i2) } -func (api *API) Mul(i1, i2 constraint.GkrVariable, in ...constraint.GkrVariable) constraint.GkrVariable { - return api.namedGate2PlusIn("mul", i1, i2, in...) +func (api *API) Mul(i1, i2 constraint.GkrVariable) constraint.GkrVariable { + return api.namedGate2PlusIn(Mul2, i1, i2) } diff --git a/std/gkr/api_test.go b/std/gkr/api_test.go index 10817fed7b..b8c4d9951c 100644 --- a/std/gkr/api_test.go +++ b/std/gkr/api_test.go @@ -433,8 +433,34 @@ func init() { } func registerMiMCGate() { - Gates["mimc"] = MiMCCipherGate{Ark: 0} - gkr.Gates["mimc"] = mimcCipherGate{} + // register mimc gate + panicIfError(RegisterGate("mimc", func(api GateAPI, input ...frontend.Variable) frontend.Variable { + mimcSnarkTotalCalls++ + + if len(input) != 2 { + panic("mimc has fan-in 2") + } + sum := api.Add(input[0], input[1] /*, m.Ark*/) + + sumCubed := api.Mul(sum, sum, sum) // sum^3 + return api.Mul(sumCubed, sumCubed, sum) + }, 2, WithDegree(7))) + + // register fr version of mimc gate + panicIfError(gkr.RegisterGate("mimc", func(input ...fr.Element) (res fr.Element) { + var sum fr.Element + + sum. + Add(&input[0], &input[1]) //.Add(&sum, &m.ark) + + res.Square(&sum) // sum^2 + res.Mul(&res, &sum) // sum^3 + res.Square(&res) //sum^6 + res.Mul(&res, &sum) //sum^7 + + mimcFrTotalCalls++ + return res + }, 2, gkr.WithDegree(7))) } type constPseudoHash int @@ -449,31 +475,6 @@ func (c constPseudoHash) Reset() {} var mimcFrTotalCalls = 0 -// Copied from gnark-crypto TODO: Make public? -type mimcCipherGate struct { - ark fr.Element -} - -func (m mimcCipherGate) Evaluate(input ...fr.Element) (res fr.Element) { - var sum fr.Element - - sum. - Add(&input[0], &input[1]). - Add(&sum, &m.ark) - - res.Square(&sum) // sum^2 - res.Mul(&res, &sum) // sum^3 - res.Square(&res) //sum^6 - res.Mul(&res, &sum) //sum^7 - - mimcFrTotalCalls++ - return -} - -func (m mimcCipherGate) Degree() int { - return 7 -} - type mimcNoGkrCircuit struct { X []frontend.Variable Y []frontend.Variable @@ -597,7 +598,6 @@ func BenchmarkMiMCNoGkrFullDepthSolve(b *testing.B) { func TestMiMCFullDepthNoDepSolve(t *testing.T) { assert := test.NewAssert(t) - registerMiMC() for i := 0; i < 100; i++ { circuit, assignment := mimcNoDepCircuits(5, 1<<2, "-20") assert.Run(func(assert *test.Assert) { @@ -608,7 +608,6 @@ func TestMiMCFullDepthNoDepSolve(t *testing.T) { func TestMiMCFullDepthNoDepSolveWithMiMCHash(t *testing.T) { assert := test.NewAssert(t) - registerMiMC() circuit, assignment := mimcNoDepCircuits(5, 1<<2, "mimc") assert.CheckCircuit(circuit, test.WithValidAssignment(assignment), test.WithCurves(ecc.BN254)) } diff --git a/std/gkr/bn254_wrapper_api.go b/std/gkr/bn254_wrapper_api.go new file mode 100644 index 0000000000..0538b1d3e2 --- /dev/null +++ b/std/gkr/bn254_wrapper_api.go @@ -0,0 +1,111 @@ +package gkr + +import ( + "errors" + "fmt" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/consensys/gnark-crypto/ecc/bn254/fr/gkr" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/internal/utils" +) + +// wrap BN254 scalar field arithmetic in a frontend.API +// bn254WrapperApi uses *fr.Element as its variable type +type bn254WrapperApi struct { + err error +} + +func ToBn254GateFunction(f func(GateAPI, ...frontend.Variable) frontend.Variable) gkr.GateFunction { + var wrapper bn254WrapperApi + + return func(x ...fr.Element) fr.Element { + if wrapper.err != nil { + return fr.Element{} + } + res := f(&wrapper, utils.Map(x, func(x fr.Element) frontend.Variable { + return &x + })...).(*fr.Element) + + return *res + } +} + +func (w *bn254WrapperApi) Add(i1, i2 frontend.Variable, in ...frontend.Variable) frontend.Variable { + var res fr.Element + res.Add(w.cast(i1), w.cast(i2)) + for i := range in { + res.Add(&res, w.cast(in[i])) + } + + return &res +} + +func (w *bn254WrapperApi) MulAcc(a, b, c frontend.Variable) frontend.Variable { + var res fr.Element + res.Mul(w.cast(b), w.cast(c)) + res.Add(&res, w.cast(a)) + return &res +} + +func (w *bn254WrapperApi) Neg(i1 frontend.Variable) frontend.Variable { + var res fr.Element + res.Neg(w.cast(i1)) + return &res +} + +func (w *bn254WrapperApi) Sub(i1, i2 frontend.Variable, in ...frontend.Variable) frontend.Variable { + var res fr.Element + res.Sub(w.cast(i1), w.cast(i2)) + for i := range in { + res.Sub(&res, w.cast(in[i])) + } + return &res +} + +func (w *bn254WrapperApi) Mul(i1, i2 frontend.Variable, in ...frontend.Variable) frontend.Variable { + var res fr.Element + res.Mul(w.cast(i1), w.cast(i2)) + for i := range in { + res.Mul(&res, w.cast(in[i])) + } + return &res +} + +func (w *bn254WrapperApi) Println(a ...frontend.Variable) { + toPrint := make([]any, len(a)) + for i, v := range a { + var x fr.Element + if _, err := x.SetInterface(v); err != nil { + if s, ok := v.(string); ok { + toPrint[i] = s + continue + } else { + w.newError("not numeric or string") + } + } else { + toPrint[i] = x.String() + } + } + fmt.Println(toPrint...) +} + +func (w *bn254WrapperApi) cast(v frontend.Variable) *fr.Element { + var res fr.Element + if w.err != nil { + return &res + } + if _, err := res.SetInterface(v); err != nil { + w.emitError(err) + } + return &res +} + +func (w *bn254WrapperApi) emitError(err error) { + if w.err == nil { + w.err = err + } +} + +func (w *bn254WrapperApi) newError(msg string) { + w.emitError(errors.New(msg)) +} diff --git a/std/gkr/compile.go b/std/gkr/compile.go index 4623bb0db0..3d683fc9e3 100644 --- a/std/gkr/compile.go +++ b/std/gkr/compile.go @@ -223,7 +223,7 @@ func newCircuitDataForSnark(info constraint.GkrInfo, assignment assignment) circ for i := range circuit { w := info.Circuit[i] circuit[i] = Wire{ - Gate: ite(w.IsInput(), Gates[w.Gate], Gate(IdentityGate{})), + Gate: GetGate(ite(w.IsInput(), GateName(w.Gate), Identity)), Inputs: utils.Map(w.Inputs, circuitAt), nbUniqueOutputs: w.NbUniqueOutputs, } diff --git a/std/gkr/gkr.go b/std/gkr/gkr.go index 0da52730a9..5a47a2d9c2 100644 --- a/std/gkr/gkr.go +++ b/std/gkr/gkr.go @@ -3,26 +3,78 @@ package gkr import ( "errors" "fmt" - "strconv" - "github.com/consensys/gnark/frontend" fiatshamir "github.com/consensys/gnark/std/fiat-shamir" "github.com/consensys/gnark/std/polynomial" "github.com/consensys/gnark/std/sumcheck" + "strconv" ) // @tabaie TODO: Contains many things copy-pasted from gnark-crypto. Generify somehow? // The goal is to prove/verify evaluations of many instances of the same circuit -// Gate must be a low-degree polynomial -type Gate interface { - Evaluate(frontend.API, ...frontend.Variable) frontend.Variable - Degree() int +// GateAPI is a limited version of frontend.API, +// allowing ring arithmetic operations +type GateAPI interface { + // --------------------------------------------------------------------------------------------- + // Arithmetic + + // Add returns res = i1+i2+...in + Add(i1, i2 frontend.Variable, in ...frontend.Variable) frontend.Variable + + // MulAcc sets and return a = a + (b*c). + // + // ! The method may mutate a without allocating a new result. If the input + // is used elsewhere, then first initialize new variable, for example by + // doing: + // + // acopy := api.Mul(a, 1) + // acopy = api.MulAcc(acopy, b, c) + // + // ! But it may not modify a, always use MulAcc(...) result for correctness. + MulAcc(a, b, c frontend.Variable) frontend.Variable + + // Neg returns -i + Neg(i1 frontend.Variable) frontend.Variable + + // Sub returns res = i1 - i2 - ...in + Sub(i1, i2 frontend.Variable, in ...frontend.Variable) frontend.Variable + + // Mul returns res = i1 * i2 * ... in + Mul(i1, i2 frontend.Variable, in ...frontend.Variable) frontend.Variable + + // Println behaves like fmt.Println but accepts frontend.Variable as parameter + // whose value will be resolved at runtime when computed by the solver + Println(a ...frontend.Variable) +} +type GateFunction func(GateAPI, ...frontend.Variable) frontend.Variable + +// A Gate is a low-degree multivariate polynomial +type Gate struct { + Evaluate GateFunction // Evaluate the polynomial function defining the gate + nbIn int // number of inputs + degree int // total degree of f + solvableVar int // if there is a variable whose value can be uniquely determined from the value of the gate and the other inputs, its index, -1 otherwise +} + +// Degree returns the total degree of the gate's polynomial i.e. Degree(xy²) = 3 +func (g *Gate) Degree() int { + return g.degree +} + +// SolvableVar returns the index of a variable of degree 1 in the gate's polynomial. If there is no such variable, it returns -1. +func (g *Gate) SolvableVar() int { + return g.solvableVar +} + +// NbIn returns the number of inputs to the gate (its fan-in) +func (g *Gate) NbIn() int { + return g.nbIn } type Wire struct { - Gate Gate + Gate *Gate Inputs []*Wire // if there are no Inputs, the wire is assumed an input wire nbUniqueOutputs int // number of other wires using it as input, not counting duplicates (i.e. providing two inputs to the same gate counts as one) } @@ -224,13 +276,6 @@ func ProofSize(c Circuit, logNbInstances int) int { return nbUniqueInputs + nbPartialEvalPolys*logNbInstances } -func max(a, b int) int { - if a > b { - return a - } - return b -} - func ChallengeNames(sorted []*Wire, logNbInstances int, prefix string) []string { // Pre-compute the size TODO: Consider not doing this and just grow the list by appending @@ -349,16 +394,6 @@ func Verify(api frontend.API, c Circuit, assignment WireAssignment, proof Proof, return nil } -type IdentityGate struct{} - -func (IdentityGate) Evaluate(_ frontend.API, input ...frontend.Variable) frontend.Variable { - return input[0] -} - -func (IdentityGate) Degree() int { - return 1 -} - // outputsList also sets the nbUniqueOutputs fields. It also sets the wire metadata. func outputsList(c Circuit, indexes map[*Wire]int) [][]int { res := make([][]int, len(c)) @@ -366,7 +401,7 @@ func outputsList(c Circuit, indexes map[*Wire]int) [][]int { res[i] = make([]int, 0) c[i].nbUniqueOutputs = 0 if c[i].IsInput() { - c[i].Gate = IdentityGate{} + c[i].Gate = GetGate(Identity) } } ins := make(map[int]struct{}, len(c)) @@ -533,39 +568,8 @@ func DeserializeProof(sorted []*Wire, serializedProof []frontend.Variable) (Proo return proof, nil } -type MulGate struct{} - -func (g MulGate) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { - if len(x) != 2 { - panic("mul has fan-in 2") - } - return api.Mul(x[0], x[1]) -} - -// TODO: Degree must take nbInputs as an argument and return degree = nbInputs -func (g MulGate) Degree() int { - return 2 -} - -type AddGate struct{} - -func (a AddGate) Evaluate(api frontend.API, v ...frontend.Variable) frontend.Variable { - switch len(v) { - case 0: - return 0 - case 1: - return v[0] +func panicIfError(err error) { + if err != nil { + panic(err) } - rest := v[2:] - return api.Add(v[0], v[1], rest...) -} - -func (a AddGate) Degree() int { - return 1 -} - -var Gates = map[string]Gate{ - "identity": IdentityGate{}, - "add": AddGate{}, - "mul": MulGate{}, } diff --git a/std/gkr/gkr_test.go b/std/gkr/gkr_test.go index d24b25a95c..50e982ea92 100644 --- a/std/gkr/gkr_test.go +++ b/std/gkr/gkr_test.go @@ -249,8 +249,7 @@ func (c CircuitInfo) toCircuit() (circuit Circuit, err error) { circuit[i].Inputs[iAsInput] = input } - var found bool - if circuit[i].Gate, found = Gates[wireInfo.Gate]; !found && wireInfo.Gate != "" { + if circuit[i].Gate = GetGate(GateName(wireInfo.Gate)); circuit[i].Gate == nil && wireInfo.Gate != "" { err = fmt.Errorf("undefined gate \"%s\"", wireInfo.Gate) } } @@ -258,18 +257,10 @@ func (c CircuitInfo) toCircuit() (circuit Circuit, err error) { return } -type _select int - func init() { - Gates["select-input-3"] = _select(2) -} - -func (g _select) Evaluate(_ frontend.API, in ...frontend.Variable) frontend.Variable { - return in[g] -} - -func (g _select) Degree() int { - return 1 + panicIfError(RegisterGate("select-input-3", func(api GateAPI, in ...frontend.Variable) frontend.Variable { + return in[2] + }, 3, WithDegree(1))) } type PrintableProof []PrintableSumcheckProof diff --git a/std/gkr/registry.go b/std/gkr/registry.go new file mode 100644 index 0000000000..55dfd82f90 --- /dev/null +++ b/std/gkr/registry.go @@ -0,0 +1,147 @@ +package gkr + +import ( + "fmt" + "github.com/consensys/gnark/frontend" + "sync" +) + +type GateName string + +var ( + gates = make(map[GateName]*Gate) + gatesLock sync.Mutex +) + +type registerGateSettings struct { + solvableVar int + noSolvableVarVerification bool + noDegreeVerification bool + degree int +} + +// TODO @Tabaie once GKR is moved to gnark, use the same options/settings type for all curves, obviating this + +type RegisterGateOption func(*registerGateSettings) + +// WithSolvableVar gives the index of a variable whose value can be uniquely determined from that of the other variables along with the gate's output. +// RegisterGate will return an error if it cannot verify that this claim is correct. +func WithSolvableVar(solvableVar int) RegisterGateOption { + return func(settings *registerGateSettings) { + settings.solvableVar = solvableVar + } +} + +// WithUnverifiedSolvableVar sets the index of a variable whose value can be uniquely determined from that of the other variables along with the gate's output. +// RegisterGate will not verify that the given index is correct. +func WithUnverifiedSolvableVar(solvableVar int) RegisterGateOption { + return func(settings *registerGateSettings) { + settings.noSolvableVarVerification = true + settings.solvableVar = solvableVar + } +} + +// WithNoSolvableVar sets the gate as having no variable whose value can be uniquely determined from that of the other variables along with the gate's output. +// RegisterGate will not check the correctness of this claim. +func WithNoSolvableVar() RegisterGateOption { + return func(settings *registerGateSettings) { + settings.solvableVar = -1 + settings.noSolvableVarVerification = true + } +} + +// WithUnverifiedDegree sets the degree of the gate. RegisterGate will not verify that the given degree is correct. +func WithUnverifiedDegree(degree int) RegisterGateOption { + return func(settings *registerGateSettings) { + settings.noDegreeVerification = true + settings.degree = degree + } +} + +// WithDegree sets the degree of the gate. RegisterGate will return an error if the degree is not correct. +func WithDegree(degree int) RegisterGateOption { + return func(settings *registerGateSettings) { + settings.degree = degree + } +} + +// RegisterGate creates a gate object and stores it in the gates registry +// name is a human-readable name for the gate +// f is the polynomial function defining the gate +// nbIn is the number of inputs to the gate +// NB! This package generally expects certain properties of the gate to be invariant across all curves. +// In particular the degree is computed and verified over BN254. If the leading coefficient is divided by +// the curve's order, the degree will be computed incorrectly. +func RegisterGate(name GateName, f GateFunction, nbIn int, options ...RegisterGateOption) error { + s := registerGateSettings{degree: -1, solvableVar: -1} + for _, option := range options { + option(&s) + } + + frF := ToBn254GateFunction(f) + + if s.degree == -1 { // find a degree + if s.noDegreeVerification { + panic("invalid settings") + } + const maxAutoDegreeBound = 32 + var err error + if s.degree, err = frF.FindDegree(maxAutoDegreeBound, nbIn); err != nil { + return fmt.Errorf("for gate %s: %v", name, err) + } + } else { + if !s.noDegreeVerification { // check that the given degree is correct + if err := frF.VerifyDegree(s.degree, nbIn); err != nil { + return fmt.Errorf("for gate %s: %v", name, err) + } + } + } + + if s.solvableVar == -1 { + if !s.noSolvableVarVerification { // find a solvable variable + s.solvableVar = frF.FindSolvableVar(nbIn) + } + } else { + // solvable variable given + if !s.noSolvableVarVerification && !frF.IsVarSolvable(s.solvableVar, nbIn) { + return fmt.Errorf("cannot verify the solvability of variable %d in gate %s", s.solvableVar, name) + } + } + + gatesLock.Lock() + defer gatesLock.Unlock() + gates[name] = &Gate{Evaluate: f, nbIn: nbIn, degree: s.degree, solvableVar: s.solvableVar} + return nil +} + +func GetGate(name GateName) *Gate { + gatesLock.Lock() + defer gatesLock.Unlock() + return gates[name] +} + +const ( + Identity GateName = "identity" // Identity gate: x -> x + Add2 GateName = "add2" // Add2 gate: (x, y) -> x + y + Sub2 GateName = "sub2" // Sub2 gate: (x, y) -> x - y + Neg GateName = "neg" // Neg gate: x -> -x + Mul2 GateName = "mul2" // Mul2 gate: (x, y) -> x * y +) + +func init() { + panicIfError(RegisterGate(Mul2, func(api GateAPI, x ...frontend.Variable) frontend.Variable { + return api.Mul(x[0], x[1]) + }, 2, WithUnverifiedDegree(2), WithNoSolvableVar())) + panicIfError(RegisterGate(Add2, func(api GateAPI, x ...frontend.Variable) frontend.Variable { + return api.Add(x[0], x[1]) + }, 2, WithUnverifiedDegree(1), WithUnverifiedSolvableVar(0))) + panicIfError(RegisterGate(Identity, func(api GateAPI, x ...frontend.Variable) frontend.Variable { + return x[0] + }, 1, WithUnverifiedDegree(1), WithUnverifiedSolvableVar(0))) + panicIfError(RegisterGate(Neg, func(api GateAPI, x ...frontend.Variable) frontend.Variable { + return api.Neg(x[0]) + }, 1, WithUnverifiedDegree(1), WithUnverifiedSolvableVar(0))) + panicIfError(RegisterGate(Sub2, func(api GateAPI, x ...frontend.Variable) frontend.Variable { + return api.Sub(x[0], x[1]) + }, 2, WithUnverifiedDegree(1), WithUnverifiedSolvableVar(0))) +} diff --git a/std/gkr/test_vectors/resources/single_input_two_outs.json b/std/gkr/test_vectors/resources/single_input_two_outs.json index c577c1cace..3a39e5625f 100644 --- a/std/gkr/test_vectors/resources/single_input_two_outs.json +++ b/std/gkr/test_vectors/resources/single_input_two_outs.json @@ -4,7 +4,7 @@ "inputs": [] }, { - "gate": "mul", + "gate": "mul2", "inputs": [0, 0] }, { diff --git a/std/gkr/test_vectors/resources/single_mul_gate.json b/std/gkr/test_vectors/resources/single_mul_gate.json index 0f65a07edf..d009ebe03d 100644 --- a/std/gkr/test_vectors/resources/single_mul_gate.json +++ b/std/gkr/test_vectors/resources/single_mul_gate.json @@ -8,7 +8,7 @@ "inputs": [] }, { - "gate": "mul", + "gate": "mul2", "inputs": [0, 1] } ] \ No newline at end of file diff --git a/std/gkr/testing.go b/std/gkr/testing.go index 50111a60e4..1c43a65e40 100644 --- a/std/gkr/testing.go +++ b/std/gkr/testing.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math/big" + "sync" "github.com/consensys/gnark-crypto/ecc" frBls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" @@ -29,35 +30,13 @@ import ( // This method only works under the test engine and should only be called to debug a GKR circuit, as the GKR prover's errors can be obscure. func (api *API) SolveInTestEngine(parentApi frontend.API) [][]frontend.Variable { res := make([][]frontend.Variable, len(api.toStore.Circuit)) - degreeTestedGates := make(map[string]struct{}) + var degreeTestedGates sync.Map for i, w := range api.toStore.Circuit { res[i] = make([]frontend.Variable, api.nbInstances()) copy(res[i], api.assignments[i]) if len(w.Inputs) == 0 { continue } - degree := Gates[w.Gate].Degree() - var degreeFr int - if parentApi.Compiler().Field().Cmp(ecc.BLS12_377.ScalarField()) == 0 { - degreeFr = gkrBls12377.Gates[w.Gate].Degree() - } else if parentApi.Compiler().Field().Cmp(ecc.BN254.ScalarField()) == 0 { - degreeFr = gkrBn254.Gates[w.Gate].Degree() - } else if parentApi.Compiler().Field().Cmp(ecc.BLS24_315.ScalarField()) == 0 { - degreeFr = gkrBls24315.Gates[w.Gate].Degree() - } else if parentApi.Compiler().Field().Cmp(ecc.BW6_761.ScalarField()) == 0 { - degreeFr = gkrBw6761.Gates[w.Gate].Degree() - } else if parentApi.Compiler().Field().Cmp(ecc.BLS12_381.ScalarField()) == 0 { - degreeFr = gkrBls12381.Gates[w.Gate].Degree() - } else if parentApi.Compiler().Field().Cmp(ecc.BLS24_317.ScalarField()) == 0 { - degreeFr = gkrBls24317.Gates[w.Gate].Degree() - } else if parentApi.Compiler().Field().Cmp(ecc.BW6_633.ScalarField()) == 0 { - degreeFr = gkrBw6633.Gates[w.Gate].Degree() - } else { - panic("field not yet supported") - } - if degree != degreeFr { - panic(fmt.Errorf("gate \"%s\" degree mismatch: SNARK %d, Raw %d", w.Gate, degree, degreeFr)) - } } for instanceI := range api.nbInstances() { for wireI, w := range api.toStore.Circuit { @@ -84,11 +63,11 @@ func (api *API) SolveInTestEngine(parentApi frontend.API) [][]frontend.Variable for i, in := range w.Inputs { ins[i] = res[in][instanceI] } - expectedV, err := parentApi.Compiler().NewHint(frGateHint(w.Gate, degreeTestedGates), 1, ins...) + expectedV, err := parentApi.Compiler().NewHint(frGateHint(GateName(w.Gate), °reeTestedGates), 1, ins...) if err != nil { panic(err) } - res[wireI][instanceI] = Gates[w.Gate].Evaluate(parentApi, ins...) + res[wireI][instanceI] = GetGate(GateName(w.Gate)).Evaluate(parentApi, ins...) parentApi.AssertIsEqual(expectedV[0], res[wireI][instanceI]) // snark and raw gate evaluations must agree } } @@ -96,23 +75,27 @@ func (api *API) SolveInTestEngine(parentApi frontend.API) [][]frontend.Variable return res } -func frGateHint(gateName string, degreeTestedGates map[string]struct{}) hint.Hint { +func frGateHint(gateName GateName, degreeTestedGates *sync.Map) hint.Hint { return func(mod *big.Int, ins, outs []*big.Int) error { + const dummyGateName = "dummy-solve-in-test-engine-gate" + var degreeFr, nbInFr, solvableVarFr int if len(outs) != 1 { return errors.New("gate must have one output") } if ecc.BLS12_377.ScalarField().Cmp(mod) == 0 { - gate := gkrBls12377.Gates[gateName] + gate := gkrBls12377.GetGate(gkrBls12377.GateName(gateName)) if gate == nil { return fmt.Errorf("gate \"%s\" not found", gateName) } - if _, ok := degreeTestedGates[gateName]; !ok { - if err := gkrBls12377.TestGateDegree(gate, len(ins)); err != nil { - return fmt.Errorf("gate %s: %w", gateName, err) + degreeFr = gate.Degree() + nbInFr = gate.NbIn() + solvableVarFr = gate.SolvableVar() + if _, ok := degreeTestedGates.Load(gateName); !ok { + // re-register the gate to make sure the degree is correct + if err := gkrBls12377.RegisterGate(dummyGateName, gate.Evaluate, nbInFr, gkrBls12377.WithDegree(degreeFr)); err != nil { + return err } - degreeTestedGates[gateName] = struct{}{} } - x := make([]frBls12377.Element, len(ins)) for i := range ins { x[i].SetBigInt(ins[i]) @@ -120,17 +103,19 @@ func frGateHint(gateName string, degreeTestedGates map[string]struct{}) hint.Hin y := gate.Evaluate(x...) y.BigInt(outs[0]) } else if ecc.BN254.ScalarField().Cmp(mod) == 0 { - gate := gkrBn254.Gates[gateName] + gate := gkrBn254.GetGate(gkrBn254.GateName(gateName)) if gate == nil { return fmt.Errorf("gate \"%s\" not found", gateName) } - if _, ok := degreeTestedGates[gateName]; !ok { - if err := gkrBn254.TestGateDegree(gate, len(ins)); err != nil { - return fmt.Errorf("gate %s: %w", gateName, err) + degreeFr = gate.Degree() + nbInFr = gate.NbIn() + solvableVarFr = gate.SolvableVar() + if _, ok := degreeTestedGates.Load(gateName); !ok { + // re-register the gate to make sure the degree is correct + if err := gkrBn254.RegisterGate(dummyGateName, gate.Evaluate, nbInFr, gkrBn254.WithDegree(degreeFr)); err != nil { + return err } - degreeTestedGates[gateName] = struct{}{} } - x := make([]frBn254.Element, len(ins)) for i := range ins { x[i].SetBigInt(ins[i]) @@ -138,17 +123,19 @@ func frGateHint(gateName string, degreeTestedGates map[string]struct{}) hint.Hin y := gate.Evaluate(x...) y.BigInt(outs[0]) } else if ecc.BLS24_315.ScalarField().Cmp(mod) == 0 { - gate := gkrBls24315.Gates[gateName] + gate := gkrBls24315.GetGate(gkrBls24315.GateName(gateName)) if gate == nil { return fmt.Errorf("gate \"%s\" not found", gateName) } - if _, ok := degreeTestedGates[gateName]; !ok { - if err := gkrBls24315.TestGateDegree(gate, len(ins)); err != nil { - return fmt.Errorf("gate %s: %w", gateName, err) + degreeFr = gate.Degree() + nbInFr = gate.NbIn() + solvableVarFr = gate.SolvableVar() + if _, ok := degreeTestedGates.Load(gateName); !ok { + // re-register the gate to make sure the degree is correct + if err := gkrBls24315.RegisterGate(dummyGateName, gate.Evaluate, nbInFr, gkrBls24315.WithDegree(degreeFr)); err != nil { + return err } - degreeTestedGates[gateName] = struct{}{} } - x := make([]frBls24315.Element, len(ins)) for i := range ins { x[i].SetBigInt(ins[i]) @@ -156,17 +143,19 @@ func frGateHint(gateName string, degreeTestedGates map[string]struct{}) hint.Hin y := gate.Evaluate(x...) y.BigInt(outs[0]) } else if ecc.BW6_761.ScalarField().Cmp(mod) == 0 { - gate := gkrBw6761.Gates[gateName] + gate := gkrBw6761.GetGate(gkrBw6761.GateName(gateName)) if gate == nil { return fmt.Errorf("gate \"%s\" not found", gateName) } - if _, ok := degreeTestedGates[gateName]; !ok { - if err := gkrBw6761.TestGateDegree(gate, len(ins)); err != nil { - return fmt.Errorf("gate %s: %w", gateName, err) + degreeFr = gate.Degree() + nbInFr = gate.NbIn() + solvableVarFr = gate.SolvableVar() + if _, ok := degreeTestedGates.Load(gateName); !ok { + // re-register the gate to make sure the degree is correct + if err := gkrBw6761.RegisterGate(dummyGateName, gate.Evaluate, nbInFr, gkrBw6761.WithDegree(degreeFr)); err != nil { + return err } - degreeTestedGates[gateName] = struct{}{} } - x := make([]frBw6761.Element, len(ins)) for i := range ins { x[i].SetBigInt(ins[i]) @@ -174,17 +163,19 @@ func frGateHint(gateName string, degreeTestedGates map[string]struct{}) hint.Hin y := gate.Evaluate(x...) y.BigInt(outs[0]) } else if ecc.BLS12_381.ScalarField().Cmp(mod) == 0 { - gate := gkrBls12381.Gates[gateName] + gate := gkrBls12381.GetGate(gkrBls12381.GateName(gateName)) if gate == nil { return fmt.Errorf("gate \"%s\" not found", gateName) } - if _, ok := degreeTestedGates[gateName]; !ok { - if err := gkrBls12381.TestGateDegree(gate, len(ins)); err != nil { - return fmt.Errorf("gate %s: %w", gateName, err) + degreeFr = gate.Degree() + nbInFr = gate.NbIn() + solvableVarFr = gate.SolvableVar() + if _, ok := degreeTestedGates.Load(gateName); !ok { + // re-register the gate to make sure the degree is correct + if err := gkrBls12381.RegisterGate(dummyGateName, gate.Evaluate, nbInFr, gkrBls12381.WithDegree(degreeFr)); err != nil { + return err } - degreeTestedGates[gateName] = struct{}{} } - x := make([]frBls12381.Element, len(ins)) for i := range ins { x[i].SetBigInt(ins[i]) @@ -192,17 +183,19 @@ func frGateHint(gateName string, degreeTestedGates map[string]struct{}) hint.Hin y := gate.Evaluate(x...) y.BigInt(outs[0]) } else if ecc.BLS24_317.ScalarField().Cmp(mod) == 0 { - gate := gkrBls24317.Gates[gateName] + gate := gkrBls24317.GetGate(gkrBls24317.GateName(gateName)) if gate == nil { return fmt.Errorf("gate \"%s\" not found", gateName) } - if _, ok := degreeTestedGates[gateName]; !ok { - if err := gkrBls24317.TestGateDegree(gate, len(ins)); err != nil { - return fmt.Errorf("gate %s: %w", gateName, err) + degreeFr = gate.Degree() + nbInFr = gate.NbIn() + solvableVarFr = gate.SolvableVar() + if _, ok := degreeTestedGates.Load(gateName); !ok { + // re-register the gate to make sure the degree is correct + if err := gkrBls24317.RegisterGate(dummyGateName, gate.Evaluate, nbInFr, gkrBls24317.WithDegree(degreeFr)); err != nil { + return err } - degreeTestedGates[gateName] = struct{}{} } - x := make([]frBls24317.Element, len(ins)) for i := range ins { x[i].SetBigInt(ins[i]) @@ -210,15 +203,18 @@ func frGateHint(gateName string, degreeTestedGates map[string]struct{}) hint.Hin y := gate.Evaluate(x...) y.BigInt(outs[0]) } else if ecc.BW6_633.ScalarField().Cmp(mod) == 0 { - gate := gkrBw6633.Gates[gateName] + gate := gkrBw6633.GetGate(gkrBw6633.GateName(gateName)) if gate == nil { return fmt.Errorf("gate \"%s\" not found", gateName) } - if _, ok := degreeTestedGates[gateName]; !ok { - if err := gkrBw6633.TestGateDegree(gate, len(ins)); err != nil { - return fmt.Errorf("gate %s: %w", gateName, err) + degreeFr = gate.Degree() + nbInFr = gate.NbIn() + solvableVarFr = gate.SolvableVar() + if _, ok := degreeTestedGates.Load(gateName); !ok { + // re-register the gate to make sure the degree is correct + if err := gkrBw6633.RegisterGate(dummyGateName, gate.Evaluate, nbInFr, gkrBw6633.WithDegree(degreeFr)); err != nil { + return err } - degreeTestedGates[gateName] = struct{}{} } x := make([]frBw6633.Element, len(ins)) for i := range ins { @@ -229,6 +225,21 @@ func frGateHint(gateName string, degreeTestedGates map[string]struct{}) hint.Hin } else { return errors.New("field not supported") } + + degreeTestedGates.Store(gateName, struct{}{}) + + if degreeFr != GetGate(gateName).Degree() { + return fmt.Errorf("gate \"%s\" degree mismatch: SNARK %d, Raw %d", gateName, GetGate(gateName).Degree(), degreeFr) + } + + if nbInFr != len(ins) { // TODO @Tabaie also check against GetGate(gateName].NbIn() + return fmt.Errorf("gate \"%s\" input count mismatch: SNARK %d, Raw %d", gateName, len(ins), nbInFr) + } + + if solvableVarFr != GetGate(gateName).SolvableVar() { + return fmt.Errorf("gate \"%s\" designated solvable variable mismatch: SNARK %d, Raw %d", gateName, GetGate(gateName).SolvableVar(), solvableVarFr) + } + return nil } } diff --git a/std/hash/poseidon2/poseidon2.go b/std/hash/poseidon2/poseidon2.go index d0427d2ca0..dbe32a85d8 100644 --- a/std/hash/poseidon2/poseidon2.go +++ b/std/hash/poseidon2/poseidon2.go @@ -5,7 +5,7 @@ import ( "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/hash" - poseidon2 "github.com/consensys/gnark/std/permutation/poseidon2" + "github.com/consensys/gnark/std/permutation/poseidon2" ) // NewMerkleDamgardHasher returns a Poseidon2 hasher using the Merkle-Damgard diff --git a/std/permutation/poseidon2/gkr.go b/std/permutation/poseidon2/gkr.go index f52d750bf0..98c07b6f8e 100644 --- a/std/permutation/poseidon2/gkr.go +++ b/std/permutation/poseidon2/gkr.go @@ -23,25 +23,17 @@ import ( // extKeyGate applies the external matrix mul, then adds the round key // because of its symmetry, we don't need to define distinct x1 and x2 versions of it -type extKeyGate struct { - roundKey *big.Int -} - -func (g *extKeyGate) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { - if len(x) != 2 { - panic("expected 2 inputs") +func extKeyGate(roundKey *big.Int) gkr.GateFunction { + return func(api gkr.GateAPI, x ...frontend.Variable) frontend.Variable { + if len(x) != 2 { + panic("expected 2 inputs") + } + return api.Add(api.Mul(x[0], 2), x[1], roundKey) } - return api.Add(api.Mul(x[0], 2), x[1], g.roundKey) -} - -func (g *extKeyGate) Degree() int { - return 1 } // pow4Gate computes a -> a⁴ -type pow4Gate struct{} - -func (g pow4Gate) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { +func pow4Gate(api gkr.GateAPI, x ...frontend.Variable) frontend.Variable { if len(x) != 1 { panic("expected 1 input") } @@ -51,14 +43,8 @@ func (g pow4Gate) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Va return y } -func (g pow4Gate) Degree() int { - return 4 -} - -// pow4Gate computes a, b -> a⁴ * b -type pow4TimesGate struct{} - -func (g pow4TimesGate) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { +// pow4TimesGate computes a, b -> a⁴ * b +func pow4TimesGate(api gkr.GateAPI, x ...frontend.Variable) frontend.Variable { if len(x) != 2 { panic("expected 1 input") } @@ -68,103 +54,80 @@ func (g pow4TimesGate) Evaluate(api frontend.API, x ...frontend.Variable) fronte return api.Mul(y, x[1]) } -func (g pow4TimesGate) Degree() int { - return 5 -} - -type pow2Gate struct{} - -func (g pow2Gate) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { +// pow2Gate computes a -> a² +func pow2Gate(api gkr.GateAPI, x ...frontend.Variable) frontend.Variable { if len(x) != 1 { panic("expected 1 input") } return api.Mul(x[0], x[0]) } -func (g pow2Gate) Degree() int { - return 2 -} - -type pow2TimesGate struct{} - -func (g pow2TimesGate) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { +// pow2TimesGate computes a, b -> a² * b +func pow2TimesGate(api gkr.GateAPI, x ...frontend.Variable) frontend.Variable { if len(x) != 2 { panic("expected 2 inputs") } return api.Mul(x[0], x[0], x[1]) } -func (g pow2TimesGate) Degree() int { - return 3 -} - // for x1, the partial round gates are identical to full round gates // for x2, the partial round gates are just a linear combination // TODO @Tabaie try eliminating the x2 partial round gates and have the x1 gates depend on i - rf/2 or so previous x1's // extGate2 applies the external matrix mul, outputting the second element of the result -type extGate2 struct { -} - -func (g *extGate2) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { +func extGate2(api gkr.GateAPI, x ...frontend.Variable) frontend.Variable { if len(x) != 2 { panic("expected 2 inputs") } return api.Add(api.Mul(x[1], 2), x[0]) } -func (g *extGate2) Degree() int { - return 1 -} - // intKeyGate2 applies the internal matrix mul, then adds the round key -type intKeyGate2 struct { - roundKey *big.Int -} - -func (g *intKeyGate2) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { - if len(x) != 2 { - panic("expected 2 inputs") +func intKeyGate2(roundKey *big.Int) gkr.GateFunction { + return func(api gkr.GateAPI, x ...frontend.Variable) frontend.Variable { + if len(x) != 2 { + panic("expected 2 inputs") + } + return api.Add(api.Mul(x[1], 3), x[0], roundKey) } - return api.Add(api.Mul(x[1], 3), x[0], g.roundKey) } -func (g *intKeyGate2) Degree() int { - return 1 -} - -type extGate struct{} - -func (g extGate) Evaluate(api frontend.API, x ...frontend.Variable) frontend.Variable { +// extGate applies the first row of the external matrix +func extGate(api gkr.GateAPI, x ...frontend.Variable) frontend.Variable { if len(x) != 2 { panic("expected 2 inputs") } return api.Add(api.Mul(x[0], 2), x[1]) } -func (g extGate) Degree() int { - return 1 +// extAddGate applies the first row of the external matrix to the first two elements and adds the third +func extAddGate(api gkr.GateAPI, x ...frontend.Variable) frontend.Variable { + if len(x) != 3 { + panic("expected 3 inputs") + } + return api.Add(api.Mul(x[0], 2), x[1], x[2]) } -type GkrPermutations struct { +type GkrCompressions struct { api frontend.API ins1 []frontend.Variable ins2 []frontend.Variable outs []frontend.Variable } -// NewGkrPermutations returns an object that can compute the Poseidon2 permutation (currently only for BLS12-377) -// The correctness of the permutations is proven using GKR +// NewGkrCompressions returns an object that can compute the Poseidon2 compression function (currently only for BLS12-377) +// which consists of a permutation along with the input fed forward. +// The correctness of the compression functions is proven using GKR. // Note that the solver will need the function RegisterGkrSolverOptions to be called with the desired curves -func NewGkrPermutations(api frontend.API) *GkrPermutations { - res := GkrPermutations{ +func NewGkrCompressions(api frontend.API) *GkrCompressions { + res := GkrCompressions{ api: api, } api.Compiler().Defer(res.finalize) return &res } -func (p *GkrPermutations) Permute(a, b frontend.Variable) frontend.Variable { +func (p *GkrCompressions) Compress(a, b frontend.Variable) frontend.Variable { s, err := p.api.Compiler().NewHint(permuteHint, 1, a, b) if err != nil { panic(err) @@ -193,7 +156,7 @@ func defineCircuit(insLeft, insRight []frontend.Variable) (*gkr.API, constraint. // poseidon2 parameters roundKeysFr := poseidon2Bls12377.GetDefaultParameters().RoundKeys - params := poseidon2Bls12377.GetDefaultParameters().String() + gateNamer := gkrPoseidon2Bls12377.RoundGateNamer(poseidon2Bls12377.GetDefaultParameters()) rF := poseidon2Bls12377.GetDefaultParameters().NbFullRounds rP := poseidon2Bls12377.GetDefaultParameters().NbPartialRounds halfRf := rF / 2 @@ -205,18 +168,18 @@ func defineCircuit(insLeft, insRight []frontend.Variable) (*gkr.API, constraint. return nil, -1, err } y, err := gkrApi.Import(insRight) + y0 := y // save to feed forward at the end if err != nil { return nil, -1, err } - // unique names for linear rounds - gateNameLinear := func(varI, round int) string { - return fmt.Sprintf("x%d-l-op-round=%d;%s", varI, round, params) - } - // the s-Box gates: u¹⁷ = (u⁴)⁴ * u - gkr.Gates["pow4"] = pow4Gate{} - gkr.Gates["pow4Times"] = pow4TimesGate{} + if err = gkr.RegisterGate("pow4", pow4Gate, 1, gkr.WithUnverifiedDegree(4), gkr.WithNoSolvableVar()); err != nil { + return nil, -1, err + } + if err = gkr.RegisterGate("pow4Times", pow4TimesGate, 2, gkr.WithUnverifiedDegree(5), gkr.WithNoSolvableVar()); err != nil { + return nil, -1, err + } // *** helper functions to register and apply gates *** @@ -235,9 +198,9 @@ func defineCircuit(insLeft, insRight []frontend.Variable) (*gkr.API, constraint. // register and apply external matrix multiplication and round key addition // round dependent due to the round key extKeySBox := func(round, varI int, a, b constraint.GkrVariable) constraint.GkrVariable { - gate := gateNameLinear(varI, round) - gkr.Gates[gate] = &extKeyGate{ - roundKey: frToInt(&roundKeysFr[round][varI]), + gate := gkr.GateName(gateNamer.Linear(varI, round)) + if err = gkr.RegisterGate(gate, extKeyGate(frToInt(&roundKeysFr[round][varI])), 2, gkr.WithUnverifiedDegree(1), gkr.WithUnverifiedSolvableVar(0)); err != nil { + return -1 } return sBox(gkrApi.NamedGate(gate, a, b)) } @@ -247,9 +210,9 @@ func defineCircuit(insLeft, insRight []frontend.Variable) (*gkr.API, constraint. // for the second variable // round independent due to the round key intKeySBox2 := func(round int, a, b constraint.GkrVariable) constraint.GkrVariable { - gate := gateNameLinear(yI, round) - gkr.Gates[gate] = &intKeyGate2{ - roundKey: frToInt(&roundKeysFr[round][1]), + gate := gkr.GateName(gateNamer.Linear(yI, round)) + if err = gkr.RegisterGate(gate, intKeyGate2(frToInt(&roundKeysFr[round][1])), 2, gkr.WithUnverifiedDegree(1), gkr.WithUnverifiedSolvableVar(0)); err != nil { + return -1 } return sBox(gkrApi.NamedGate(gate, a, b)) } @@ -271,8 +234,10 @@ func defineCircuit(insLeft, insRight []frontend.Variable) (*gkr.API, constraint. // still using the external matrix, since the linear operation still belongs to a full (canonical) round x1 := extKeySBox(halfRf, xI, x, y) - gate := gateNameLinear(yI, halfRf) - gkr.Gates[gate] = &extGate2{} + gate := gkr.GateName(gateNamer.Linear(yI, halfRf)) + if err = gkr.RegisterGate(gate, extGate2, 2, gkr.WithUnverifiedDegree(1), gkr.WithUnverifiedSolvableVar(0)); err != nil { + return nil, -1, err + } x, y = x1, gkrApi.NamedGate(gate, x, y) } @@ -280,9 +245,9 @@ func defineCircuit(insLeft, insRight []frontend.Variable) (*gkr.API, constraint. for i := halfRf + 1; i < halfRf+rP; i++ { x1 := extKeySBox(i, xI, x, y) // the first row of the internal matrix is the same as that of the external matrix - gate := gateNameLinear(yI, i) - gkr.Gates[gate] = &intKeyGate2{ - roundKey: zero, + gate := gkr.GateName(gateNamer.Linear(yI, i)) + if err = gkr.RegisterGate(gate, intKeyGate2(zero), 2, gkr.WithUnverifiedDegree(1), gkr.WithUnverifiedSolvableVar(0)); err != nil { + return nil, -1, err } x, y = x1, gkrApi.NamedGate(gate, x, y) } @@ -300,14 +265,16 @@ func defineCircuit(insLeft, insRight []frontend.Variable) (*gkr.API, constraint. } // apply the external matrix one last time to obtain the final value of y - gate := gateNameLinear(yI, rP+rF) - gkr.Gates[gate] = extGate{} - y = gkrApi.NamedGate(gate, y, x) + gate := gkr.GateName(gateNamer.Linear(yI, rP+rF)) + if err = gkr.RegisterGate(gate, extAddGate, 3, gkr.WithUnverifiedDegree(1), gkr.WithUnverifiedSolvableVar(0)); err != nil { + return nil, -1, err + } + y = gkrApi.NamedGate(gate, y, x, y0) return gkrApi, y, nil } -func (p *GkrPermutations) finalize(api frontend.API) error { +func (p *GkrCompressions) finalize(api frontend.API) error { if p.api != api { panic("unexpected API") } @@ -367,8 +334,10 @@ func permuteHint(m *big.Int, ins, outs []*big.Int) error { var x [2]frBls12377.Element x[0].SetBigInt(ins[0]) x[1].SetBigInt(ins[1]) + y0 := x[1] err := bls12377Permutation().Permutation(x[:]) + x[1].Add(&x[1], &y0) // feed forward x[1].BigInt(outs[0]) return err } @@ -390,7 +359,9 @@ func RegisterGkrSolverOptions(curves ...ecc.ID) { csBls12377.RegisterHashBuilder("mimc", func() hash.Hash { return mimcBls12377.NewMiMC() }) - gkrPoseidon2Bls12377.RegisterGkrGates() + if err := gkrPoseidon2Bls12377.RegisterGkrGates(); err != nil { + panic(err) + } default: panic(fmt.Sprintf("curve %s not currently supported", curve)) } diff --git a/std/permutation/poseidon2/gkr_test.go b/std/permutation/poseidon2/gkr_test.go index 1cc39c4053..96a9c2494c 100644 --- a/std/permutation/poseidon2/gkr_test.go +++ b/std/permutation/poseidon2/gkr_test.go @@ -11,7 +11,7 @@ import ( "testing" ) -func TestGkrPermutation(t *testing.T) { +func TestGkrCompression(t *testing.T) { const n = 2 var k int64 ins := make([][2]frontend.Variable, n) @@ -22,8 +22,10 @@ func TestGkrPermutation(t *testing.T) { x[0].SetInt64(k) x[1].SetInt64(k + 1) + y0 := x[1] require.NoError(t, bls12377Permutation().Permutation(x[:])) + x[1].Add(&x[1], &y0) outs[i] = x[1] k += 2 @@ -46,10 +48,10 @@ type testGkrPermutationCircuit struct { func (c *testGkrPermutationCircuit) Define(api frontend.API) error { - pos2 := NewGkrPermutations(api) + pos2 := NewGkrCompressions(api) api.AssertIsEqual(len(c.Ins), len(c.Outs)) for i := range c.Ins { - api.AssertIsEqual(c.Outs[i], pos2.Permute(c.Ins[i][0], c.Ins[i][1])) + api.AssertIsEqual(c.Outs[i], pos2.Compress(c.Ins[i][0], c.Ins[i][1])) } return nil diff --git a/std/permutation/poseidon2/poseidon2.go b/std/permutation/poseidon2/poseidon2.go index f7a8d4068a..55afe73be5 100644 --- a/std/permutation/poseidon2/poseidon2.go +++ b/std/permutation/poseidon2/poseidon2.go @@ -328,5 +328,5 @@ func (h *Permutation) Compress(left, right frontend.Variable) frontend.Variable if err := h.Permutation(vars[:]); err != nil { panic(err) // this would never happen } - return vars[1] + return h.api.Add(vars[1], right) } From b1ba4257501488549e77c02b0ccda23ce8959b41 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 7 Apr 2025 12:51:30 +0000 Subject: [PATCH 044/105] chore: go.mod update --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index e3aa584208..af4a32e095 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ toolchain go1.22.6 require ( github.com/bits-and-blooms/bitset v1.20.0 github.com/blang/semver/v4 v4.0.0 - github.com/consensys/bavard v0.1.31-0.20250314194434-b30d4344e6d4 + github.com/consensys/bavard v0.1.31-0.20250406004941-2db259e4b582 github.com/consensys/compress v0.2.5 - github.com/consensys/gnark-crypto v0.17.1-0.20250326164229-5fd6610ac2a1 + github.com/consensys/gnark-crypto v0.17.1-0.20250407123233-f3e34b715587 github.com/fxamacker/cbor/v2 v2.7.0 github.com/google/go-cmp v0.6.0 github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 diff --git a/go.sum b/go.sum index 7e436d0c47..d17f6716b0 100644 --- a/go.sum +++ b/go.sum @@ -57,12 +57,12 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/consensys/bavard v0.1.31-0.20250314194434-b30d4344e6d4 h1:0J+ppRic2ZXsQE+Y+Lr9miam+RQVcWqwqe3SeiggR6s= -github.com/consensys/bavard v0.1.31-0.20250314194434-b30d4344e6d4/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= +github.com/consensys/bavard v0.1.31-0.20250406004941-2db259e4b582 h1:dTlIwEdFQmldzFf5F6bbTcYWhvnAgZai2g8eq3Wwxqg= +github.com/consensys/bavard v0.1.31-0.20250406004941-2db259e4b582/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= github.com/consensys/compress v0.2.5 h1:gJr1hKzbOD36JFsF1AN8lfXz1yevnJi1YolffY19Ntk= github.com/consensys/compress v0.2.5/go.mod h1:pyM+ZXiNUh7/0+AUjUf9RKUM6vSH7T/fsn5LLS0j1Tk= -github.com/consensys/gnark-crypto v0.17.1-0.20250326164229-5fd6610ac2a1 h1:6cK71BoMAjWHNl+EpvBh2PDDa0PIeoz1KFJ/6R16DjQ= -github.com/consensys/gnark-crypto v0.17.1-0.20250326164229-5fd6610ac2a1/go.mod h1:uV1HwfBwGRj50DGK3LbDLeCvq0RX/vFXST3CRSAu0Fs= +github.com/consensys/gnark-crypto v0.17.1-0.20250407123233-f3e34b715587 h1:7kACJq/CuNoKV9tfDHbXgS1hVsBu6yu1IH8hNtHXCdU= +github.com/consensys/gnark-crypto v0.17.1-0.20250407123233-f3e34b715587/go.mod h1:n9v7xUdQOvDbzaM55cFQUi67FIyb9Rl+Ia6PaM62ig0= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= From ebe3b999ccb77b82b3b00aa902de728463d8be3c Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 7 Apr 2025 12:51:59 +0000 Subject: [PATCH 045/105] refactor: use sqrt from gnark-crypto --- std/algebra/emulated/sw_bls12381/hints.go | 27 ++++++++ std/algebra/emulated/sw_bls12381/map_to_g1.go | 62 ------------------- 2 files changed, 27 insertions(+), 62 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/hints.go b/std/algebra/emulated/sw_bls12381/hints.go index 92dc8d0049..53f04bf959 100644 --- a/std/algebra/emulated/sw_bls12381/hints.go +++ b/std/algebra/emulated/sw_bls12381/hints.go @@ -7,6 +7,8 @@ import ( "github.com/consensys/gnark-crypto/ecc" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark/std/math/emulated" ) @@ -23,6 +25,7 @@ func GetHints() []solver.Hint { millerLoopAndCheckFinalExpHint, decomposeScalarG1Subscalars, decomposeScalarG1Signs, + g1SqrtRatioHint, } } @@ -330,3 +333,27 @@ func decomposeScalarG1Signs(mod *big.Int, inputs []*big.Int, outputs []*big.Int) return nil }) } + +// g1SqrtRatio computes the square root of u/v and returns 0 iff u/v was indeed a quadratic residue +// if not, we get sqrt(Z * u / v). Recall that Z is non-residue +// If v = 0, u/v is meaningless and the output is unspecified, without raising an error. +// The main idea is that since the computation of the square root involves taking large powers of u/v, the inversion of v can be avoided. +// +// nativeInputs[0] = u, nativeInputs[1]=v +// nativeOutput[1] = 1 if u/v is a QR, 0 otherwise, nativeOutput[1]=sqrt(u/v) or sqrt(Z u/v) +func g1SqrtRatioHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { + return emulated.UnwrapHint(nativeInputs, nativeOutputs, + func(mod *big.Int, inputs, outputs []*big.Int) error { + var u, v, z fp.Element + u.SetBigInt(inputs[0]) + v.SetBigInt(inputs[1]) + + isQNr := hash_to_curve.G1SqrtRatio(&z, &u, &v) + if isQNr != 0 { + isQNr = 1 + } + z.BigInt(outputs[0]) + outputs[1].SetInt64(int64(isQNr)) + return nil + }) +} diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index e6c5d4c1d0..e3d8834b9e 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -1,18 +1,10 @@ package sw_bls12381 import ( - "math/big" - - "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" - "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/math/emulated" ) -func init() { - solver.RegisterHint(g1SqrtRatioHint) -} - type FpApi = emulated.Field[emulated.BLS12381Fp] type FpElement = emulated.Element[emulated.BLS12381Fp] @@ -162,60 +154,6 @@ func g1Isogeny(fpApi *FpApi, p *G1Affine) (*G1Affine, error) { } -// g1SqrtRatio computes the square root of u/v and returns 0 iff u/v was indeed a quadratic residue -// if not, we get sqrt(Z * u / v). Recall that Z is non-residue -// If v = 0, u/v is meaningless and the output is unspecified, without raising an error. -// The main idea is that since the computation of the square root involves taking large powers of u/v, the inversion of v can be avoided. -// -// nativeInputs[0] = u, nativeInputs[1]=v -// nativeOutput[1] = 1 if u/v is a QR, 0 otherwise, nativeOutput[1]=sqrt(u/v) or sqrt(Z u/v) -func g1SqrtRatioHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { - return emulated.UnwrapHint(nativeInputs, nativeOutputs, - func(mod *big.Int, inputs, outputs []*big.Int) error { - - var z, u, v fp.Element - - u.SetBigInt(inputs[0]) - v.SetBigInt(inputs[1]) - - // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-optimized-sqrt_ratio-for-q- (3 mod 4) - var tv1 fp.Element - tv1.Square(&v) // 1. tv1 = v² - - var tv2 fp.Element - tv2.Mul(&u, &v) // 2. tv2 = u * v - tv1.Mul(&tv1, &tv2) // 3. tv1 = tv1 * tv2 - - var y1 fp.Element - { - var c1 big.Int - // c1 = 1000602388805416848354447456433976039139220704984751971333014534031007912622709466110671907282253916009473568139946 - c1.SetBytes([]byte{6, 128, 68, 122, 142, 95, 249, 166, 146, 198, 233, 237, 144, 210, 235, 53, 217, 29, 210, 225, 60, 225, 68, 175, 217, 204, 52, 168, 61, 172, 61, 137, 7, 170, 255, 255, 172, 84, 255, 255, 238, 127, 191, 255, 255, 255, 234, 170}) // c1 = (q - 3) / 4 # Integer arithmetic - - y1.Exp(tv1, &c1) // 4. y1 = tv1ᶜ¹ - } - - y1.Mul(&y1, &tv2) // 5. y1 = y1 * tv2 - - var y2 fp.Element - // c2 = sqrt(-Z) - tv3 := fp.Element{17544630987809824292, 17306709551153317753, 8299808889594647786, 5930295261504720397, 675038575008112577, 167386374569371918} - y2.Mul(&y1, &tv3) // 6. y2 = y1 * c2 - tv3.Square(&y1) // 7. tv3 = y1² - tv3.Mul(&tv3, &v) // 8. tv3 = tv3 * v - isQNr := tv3.NotEqual(&u) // 9. isQR = tv3 == u - z.Select(int(isQNr), &y1, &y2) // 10. y = CMOV(y2, y1, isQR) - - if isQNr != 0 { - isQNr = 1 - } - z.BigInt(outputs[0]) - outputs[1] = big.NewInt(int64(isQNr)) - - return nil - }) -} - // g1Sgn0 returns the parity of a func g1Sgn0(api *FpApi, a *FpElement) frontend.Variable { aReduced := api.Reduce(a) From 6e6246a9aa0bfe1d990b5d8da86da1d2cd674f48 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 7 Apr 2025 14:21:11 +0000 Subject: [PATCH 046/105] chore: go.mod update --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index af4a32e095..8202427261 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/blang/semver/v4 v4.0.0 github.com/consensys/bavard v0.1.31-0.20250406004941-2db259e4b582 github.com/consensys/compress v0.2.5 - github.com/consensys/gnark-crypto v0.17.1-0.20250407123233-f3e34b715587 + github.com/consensys/gnark-crypto v0.17.1-0.20250407141002-d4f06e01a637 github.com/fxamacker/cbor/v2 v2.7.0 github.com/google/go-cmp v0.6.0 github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 diff --git a/go.sum b/go.sum index d17f6716b0..8d81217dcd 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,8 @@ github.com/consensys/compress v0.2.5 h1:gJr1hKzbOD36JFsF1AN8lfXz1yevnJi1YolffY19 github.com/consensys/compress v0.2.5/go.mod h1:pyM+ZXiNUh7/0+AUjUf9RKUM6vSH7T/fsn5LLS0j1Tk= github.com/consensys/gnark-crypto v0.17.1-0.20250407123233-f3e34b715587 h1:7kACJq/CuNoKV9tfDHbXgS1hVsBu6yu1IH8hNtHXCdU= github.com/consensys/gnark-crypto v0.17.1-0.20250407123233-f3e34b715587/go.mod h1:n9v7xUdQOvDbzaM55cFQUi67FIyb9Rl+Ia6PaM62ig0= +github.com/consensys/gnark-crypto v0.17.1-0.20250407141002-d4f06e01a637 h1:KiqiL4mcpZUrnJjfm7IDh3wlfbnc88eRgI93KoUJ3cg= +github.com/consensys/gnark-crypto v0.17.1-0.20250407141002-d4f06e01a637/go.mod h1:n9v7xUdQOvDbzaM55cFQUi67FIyb9Rl+Ia6PaM62ig0= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= From 3730ea994c67998b983bde334fafee7390f6e4e3 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 7 Apr 2025 14:21:26 +0000 Subject: [PATCH 047/105] refactor: use isogeny map from gnark-crypto --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 147 +++--------------- 1 file changed, 25 insertions(+), 122 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index e3d8834b9e..2f6796ac02 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -1,6 +1,10 @@ package sw_bls12381 import ( + "fmt" + + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/math/emulated" ) @@ -8,150 +12,49 @@ import ( type FpApi = emulated.Field[emulated.BLS12381Fp] type FpElement = emulated.Element[emulated.BLS12381Fp] -func g1IsogenyXNumerator(api *FpApi, x *FpElement) (*FpElement, error) { - - return g1EvalPolynomial( - api, - false, - []FpElement{ - emulated.ValueOf[emulated.BLS12381Fp]("0x11a05f2b1e833340b809101dd99815856b303e88a2d7005ff2627b56cdb4e2c85610c2d5f2e62d6eaeac1662734649b7"), - emulated.ValueOf[emulated.BLS12381Fp]("0x17294ed3e943ab2f0588bab22147a81c7c17e75b2f6a8417f565e33c70d1e86b4838f2a6f318c356e834eef1b3cb83bb"), - emulated.ValueOf[emulated.BLS12381Fp]("0xd54005db97678ec1d1048c5d10a9a1bce032473295983e56878e501ec68e25c958c3e3d2a09729fe0179f9dac9edcb0"), - emulated.ValueOf[emulated.BLS12381Fp]("0x1778e7166fcc6db74e0609d307e55412d7f5e4656a8dbf25f1b33289f1b330835336e25ce3107193c5b388641d9b6861"), - emulated.ValueOf[emulated.BLS12381Fp]("0xe99726a3199f4436642b4b3e4118e5499db995a1257fb3f086eeb65982fac18985a286f301e77c451154ce9ac8895d9"), - emulated.ValueOf[emulated.BLS12381Fp]("0x1630c3250d7313ff01d1201bf7a74ab5db3cb17dd952799b9ed3ab9097e68f90a0870d2dcae73d19cd13c1c66f652983"), - emulated.ValueOf[emulated.BLS12381Fp]("0xd6ed6553fe44d296a3726c38ae652bfb11586264f0f8ce19008e218f9c86b2a8da25128c1052ecaddd7f225a139ed84"), - emulated.ValueOf[emulated.BLS12381Fp]("0x17b81e7701abdbe2e8743884d1117e53356de5ab275b4db1a682c62ef0f2753339b7c8f8c8f475af9ccb5618e3f0c88e"), - emulated.ValueOf[emulated.BLS12381Fp]("0x80d3cf1f9a78fc47b90b33563be990dc43b756ce79f5574a2c596c928c5d1de4fa295f296b74e956d71986a8497e317"), - emulated.ValueOf[emulated.BLS12381Fp]("0x169b1f8e1bcfa7c42e0c37515d138f22dd2ecb803a0c5c99676314baf4bb1b7fa3190b2edc0327797f241067be390c9e"), - emulated.ValueOf[emulated.BLS12381Fp]("0x10321da079ce07e272d8ec09d2565b0dfa7dccdde6787f96d50af36003b14866f69b771f8c285decca67df3f1605fb7b"), - emulated.ValueOf[emulated.BLS12381Fp]("0x6e08c248e260e70bd1e962381edee3d31d79d7e22c837bc23c0bf1bc24c6b68c24b1b80b64d391fa9c8ba2e8ba2d229"), - }, - x) -} - -func g1IsogenyXDenominator(api *FpApi, x *FpElement) (*FpElement, error) { - - return g1EvalPolynomial( - api, - true, - []FpElement{ - emulated.ValueOf[emulated.BLS12381Fp]("0x8ca8d548cff19ae18b2e62f4bd3fa6f01d5ef4ba35b48ba9c9588617fc8ac62b558d681be343df8993cf9fa40d21b1c"), - emulated.ValueOf[emulated.BLS12381Fp]("0x12561a5deb559c4348b4711298e536367041e8ca0cf0800c0126c2588c48bf5713daa8846cb026e9e5c8276ec82b3bff"), - emulated.ValueOf[emulated.BLS12381Fp]("0xb2962fe57a3225e8137e629bff2991f6f89416f5a718cd1fca64e00b11aceacd6a3d0967c94fedcfcc239ba5cb83e19"), - emulated.ValueOf[emulated.BLS12381Fp]("0x3425581a58ae2fec83aafef7c40eb545b08243f16b1655154cca8abc28d6fd04976d5243eecf5c4130de8938dc62cd8"), - emulated.ValueOf[emulated.BLS12381Fp]("0x13a8e162022914a80a6f1d5f43e7a07dffdfc759a12062bb8d6b44e833b306da9bd29ba81f35781d539d395b3532a21e"), - emulated.ValueOf[emulated.BLS12381Fp]("0xe7355f8e4e667b955390f7f0506c6e9395735e9ce9cad4d0a43bcef24b8982f7400d24bc4228f11c02df9a29f6304a5"), - emulated.ValueOf[emulated.BLS12381Fp]("0x772caacf16936190f3e0c63e0596721570f5799af53a1894e2e073062aede9cea73b3538f0de06cec2574496ee84a3a"), - emulated.ValueOf[emulated.BLS12381Fp]("0x14a7ac2a9d64a8b230b3f5b074cf01996e7f63c21bca68a81996e1cdf9822c580fa5b9489d11e2d311f7d99bbdcc5a5e"), - emulated.ValueOf[emulated.BLS12381Fp]("0xa10ecf6ada54f825e920b3dafc7a3cce07f8d1d7161366b74100da67f39883503826692abba43704776ec3a79a1d641"), - emulated.ValueOf[emulated.BLS12381Fp]("0x95fc13ab9e92ad4476d6e3eb3a56680f682b4ee96f7d03776df533978f31c1593174e4b4b7865002d6384d168ecdd0a"), - }, - x) -} - -func g1IsogenyYNumerator(api *FpApi, x, y *FpElement) (*FpElement, error) { - - ix, err := g1EvalPolynomial( - api, - false, - []FpElement{ - emulated.ValueOf[emulated.BLS12381Fp]("0x90d97c81ba24ee0259d1f094980dcfa11ad138e48a869522b52af6c956543d3cd0c7aee9b3ba3c2be9845719707bb33"), - emulated.ValueOf[emulated.BLS12381Fp]("0x134996a104ee5811d51036d776fb46831223e96c254f383d0f906343eb67ad34d6c56711962fa8bfe097e75a2e41c696"), - emulated.ValueOf[emulated.BLS12381Fp]("0xcc786baa966e66f4a384c86a3b49942552e2d658a31ce2c344be4b91400da7d26d521628b00523b8dfe240c72de1f6"), - emulated.ValueOf[emulated.BLS12381Fp]("0x1f86376e8981c217898751ad8746757d42aa7b90eeb791c09e4a3ec03251cf9de405aba9ec61deca6355c77b0e5f4cb"), - emulated.ValueOf[emulated.BLS12381Fp]("0x8cc03fdefe0ff135caf4fe2a21529c4195536fbe3ce50b879833fd221351adc2ee7f8dc099040a841b6daecf2e8fedb"), - emulated.ValueOf[emulated.BLS12381Fp]("0x16603fca40634b6a2211e11db8f0a6a074a7d0d4afadb7bd76505c3d3ad5544e203f6326c95a807299b23ab13633a5f0"), - emulated.ValueOf[emulated.BLS12381Fp]("0x4ab0b9bcfac1bbcb2c977d027796b3ce75bb8ca2be184cb5231413c4d634f3747a87ac2460f415ec961f8855fe9d6f2"), - emulated.ValueOf[emulated.BLS12381Fp]("0x987c8d5333ab86fde9926bd2ca6c674170a05bfe3bdd81ffd038da6c26c842642f64550fedfe935a15e4ca31870fb29"), - emulated.ValueOf[emulated.BLS12381Fp]("0x9fc4018bd96684be88c9e221e4da1bb8f3abd16679dc26c1e8b6e6a1f20cabe69d65201c78607a360370e577bdba587"), - emulated.ValueOf[emulated.BLS12381Fp]("0xe1bba7a1186bdb5223abde7ada14a23c42a0ca7915af6fe06985e7ed1e4d43b9b3f7055dd4eba6f2bafaaebca731c30"), - emulated.ValueOf[emulated.BLS12381Fp]("0x19713e47937cd1be0dfd0b8f1d43fb93cd2fcbcb6caf493fd1183e416389e61031bf3a5cce3fbafce813711ad011c132"), - emulated.ValueOf[emulated.BLS12381Fp]("0x18b46a908f36f6deb918c143fed2edcc523559b8aaf0c2462e6bfe7f911f643249d9cdf41b44d606ce07c8a4d0074d8e"), - emulated.ValueOf[emulated.BLS12381Fp]("0xb182cac101b9399d155096004f53f447aa7b12a3426b08ec02710e807b4633f06c851c1919211f20d4c04f00b971ef8"), - emulated.ValueOf[emulated.BLS12381Fp]("0x245a394ad1eca9b72fc00ae7be315dc757b3b080d4c158013e6632d3c40659cc6cf90ad1c232a6442d9d3f5db980133"), - emulated.ValueOf[emulated.BLS12381Fp]("0x5c129645e44cf1102a159f748c4a3fc5e673d81d7e86568d9ab0f5d396a7ce46ba1049b6579afb7866b1e715475224b"), - emulated.ValueOf[emulated.BLS12381Fp]("0x15e6be4e990f03ce4ea50b3b42df2eb5cb181d8f84965a3957add4fa95af01b2b665027efec01c7704b456be69c8b604"), - }, - x) - if err != nil { - return ix, err +func g1EvalPolynomial(api *FpApi, monic bool, coefficients []fp.Element, x *FpElement) (*FpElement, error) { + emuCoefficients := make([]*baseEl, len(coefficients)) + for i := range coefficients { + emulatedCoefficient := emulated.ValueOf[emulated.BLS12381Fp](coefficients[i]) + emuCoefficients[i] = &emulatedCoefficient } - - ix = api.Mul(ix, y) - return ix, nil -} - -func g1IsogenyYDenominator(api *FpApi, x *FpElement) (*FpElement, error) { - - return g1EvalPolynomial( - api, - true, - []FpElement{ - emulated.ValueOf[emulated.BLS12381Fp]("0x16112c4c3a9c98b252181140fad0eae9601a6de578980be6eec3232b5be72e7a07f3688ef60c206d01479253b03663c1"), - emulated.ValueOf[emulated.BLS12381Fp]("0x1962d75c2381201e1a0cbd6c43c348b885c84ff731c4d59ca4a10356f453e01f78a4260763529e3532f6102c2e49a03d"), - emulated.ValueOf[emulated.BLS12381Fp]("0x58df3306640da276faaae7d6e8eb15778c4855551ae7f310c35a5dd279cd2eca6757cd636f96f891e2538b53dbf67f2"), - emulated.ValueOf[emulated.BLS12381Fp]("0x16b7d288798e5395f20d23bf89edb4d1d115c5dbddbcd30e123da489e726af41727364f2c28297ada8d26d98445f5416"), - emulated.ValueOf[emulated.BLS12381Fp]("0xbe0e079545f43e4b00cc912f8228ddcc6d19c9f0f69bbb0542eda0fc9dec916a20b15dc0fd2ededda39142311a5001d"), - emulated.ValueOf[emulated.BLS12381Fp]("0x8d9e5297186db2d9fb266eaac783182b70152c65550d881c5ecd87b6f0f5a6449f38db9dfa9cce202c6477faaf9b7ac"), - emulated.ValueOf[emulated.BLS12381Fp]("0x166007c08a99db2fc3ba8734ace9824b5eecfdfa8d0cf8ef5dd365bc400a0051d5fa9c01a58b1fb93d1a1399126a775c"), - emulated.ValueOf[emulated.BLS12381Fp]("0x16a3ef08be3ea7ea03bcddfabba6ff6ee5a4375efa1f4fd7feb34fd206357132b920f5b00801dee460ee415a15812ed9"), - emulated.ValueOf[emulated.BLS12381Fp]("0x1866c8ed336c61231a1be54fd1d74cc4f9fb0ce4c6af5920abc5750c4bf39b4852cfe2f7bb9248836b233d9d55535d4a"), - emulated.ValueOf[emulated.BLS12381Fp]("0x167a55cda70a6e1cea820597d94a84903216f763e13d87bb5308592e7ea7d4fbc7385ea3d529b35e346ef48bb8913f55"), - emulated.ValueOf[emulated.BLS12381Fp]("0x4d2f259eea405bd48f010a01ad2911d9c6dd039bb61a6290e591b36e636a5c871a5c29f4f83060400f8b49cba8f6aa8"), - emulated.ValueOf[emulated.BLS12381Fp]("0xaccbb67481d033ff5852c1e48c50c477f94ff8aefce42d28c0f9a88cea7913516f968986f7ebbea9684b529e2561092"), - emulated.ValueOf[emulated.BLS12381Fp]("0xad6b9514c767fe3c3613144b45f1496543346d98adf02267d5ceef9a00d9b8693000763e3b90ac11e99b138573345cc"), - emulated.ValueOf[emulated.BLS12381Fp]("0x2660400eb2e4f3b628bdd0d53cd76f2bf565b94e72927c1cb748df27942480e420517bd8714cc80d1fadc1326ed06f7"), - emulated.ValueOf[emulated.BLS12381Fp]("0xe0fa1d816ddc03e6b24255e0d7819c171c40f65e273b853324efcd6356caa205ca2f570f13497804415473a1d634b8f"), - }, - x) -} - -func g1EvalPolynomial(api *FpApi, monic bool, coefficients []FpElement, x *FpElement) (*FpElement, error) { - var res *FpElement if monic { - res = api.Add(&coefficients[len(coefficients)-1], x) + res = api.Add(emuCoefficients[len(emuCoefficients)-1], x) } else { - res = &coefficients[len(coefficients)-1] + res = emuCoefficients[len(emuCoefficients)-1] } - for i := len(coefficients) - 2; i >= 0; i-- { + for i := len(emuCoefficients) - 2; i >= 0; i-- { res = api.Mul(res, x) - res = api.Add(res, &coefficients[i]) + res = api.Add(res, emuCoefficients[i]) } return res, nil } func g1Isogeny(fpApi *FpApi, p *G1Affine) (*G1Affine, error) { - - den := make([]*FpElement, 2) - var err error - - den[1], err = g1IsogenyYDenominator(fpApi, &p.X) + isogenyMap := hash_to_curve.G1IsogenyMap() + ydenom, err := g1EvalPolynomial(fpApi, true, isogenyMap[3], &p.X) if err != nil { - return nil, err + return nil, fmt.Errorf("y denom: %w", err) } - den[0], err = g1IsogenyXDenominator(fpApi, &p.X) + xdenom, err := g1EvalPolynomial(fpApi, true, isogenyMap[1], &p.X) if err != nil { - return nil, err + return nil, fmt.Errorf("x denom: %w", err) } - - y, err := g1IsogenyYNumerator(fpApi, &p.X, &p.Y) + y, err := g1EvalPolynomial(fpApi, false, isogenyMap[2], &p.X) if err != nil { - return nil, err + return nil, fmt.Errorf("y num: %w", err) } - x, err := g1IsogenyXNumerator(fpApi, &p.X) + y = fpApi.Mul(y, &p.Y) + x, err := g1EvalPolynomial(fpApi, false, isogenyMap[0], &p.X) if err != nil { - return nil, err + return nil, fmt.Errorf("x num: %w", err) } - - x = fpApi.Div(x, den[0]) - y = fpApi.Div(y, den[1]) - + x = fpApi.Div(x, xdenom) + y = fpApi.Div(y, ydenom) return &G1Affine{X: *x, Y: *y}, nil - } // g1Sgn0 returns the parity of a From e5c9a42aa31e17bb2e7b5efb12c95521a58da319 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 7 Apr 2025 15:27:58 +0000 Subject: [PATCH 048/105] chore: do not redefine base element --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 2f6796ac02..0663b4c175 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -10,15 +10,14 @@ import ( ) type FpApi = emulated.Field[emulated.BLS12381Fp] -type FpElement = emulated.Element[emulated.BLS12381Fp] -func g1EvalPolynomial(api *FpApi, monic bool, coefficients []fp.Element, x *FpElement) (*FpElement, error) { +func g1EvalFixedPolynomial(api *FpApi, monic bool, coefficients []fp.Element, x *baseEl) (*baseEl, error) { emuCoefficients := make([]*baseEl, len(coefficients)) for i := range coefficients { emulatedCoefficient := emulated.ValueOf[emulated.BLS12381Fp](coefficients[i]) emuCoefficients[i] = &emulatedCoefficient } - var res *FpElement + var res *baseEl if monic { res = api.Add(emuCoefficients[len(emuCoefficients)-1], x) } else { @@ -35,20 +34,20 @@ func g1EvalPolynomial(api *FpApi, monic bool, coefficients []fp.Element, x *FpEl func g1Isogeny(fpApi *FpApi, p *G1Affine) (*G1Affine, error) { isogenyMap := hash_to_curve.G1IsogenyMap() - ydenom, err := g1EvalPolynomial(fpApi, true, isogenyMap[3], &p.X) + ydenom, err := g1EvalFixedPolynomial(fpApi, true, isogenyMap[3], &p.X) if err != nil { return nil, fmt.Errorf("y denom: %w", err) } - xdenom, err := g1EvalPolynomial(fpApi, true, isogenyMap[1], &p.X) + xdenom, err := g1EvalFixedPolynomial(fpApi, true, isogenyMap[1], &p.X) if err != nil { return nil, fmt.Errorf("x denom: %w", err) } - y, err := g1EvalPolynomial(fpApi, false, isogenyMap[2], &p.X) + y, err := g1EvalFixedPolynomial(fpApi, false, isogenyMap[2], &p.X) if err != nil { return nil, fmt.Errorf("y num: %w", err) } y = fpApi.Mul(y, &p.Y) - x, err := g1EvalPolynomial(fpApi, false, isogenyMap[0], &p.X) + x, err := g1EvalFixedPolynomial(fpApi, false, isogenyMap[0], &p.X) if err != nil { return nil, fmt.Errorf("x num: %w", err) } @@ -58,7 +57,7 @@ func g1Isogeny(fpApi *FpApi, p *G1Affine) (*G1Affine, error) { } // g1Sgn0 returns the parity of a -func g1Sgn0(api *FpApi, a *FpElement) frontend.Variable { +func g1Sgn0(api *FpApi, a *baseEl) frontend.Variable { aReduced := api.Reduce(a) ab := api.ToBits(aReduced) return ab[0] @@ -91,7 +90,7 @@ func ClearCofactor(g *G1, q *G1Affine) (*G1Affine, error) { // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-simplified-swu-method // MapToCurve1 implements the SSWU map // No cofactor clearing or isogeny -func MapToCurve1(api frontend.API, u *FpElement) (*G1Affine, error) { +func MapToCurve1(api frontend.API, u *baseEl) (*G1Affine, error) { one := emulated.ValueOf[emulated.BLS12381Fp]("1") eleven := emulated.ValueOf[emulated.BLS12381Fp]("11") @@ -191,7 +190,7 @@ func MapToCurve1(api frontend.API, u *FpElement) (*G1Affine, error) { } // MapToG1 invokes the SSWU map, and guarantees that the result is in g1 -func MapToG1(api frontend.API, u *FpElement) (*G1Affine, error) { +func MapToG1(api frontend.API, u *baseEl) (*G1Affine, error) { res, err := MapToCurve1(api, u) if err != nil { From 4ab6383b8394e1ae9a3402fd3ef825cd8094e138 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 7 Apr 2025 15:45:30 +0000 Subject: [PATCH 049/105] chore: remove unneeded error handling --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 186 ++++++++---------- .../emulated/sw_bls12381/map_to_g1_test.go | 22 +-- std/evmprecompiles/16-blsmaptog1.go | 7 +- std/evmprecompiles/bls_test.go | 5 +- 4 files changed, 98 insertions(+), 122 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 0663b4c175..d7387c81ae 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -11,7 +11,7 @@ import ( type FpApi = emulated.Field[emulated.BLS12381Fp] -func g1EvalFixedPolynomial(api *FpApi, monic bool, coefficients []fp.Element, x *baseEl) (*baseEl, error) { +func (g1 *G1) evalFixedPolynomial(monic bool, coefficients []fp.Element, x *baseEl) (*baseEl, error) { emuCoefficients := make([]*baseEl, len(coefficients)) for i := range coefficients { emulatedCoefficient := emulated.ValueOf[emulated.BLS12381Fp](coefficients[i]) @@ -19,128 +19,121 @@ func g1EvalFixedPolynomial(api *FpApi, monic bool, coefficients []fp.Element, x } var res *baseEl if monic { - res = api.Add(emuCoefficients[len(emuCoefficients)-1], x) + res = g1.curveF.Add(emuCoefficients[len(emuCoefficients)-1], x) } else { res = emuCoefficients[len(emuCoefficients)-1] } for i := len(emuCoefficients) - 2; i >= 0; i-- { - res = api.Mul(res, x) - res = api.Add(res, emuCoefficients[i]) + res = g1.curveF.Mul(res, x) + res = g1.curveF.Add(res, emuCoefficients[i]) } return res, nil } -func g1Isogeny(fpApi *FpApi, p *G1Affine) (*G1Affine, error) { +func (g1 *G1) isogeny(p *G1Affine) (*G1Affine, error) { isogenyMap := hash_to_curve.G1IsogenyMap() - ydenom, err := g1EvalFixedPolynomial(fpApi, true, isogenyMap[3], &p.X) + ydenom, err := g1.evalFixedPolynomial(true, isogenyMap[3], &p.X) if err != nil { return nil, fmt.Errorf("y denom: %w", err) } - xdenom, err := g1EvalFixedPolynomial(fpApi, true, isogenyMap[1], &p.X) + xdenom, err := g1.evalFixedPolynomial(true, isogenyMap[1], &p.X) if err != nil { return nil, fmt.Errorf("x denom: %w", err) } - y, err := g1EvalFixedPolynomial(fpApi, false, isogenyMap[2], &p.X) + y, err := g1.evalFixedPolynomial(false, isogenyMap[2], &p.X) if err != nil { return nil, fmt.Errorf("y num: %w", err) } - y = fpApi.Mul(y, &p.Y) - x, err := g1EvalFixedPolynomial(fpApi, false, isogenyMap[0], &p.X) + y = g1.curveF.Mul(y, &p.Y) + x, err := g1.evalFixedPolynomial(false, isogenyMap[0], &p.X) if err != nil { return nil, fmt.Errorf("x num: %w", err) } - x = fpApi.Div(x, xdenom) - y = fpApi.Div(y, ydenom) + x = g1.curveF.Div(x, xdenom) + y = g1.curveF.Div(y, ydenom) return &G1Affine{X: *x, Y: *y}, nil } // g1Sgn0 returns the parity of a -func g1Sgn0(api *FpApi, a *baseEl) frontend.Variable { - aReduced := api.Reduce(a) - ab := api.ToBits(aReduced) +func (g1 *G1) sgn0(a *baseEl) frontend.Variable { + ab := g1.curveF.ToBitsCanonical(a) return ab[0] } -func ClearCofactor(g *G1, q *G1Affine) (*G1Affine, error) { - +func (g1 *G1) ClearCofactor(q *G1Affine) *G1Affine { // cf https://eprint.iacr.org/2019/403.pdf, 5 // mulBySeed - z := g.double(q) - z = g.add(z, q) - z = g.double(z) - z = g.doubleAndAdd(z, q) - z = g.doubleN(z, 2) - z = g.doubleAndAdd(z, q) - z = g.doubleN(z, 8) - z = g.doubleAndAdd(z, q) - z = g.doubleN(z, 31) - z = g.doubleAndAdd(z, q) - z = g.doubleN(z, 16) + z := g1.double(q) + z = g1.add(z, q) + z = g1.double(z) + z = g1.doubleAndAdd(z, q) + z = g1.doubleN(z, 2) + z = g1.doubleAndAdd(z, q) + z = g1.doubleN(z, 8) + z = g1.doubleAndAdd(z, q) + z = g1.doubleN(z, 31) + z = g1.doubleAndAdd(z, q) + z = g1.doubleN(z, 16) // Add assign - z = g.add(z, q) - - return z, nil + z = g1.add(z, q) + return z } -// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-simplified-swu-method -// MapToCurve1 implements the SSWU map -// No cofactor clearing or isogeny -func MapToCurve1(api frontend.API, u *baseEl) (*G1Affine, error) { +// MapToCurve1 implements the SSWU map. It does not perform cofactor clearing or isogeny computation. +// See [G1.MapToG1] for the complete map to G1. +// +// See: https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-simplified-swu-method +func (g1 *G1) MapToCurve1(u *baseEl) (*G1Affine, error) { one := emulated.ValueOf[emulated.BLS12381Fp]("1") eleven := emulated.ValueOf[emulated.BLS12381Fp]("11") - fpApi, err := emulated.NewField[emulated.BLS12381Fp](api) - if err != nil { - return nil, err - } - sswuIsoCurveCoeffA := emulated.ValueOf[emulated.BLS12381Fp]("0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d") sswuIsoCurveCoeffB := emulated.ValueOf[emulated.BLS12381Fp]("0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0") - tv1 := fpApi.Mul(u, u) // 1. tv1 = u² + tv1 := g1.curveF.Mul(u, u) // 1. tv1 = u² //mul tv1 by Z ( g1MulByZ) - tv1 = fpApi.Mul(&eleven, tv1) + tv1 = g1.curveF.Mul(&eleven, tv1) // var tv2 fp.Element - tv2 := fpApi.Mul(tv1, tv1) // 3. tv2 = tv1² - tv2 = fpApi.Add(tv2, tv1) // 4. tv2 = tv2 + tv1 + tv2 := g1.curveF.Mul(tv1, tv1) // 3. tv2 = tv1² + tv2 = g1.curveF.Add(tv2, tv1) // 4. tv2 = tv2 + tv1 // var tv3 fp.Element // var tv4 fp.Element - tv3 := fpApi.Add(tv2, &one) // 5. tv3 = tv2 + 1 - tv3 = fpApi.Mul(tv3, &sswuIsoCurveCoeffB) // 6. tv3 = B * tv3 + tv3 := g1.curveF.Add(tv2, &one) // 5. tv3 = tv2 + 1 + tv3 = g1.curveF.Mul(tv3, &sswuIsoCurveCoeffB) // 6. tv3 = B * tv3 // tv2NZero := g1NotZero(&tv2) - tv2IsZero := fpApi.IsZero(tv2) + tv2IsZero := g1.curveF.IsZero(tv2) // tv4 = Z - tv2 = fpApi.Neg(tv2) // tv2.Neg(&tv2) - tv4 := fpApi.Select(tv2IsZero, &eleven, tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) - tv4 = fpApi.Mul(tv4, &sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 + tv2 = g1.curveF.Neg(tv2) // tv2.Neg(&tv2) + tv4 := g1.curveF.Select(tv2IsZero, &eleven, tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) + tv4 = g1.curveF.Mul(tv4, &sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 - tv2 = fpApi.Mul(tv3, tv3) // 9. tv2 = tv3² + tv2 = g1.curveF.Mul(tv3, tv3) // 9. tv2 = tv3² - tv6 := fpApi.Mul(tv4, tv4) // 10. tv6 = tv4² + tv6 := g1.curveF.Mul(tv4, tv4) // 10. tv6 = tv4² - tv5 := fpApi.Mul(tv6, &sswuIsoCurveCoeffA) // 11. tv5 = A * tv6 + tv5 := g1.curveF.Mul(tv6, &sswuIsoCurveCoeffA) // 11. tv5 = A * tv6 - tv2 = fpApi.Add(tv2, tv5) // 12. tv2 = tv2 + tv5 - tv2 = fpApi.Mul(tv2, tv3) // 13. tv2 = tv2 * tv3 - tv6 = fpApi.Mul(tv6, tv4) // 14. tv6 = tv6 * tv4 + tv2 = g1.curveF.Add(tv2, tv5) // 12. tv2 = tv2 + tv5 + tv2 = g1.curveF.Mul(tv2, tv3) // 13. tv2 = tv2 * tv3 + tv6 = g1.curveF.Mul(tv6, tv4) // 14. tv6 = tv6 * tv4 - tv5 = fpApi.Mul(tv6, &sswuIsoCurveCoeffB) // 15. tv5 = B * tv6 - tv2 = fpApi.Add(tv2, tv5) // 16. tv2 = tv2 + tv5 + tv5 = g1.curveF.Mul(tv6, &sswuIsoCurveCoeffB) // 15. tv5 = B * tv6 + tv2 = g1.curveF.Add(tv2, tv5) // 16. tv2 = tv2 + tv5 - x := fpApi.Mul(tv1, tv3) // 17. x = tv1 * tv3 + x := g1.curveF.Mul(tv1, tv3) // 17. x = tv1 * tv3 - hint, err := fpApi.NewHint(g1SqrtRatioHint, 2, tv2, tv6) + hint, err := g1.curveF.NewHint(g1SqrtRatioHint, 2, tv2, tv6) if err != nil { return nil, err } @@ -151,71 +144,58 @@ func MapToCurve1(api frontend.API, u *baseEl) (*G1Affine, error) { // (gx1NSquare==1 AND (u/v) QNR ) OR (gx1NSquare==0 AND (u/v) QR ) gx1NSquare := hint[1].Limbs[0] - api.AssertIsBoolean(gx1NSquare) - y1Squarev := fpApi.Mul(y1, y1) - y1Squarev = fpApi.Mul(y1Squarev, tv6) - uz := fpApi.Mul(tv2, &eleven) - ysvMinusuz := fpApi.Sub(y1Squarev, uz) - isQNRWitness := fpApi.IsZero(ysvMinusuz) - cond1 := api.And(isQNRWitness, gx1NSquare) + g1.api.AssertIsBoolean(gx1NSquare) + y1Squarev := g1.curveF.Mul(y1, y1) + y1Squarev = g1.curveF.Mul(y1Squarev, tv6) + uz := g1.curveF.Mul(tv2, &eleven) + ysvMinusuz := g1.curveF.Sub(y1Squarev, uz) + isQNRWitness := g1.curveF.IsZero(ysvMinusuz) + cond1 := g1.api.And(isQNRWitness, gx1NSquare) - ysvMinusu := fpApi.Sub(y1Squarev, tv2) - isQRWitness := fpApi.IsZero(ysvMinusu) - isQR := api.Sub(1, gx1NSquare) - cond2 := api.And(isQR, isQRWitness) + ysvMinusu := g1.curveF.Sub(y1Squarev, tv2) + isQRWitness := g1.curveF.IsZero(ysvMinusu) + isQR := g1.api.Sub(1, gx1NSquare) + cond2 := g1.api.And(isQR, isQRWitness) - cond := api.Xor(cond1, cond2) - api.AssertIsEqual(cond, 1) + cond := g1.api.Xor(cond1, cond2) + g1.api.AssertIsEqual(cond, 1) // var y fp.Element - y := fpApi.Mul(tv1, u) // 19. y = tv1 * u + y := g1.curveF.Mul(tv1, u) // 19. y = tv1 * u - y = fpApi.Mul(y, y1) // 20. y = y * y1 + y = g1.curveF.Mul(y, y1) // 20. y = y * y1 - x = fpApi.Select(gx1NSquare, x, tv3) // 21. x = CMOV(x, tv3, is_gx1_square) - y = fpApi.Select(gx1NSquare, y, y1) // 22. y = CMOV(y, y1, is_gx1_square) + x = g1.curveF.Select(gx1NSquare, x, tv3) // 21. x = CMOV(x, tv3, is_gx1_square) + y = g1.curveF.Select(gx1NSquare, y, y1) // 22. y = CMOV(y, y1, is_gx1_square) - y1 = fpApi.Neg(y) - y1 = fpApi.Reduce(y1) - sel := api.IsZero(api.Sub(g1Sgn0(fpApi, u), g1Sgn0(fpApi, y))) - y = fpApi.Select(sel, y, y1) + y1 = g1.curveF.Neg(y) + y1 = g1.curveF.Reduce(y1) + sel := g1.api.IsZero(g1.api.Sub(g1.sgn0(u), g1.sgn0(y))) + y = g1.curveF.Select(sel, y, y1) // // 23. e1 = sgn0(u) == sgn0(y) // // 24. y = CMOV(-y, y, e1) - x = fpApi.Div(x, tv4) // 25. x = x / tv4 + x = g1.curveF.Div(x, tv4) // 25. x = x / tv4 return &G1Affine{X: *x, Y: *y}, nil } -// MapToG1 invokes the SSWU map, and guarantees that the result is in g1 -func MapToG1(api frontend.API, u *baseEl) (*G1Affine, error) { - - res, err := MapToCurve1(api, u) - if err != nil { - return nil, err - } - - //this is in an isogenous curve - fpApi, err := emulated.NewField[emulated.BLS12381Fp](api) - if err != nil { - return nil, err - } - z, err := g1Isogeny(fpApi, res) +// MapToG1 invokes the SSWU map, and guarantees that the result is in G1. For +// variant without cofactor clearing and isogeny, see [G1.MapToCurve1]. +func (g1 *G1) MapToG1(u *baseEl) (*G1Affine, error) { + res, err := g1.MapToCurve1(u) if err != nil { - return nil, err + return nil, fmt.Errorf("map to curve: %w", err) } - g1, err := NewG1(api) + z, err := g1.isogeny(res) if err != nil { - return nil, err + return nil, fmt.Errorf("isogeny: %w", err) } - z, err = ClearCofactor(g1, z) - if err != nil { - return nil, err - } + z = g1.ClearCofactor(z) return z, nil } diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1_test.go b/std/algebra/emulated/sw_bls12381/map_to_g1_test.go index 154ca7b055..e6f853f2b8 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1_test.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1_test.go @@ -1,6 +1,7 @@ package sw_bls12381 import ( + "fmt" "testing" "github.com/consensys/gnark-crypto/ecc" @@ -22,16 +23,12 @@ func (circuit *ClearCofactorCircuit) Define(api frontend.API) error { if err != nil { return err } - clearedPoint, err := ClearCofactor(g, &circuit.Point) - if err != nil { - return err - } + clearedPoint := g.ClearCofactor(&circuit.Point) g.AssertIsEqual(clearedPoint, &circuit.Res) return nil } func TestClearCofactor(t *testing.T) { - assert := test.NewAssert(t) _, _, g1, _ := bls12381.Generators() var g2 bls12381.G1Affine @@ -47,18 +44,17 @@ func TestClearCofactor(t *testing.T) { // Test MapToCurve type MapToCurveCircuit struct { - U FpElement + U emulated.Element[BaseField] Res G1Affine } func (circuit *MapToCurveCircuit) Define(api frontend.API) error { - g, err := NewG1(api) if err != nil { return err } - r, err := MapToCurve1(api, &circuit.U) + r, err := g.MapToCurve1(&circuit.U) if err != nil { return err } @@ -86,18 +82,16 @@ func TestMapToCurve(t *testing.T) { // Test Map to G1 type MapToG1Circuit struct { - A FpElement + A emulated.Element[BaseField] R G1Affine } func (circuit *MapToG1Circuit) Define(api frontend.API) error { - - res, err := MapToG1(api, &circuit.A) + g, err := NewG1(api) if err != nil { - return err + return fmt.Errorf("new G1: %w", err) } - - g, err := NewG1(api) + res, err := g.MapToG1(&circuit.A) if err != nil { return err } diff --git a/std/evmprecompiles/16-blsmaptog1.go b/std/evmprecompiles/16-blsmaptog1.go index ed3c080e60..0d9776fd4f 100644 --- a/std/evmprecompiles/16-blsmaptog1.go +++ b/std/evmprecompiles/16-blsmaptog1.go @@ -11,8 +11,11 @@ import ( // // [ECMapToG1BLS]: https://eips.ethereum.org/EIPS/eip-2537 func ECMapToG1BLS(api frontend.API, u *emulated.Element[emulated.BLS12381Fp]) *sw_emulated.AffinePoint[emulated.BLS12381Fp] { - - res, err := sw_bls12381.MapToG1(api, u) + g, err := sw_bls12381.NewG1(api) + if err != nil { + panic(err) + } + res, err := g.MapToG1(u) if err != nil { panic(err) } diff --git a/std/evmprecompiles/bls_test.go b/std/evmprecompiles/bls_test.go index b5cee23c6f..4f0e04e0b5 100644 --- a/std/evmprecompiles/bls_test.go +++ b/std/evmprecompiles/bls_test.go @@ -8,7 +8,6 @@ import ( "github.com/consensys/gnark-crypto/ecc" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" - fr_bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" @@ -103,7 +102,7 @@ func (c *ecmsmg1BLSCircuit) Define(api frontend.API) error { func TestECMSMG1BLSCircuit(t *testing.T) { assert := test.NewAssert(t) P := make([]bls12381.G1Affine, 10) - S := make([]fr_bls12381.Element, 10) + S := make([]fr.Element, 10) for i := 0; i < 10; i++ { S[i].SetRandom() P[i].ScalarMultiplicationBase(S[i].BigInt(new(big.Int))) @@ -216,7 +215,7 @@ func (c *ecmsmg2BLSCircuit) Define(api frontend.API) error { func TestECMSMG2BLSCircuit(t *testing.T) { assert := test.NewAssert(t) P := make([]bls12381.G2Affine, 10) - S := make([]fr_bls12381.Element, 10) + S := make([]fr.Element, 10) for i := 0; i < 10; i++ { S[i].SetRandom() P[i].ScalarMultiplicationBase(S[i].BigInt(new(big.Int))) From c7b8845da4115ac1fd27ac891c023800321e50f1 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 7 Apr 2025 15:46:25 +0000 Subject: [PATCH 050/105] chore: cleanup unused definition --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index d7387c81ae..2db5437478 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -9,8 +9,6 @@ import ( "github.com/consensys/gnark/std/math/emulated" ) -type FpApi = emulated.Field[emulated.BLS12381Fp] - func (g1 *G1) evalFixedPolynomial(monic bool, coefficients []fp.Element, x *baseEl) (*baseEl, error) { emuCoefficients := make([]*baseEl, len(coefficients)) for i := range coefficients { @@ -62,6 +60,9 @@ func (g1 *G1) sgn0(a *baseEl) frontend.Variable { return ab[0] } +// ClearCofactor clears the cofactor of a point in G1. +// +// See: https://eprint.iacr.org/2019/403.pdf, 5 func (g1 *G1) ClearCofactor(q *G1Affine) *G1Affine { // cf https://eprint.iacr.org/2019/403.pdf, 5 From 31c96489ab13d68b8847404766d8f7ca45a83795 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 7 Apr 2025 20:36:23 +0000 Subject: [PATCH 051/105] chore: fix some merge issues --- std/algebra/emulated/sw_bls12381/g2.go | 54 ------------------- std/algebra/emulated/sw_bls12381/g2_test.go | 7 ++- .../emulated/sw_bls12381/hash_to_g2.go | 13 +++-- .../emulated/sw_bls12381/hash_to_g2_test.go | 15 ++++-- std/algebra/emulated/sw_bls12381/hints.go | 24 +++++---- 5 files changed, 39 insertions(+), 74 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/g2.go b/std/algebra/emulated/sw_bls12381/g2.go index 97cc1423ed..5b65d1fddb 100644 --- a/std/algebra/emulated/sw_bls12381/g2.go +++ b/std/algebra/emulated/sw_bls12381/g2.go @@ -236,60 +236,6 @@ func (g2 G2) add(p, q *G2Affine) *G2Affine { } } -// Follow sw_emulated.Curve.AddUnified to implement the Brier and Joye algorithm -// to handle edge cases, i.e., p == q, p == 0 or/and q == 0 -func (g2 G2) addUnified(p, q *G2Affine) *G2Affine { - - // selector1 = 1 when p is (0,0) and 0 otherwise - selector1 := g2.api.And(g2.Ext2.IsZero(&p.P.X), g2.Ext2.IsZero(&p.P.Y)) - // selector2 = 1 when q is (0,0) and 0 otherwise - selector2 := g2.api.And(g2.Ext2.IsZero(&q.P.X), g2.Ext2.IsZero(&q.P.Y)) - - // λ = ((p.x+q.x)² - p.x*q.x + a)/(p.y + q.y) - pxqx := g2.Ext2.Mul(&p.P.X, &q.P.X) - pxplusqx := g2.Ext2.Add(&p.P.X, &q.P.X) - num := g2.Ext2.Mul(pxplusqx, pxplusqx) - num = g2.Ext2.Sub(num, pxqx) - denum := g2.Ext2.Add(&p.P.Y, &q.P.Y) - // if p.y + q.y = 0, assign dummy 1 to denum and continue - selector3 := g2.Ext2.IsZero(denum) - denum = g2.Ext2.Select(selector3, g2.Ext2.One(), denum) - λ := g2.Ext2.DivUnchecked(num, denum) // we already know that denum won't be zero - - // x = λ^2 - p.x - q.x - xr := g2.Ext2.Mul(λ, λ) - xr = g2.Ext2.Sub(xr, pxplusqx) - - // y = λ(p.x - xr) - p.y - yr := g2.Ext2.Sub(&p.P.X, xr) - yr = g2.Ext2.Mul(yr, λ) - yr = g2.Ext2.Sub(yr, &p.P.Y) - result := &G2Affine{ - P: g2AffP{ - X: *xr, - Y: *yr, - }, - } - - zero := g2.Ext2.Zero() - // if p=(0,0) return q - resultX := *g2.Select(selector1, &q.P.X, &result.P.X) - resultY := *g2.Select(selector1, &q.P.Y, &result.P.Y) - // if q=(0,0) return p - resultX = *g2.Select(selector2, &p.P.X, &resultX) - resultY = *g2.Select(selector2, &p.P.Y, &resultY) - // if p.y + q.y = 0, return (0, 0) - resultX = *g2.Select(selector3, zero, &resultX) - resultY = *g2.Select(selector3, zero, &resultY) - - return &G2Affine{ - P: g2AffP{ - X: resultX, - Y: resultY, - }, - } -} - func (g2 G2) neg(p *G2Affine) *G2Affine { xr := &p.P.X yr := g2.Ext2.Neg(&p.P.Y) diff --git a/std/algebra/emulated/sw_bls12381/g2_test.go b/std/algebra/emulated/sw_bls12381/g2_test.go index d5b3f32484..85b4d11a84 100644 --- a/std/algebra/emulated/sw_bls12381/g2_test.go +++ b/std/algebra/emulated/sw_bls12381/g2_test.go @@ -100,8 +100,11 @@ type addG2UnifiedCircuit struct { } func (c *addG2UnifiedCircuit) Define(api frontend.API) error { - g2 := NewG2(api) - res := g2.addUnified(&c.In1, &c.In2) + g2, err := NewG2(api) + if err != nil { + return err + } + res := g2.AddUnified(&c.In1, &c.In2) g2.AssertIsEqual(res, &c.Res) return nil } diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2.go b/std/algebra/emulated/sw_bls12381/hash_to_g2.go index 55d8d9a52f..bd6ba18019 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2.go @@ -24,7 +24,10 @@ func HashToG2(api frontend.API, msg []uints.U8, dst []byte) (*G2Affine, error) { } ext2 := fields_bls12381.NewExt2(api) mapper := newMapper(api, ext2, fp) - g2 := NewG2(api) + g2, err := NewG2(api) + if err != nil { + return nil, err + } // Steps: // 1. u = hash_to_field(msg, 2) @@ -51,7 +54,7 @@ func HashToG2(api frontend.API, msg []uints.U8, dst []byte) (*G2Affine, error) { Q0 = mapper.isogeny(&Q0.P.X, &Q0.P.Y) Q1 = mapper.isogeny(&Q1.P.X, &Q1.P.Y) - R := g2.addUnified(Q0, Q1) + R := g2.AddUnified(Q0, Q1) return clearCofactor(g2, fp, R), nil } @@ -210,7 +213,7 @@ func (m sswuMapper) sgn0(x *fields_bls12381.E2) frontend.Variable { func (m sswuMapper) sqrtRatio(u, v *fields_bls12381.E2) (frontend.Variable, *fields_bls12381.E2) { // Steps // 1. extract the base values of u, v, then compute G2SqrtRatio with gnark-crypto - x, err := m.fp.NewHint(GetHints()[0], 3, &u.A0, &u.A1, &v.A0, &v.A1) + x, err := m.fp.NewHint(sqrtRatioHint, 3, &u.A0, &u.A1, &v.A0, &v.A1) if err != nil { panic("failed to calculate sqrtRatio with gnark-crypto " + err.Error()) } @@ -359,11 +362,11 @@ func clearCofactor(g2 *G2, fp *emulated.Field[emparams.BLS12381Fp], p *G2Affine) // 5. t3 = t3 - t2 t3 = g2.sub(t3, t2) // 6. t2 = t1 + t2 - t2 = g2.addUnified(t1, t2) + t2 = g2.AddUnified(t1, t2) // 7. t2 = c1 * t2 t2 = g2.scalarMulBySeed(t2) // 8. t3 = t3 + t2 - t3 = g2.addUnified(t3, t2) + t3 = g2.AddUnified(t3, t2) // 9. t3 = t3 - t1 t3 = g2.sub(t3, t1) // 10. Q = t3 - P diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go b/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go index 683106b5b6..1675e2141f 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go @@ -89,7 +89,10 @@ func (c *mapToCurveCircuit) Define(api frontend.API) error { e := fields_bls12381.E2{A0: *ele1, A1: *ele2} affine := mapper.mapToCurve(e) - g2 := NewG2(api) + g2, err := NewG2(api) + if err != nil { + return err + } g2.AssertIsEqual(affine, &c.Res) return nil @@ -126,7 +129,10 @@ type clearCofactorCircuit struct { } func (c *clearCofactorCircuit) Define(api frontend.API) error { - g2 := NewG2(api) + g2, err := NewG2(api) + if err != nil { + return err + } fp, _ := emulated.NewField[emulated.BLS12381Fp](api) res := clearCofactor(g2, fp, &c.In) g2.AssertIsEqual(res, &c.Res) @@ -164,7 +170,10 @@ func (c *hashToG2Circuit) Define(api frontend.API) error { return e } - g2 := NewG2(api) + g2, err := NewG2(api) + if err != nil { + return err + } g2.AssertIsEqual(res, &c.Res) return nil } diff --git a/std/algebra/emulated/sw_bls12381/hints.go b/std/algebra/emulated/sw_bls12381/hints.go index 7d4804aafd..a2839702f6 100644 --- a/std/algebra/emulated/sw_bls12381/hints.go +++ b/std/algebra/emulated/sw_bls12381/hints.go @@ -39,16 +39,20 @@ func sqrtRatioHint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { return fmt.Errorf("expecting 3 outputs") } - var z0, z1, u0, u1, v0, v1 fp.Element - u0.SetBigInt(inputs[0]) - u1.SetBigInt(inputs[1]) - v0.SetBigInt(inputs[2]) - v1.SetBigInt(inputs[3]) - - // b := bls12381.G2SqrtRatio(&z0, &z1, &u0, &u1, &v0, &v1) - // outputs[0].SetUint64(b) - z0.BigInt(outputs[1]) - z1.BigInt(outputs[2]) + var z, u, v bls12381.E2 + u.A0.SetBigInt(inputs[0]) + u.A1.SetBigInt(inputs[1]) + v.A0.SetBigInt(inputs[2]) + v.A1.SetBigInt(inputs[3]) + + res := hash_to_curve.G2SqrtRatio(&z, &u, &v) + if res != 0 { + res = 1 + } + + outputs[0].SetUint64(res) + z.A0.BigInt(outputs[1]) + z.A1.BigInt(outputs[2]) return nil }) } From b6dccf12b5bc4058efe73c216556f7fa150e5d98 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 7 Apr 2025 20:59:31 +0000 Subject: [PATCH 052/105] chore: rename g2sqrtratiohint --- .../emulated/sw_bls12381/hash_to_g2.go | 2 +- std/algebra/emulated/sw_bls12381/hints.go | 56 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2.go b/std/algebra/emulated/sw_bls12381/hash_to_g2.go index bd6ba18019..d5a9e991a5 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2.go @@ -213,7 +213,7 @@ func (m sswuMapper) sgn0(x *fields_bls12381.E2) frontend.Variable { func (m sswuMapper) sqrtRatio(u, v *fields_bls12381.E2) (frontend.Variable, *fields_bls12381.E2) { // Steps // 1. extract the base values of u, v, then compute G2SqrtRatio with gnark-crypto - x, err := m.fp.NewHint(sqrtRatioHint, 3, &u.A0, &u.A1, &v.A0, &v.A1) + x, err := m.fp.NewHint(g2SqrtRatioHint, 3, &u.A0, &u.A1, &v.A0, &v.A1) if err != nil { panic("failed to calculate sqrtRatio with gnark-crypto " + err.Error()) } diff --git a/std/algebra/emulated/sw_bls12381/hints.go b/std/algebra/emulated/sw_bls12381/hints.go index a2839702f6..928ea93c7e 100644 --- a/std/algebra/emulated/sw_bls12381/hints.go +++ b/std/algebra/emulated/sw_bls12381/hints.go @@ -26,37 +26,10 @@ func GetHints() []solver.Hint { decomposeScalarG1Subscalars, decomposeScalarG1Signs, g1SqrtRatioHint, - sqrtRatioHint, + g2SqrtRatioHint, } } -func sqrtRatioHint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { - return emulated.UnwrapHint(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { - if len(inputs) != 4 { - return fmt.Errorf("expecting 4 inputs") - } - if len(outputs) != 3 { - return fmt.Errorf("expecting 3 outputs") - } - - var z, u, v bls12381.E2 - u.A0.SetBigInt(inputs[0]) - u.A1.SetBigInt(inputs[1]) - v.A0.SetBigInt(inputs[2]) - v.A1.SetBigInt(inputs[3]) - - res := hash_to_curve.G2SqrtRatio(&z, &u, &v) - if res != 0 { - res = 1 - } - - outputs[0].SetUint64(res) - z.A0.BigInt(outputs[1]) - z.A1.BigInt(outputs[2]) - return nil - }) -} - func finalExpHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { // This is inspired from https://eprint.iacr.org/2024/640.pdf // and based on a personal communication with the author Andrija Novakovic. @@ -385,3 +358,30 @@ func g1SqrtRatioHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) return nil }) } + +func g2SqrtRatioHint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error { + return emulated.UnwrapHint(inputs, outputs, func(field *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 4 { + return fmt.Errorf("expecting 4 inputs") + } + if len(outputs) != 3 { + return fmt.Errorf("expecting 3 outputs") + } + + var z, u, v bls12381.E2 + u.A0.SetBigInt(inputs[0]) + u.A1.SetBigInt(inputs[1]) + v.A0.SetBigInt(inputs[2]) + v.A1.SetBigInt(inputs[3]) + + isQNr := hash_to_curve.G2SqrtRatio(&z, &u, &v) + if isQNr != 0 { + isQNr = 1 + } + + outputs[0].SetUint64(isQNr) + z.A0.BigInt(outputs[1]) + z.A1.BigInt(outputs[2]) + return nil + }) +} From 2f1273d39029229fc9acfde8b797a0410eefa4ce Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 7 Apr 2025 21:02:06 +0000 Subject: [PATCH 053/105] chore: remove unused const --- std/algebra/emulated/sw_bls12381/hash_to_g2.go | 1 - 1 file changed, 1 deletion(-) diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2.go b/std/algebra/emulated/sw_bls12381/hash_to_g2.go index d5a9e991a5..38b0dbcbd0 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2.go @@ -13,7 +13,6 @@ import ( ) const ( - security_level = 128 len_per_base_element = 64 ) From e558d23c818c3ff583a1e66c03c1bc85e0f1276c Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Tue, 8 Apr 2025 17:11:15 +0100 Subject: [PATCH 054/105] fix: address PR review --- std/algebra/emulated/fields_bw6761/e6.go | 20 ++++++ std/algebra/emulated/sw_bls12381/g2.go | 10 +-- std/algebra/emulated/sw_bls12381/g2_test.go | 13 ++-- std/algebra/emulated/sw_bls12381/pairing.go | 4 -- .../emulated/sw_bls12381/pairing_test.go | 2 +- std/algebra/emulated/sw_bw6761/pairing.go | 4 ++ std/algebra/interfaces.go | 3 + std/algebra/native/sw_bls12377/pairing2.go | 36 ++++++++++ std/algebra/native/sw_bls24315/pairing2.go | 69 +++++++++++++++++++ std/evmprecompiles/08-bnpairing.go | 3 + std/evmprecompiles/15-blspairing.go | 4 ++ 11 files changed, 153 insertions(+), 15 deletions(-) diff --git a/std/algebra/emulated/fields_bw6761/e6.go b/std/algebra/emulated/fields_bw6761/e6.go index 125c902d26..0c3a81bdc2 100644 --- a/std/algebra/emulated/fields_bw6761/e6.go +++ b/std/algebra/emulated/fields_bw6761/e6.go @@ -1109,6 +1109,26 @@ func (e Ext6) AssertIsEqual(a, b *E6) { } +func (e Ext6) IsEqual(x, y *E6) frontend.Variable { + diff0 := e.fp.Sub(&x.A0, &y.A0) + diff1 := e.fp.Sub(&x.A1, &y.A1) + diff2 := e.fp.Sub(&x.A2, &y.A2) + diff3 := e.fp.Sub(&x.A3, &y.A3) + diff4 := e.fp.Sub(&x.A4, &y.A4) + diff5 := e.fp.Sub(&x.A5, &y.A5) + isZero0 := e.fp.IsZero(diff0) + isZero1 := e.fp.IsZero(diff1) + isZero2 := e.fp.IsZero(diff2) + isZero3 := e.fp.IsZero(diff3) + isZero4 := e.fp.IsZero(diff4) + isZero5 := e.fp.IsZero(diff5) + + return e.api.And( + e.api.And(e.api.And(isZero0, isZero1), e.api.And(isZero2, isZero3)), + e.api.And(isZero4, isZero5), + ) +} + func (e Ext6) Copy(x *E6) *E6 { return &E6{ A0: x.A0, diff --git a/std/algebra/emulated/sw_bls12381/g2.go b/std/algebra/emulated/sw_bls12381/g2.go index 59ec967e9e..eb97b937f7 100644 --- a/std/algebra/emulated/sw_bls12381/g2.go +++ b/std/algebra/emulated/sw_bls12381/g2.go @@ -416,10 +416,12 @@ func (g2 *G2) computeTwistEquation(Q *G2Affine) (left, right *fields_bls12381.E2 b := g2.Ext2.Select(selector, g2.Ext2.Zero(), &bTwist) left = g2.Ext2.Square(&Q.P.Y) - // TODO: use Eval for right - right = g2.Ext2.Square(&Q.P.X) - right = g2.Ext2.Mul(right, &Q.P.X) - right = g2.Ext2.Add(right, b) + mone := g2.fp.NewElement(-1) + right = &fields_bls12381.E2{ + A0: *g2.fp.Eval([][]*baseEl{{&Q.P.X.A0, &Q.P.X.A0, &Q.P.X.A0}, {mone, &Q.P.X.A0, &Q.P.X.A1, &Q.P.X.A1}, {&b.A0}}, []int{1, 3, 1}), + A1: *g2.fp.Eval([][]*baseEl{{&Q.P.X.A1, &Q.P.X.A0, &Q.P.X.A0}, {mone, &Q.P.X.A1, &Q.P.X.A1, &Q.P.X.A1}, {&b.A1}}, []int{3, 1, 1}), + } + return left, right } diff --git a/std/algebra/emulated/sw_bls12381/g2_test.go b/std/algebra/emulated/sw_bls12381/g2_test.go index 2b9d42f794..7356433325 100644 --- a/std/algebra/emulated/sw_bls12381/g2_test.go +++ b/std/algebra/emulated/sw_bls12381/g2_test.go @@ -1,6 +1,7 @@ package sw_bls12381 import ( + "fmt" "math/big" "testing" @@ -21,7 +22,7 @@ type mulG2Circuit struct { func (c *mulG2Circuit) Define(api frontend.API) error { g2, err := NewG2(api) if err != nil { - panic(err) + return fmt.Errorf("new G2 struct: %w", err) } res1 := g2.scalarMulGLV(&c.In, &c.S) res2 := g2.scalarMulGeneric(&c.In, &c.S) @@ -57,7 +58,7 @@ type addG2Circuit struct { func (c *addG2Circuit) Define(api frontend.API) error { g2, err := NewG2(api) if err != nil { - panic(err) + return fmt.Errorf("new G2 struct: %w", err) } res := g2.add(&c.In1, &c.In2) g2.AssertIsEqual(res, &c.Res) @@ -87,7 +88,7 @@ type doubleG2Circuit struct { func (c *doubleG2Circuit) Define(api frontend.API) error { g2, err := NewG2(api) if err != nil { - panic(err) + return fmt.Errorf("new G2 struct: %w", err) } res := g2.double(&c.In1) g2.AssertIsEqual(res, &c.Res) @@ -118,7 +119,7 @@ type doubleAndAddG2Circuit struct { func (c *doubleAndAddG2Circuit) Define(api frontend.API) error { g2, err := NewG2(api) if err != nil { - panic(err) + return fmt.Errorf("new G2 struct: %w", err) } res := g2.doubleAndAdd(&c.In1, &c.In2) g2.AssertIsEqual(res, &c.Res) @@ -149,7 +150,7 @@ type scalarMulG2BySeedCircuit struct { func (c *scalarMulG2BySeedCircuit) Define(api frontend.API) error { g2, err := NewG2(api) if err != nil { - panic(err) + return fmt.Errorf("new G2 struct: %w", err) } res := g2.scalarMulBySeed(&c.In1) g2.AssertIsEqual(res, &c.Res) @@ -179,7 +180,7 @@ type MultiScalarMulTest struct { func (c *MultiScalarMulTest) Define(api frontend.API) error { g2, err := NewG2(api) if err != nil { - panic(err) + return fmt.Errorf("new G2 struct: %w", err) } ps := make([]*G2Affine, len(c.Points)) for i := range c.Points { diff --git a/std/algebra/emulated/sw_bls12381/pairing.go b/std/algebra/emulated/sw_bls12381/pairing.go index dacffecf44..8ebb8386e2 100644 --- a/std/algebra/emulated/sw_bls12381/pairing.go +++ b/std/algebra/emulated/sw_bls12381/pairing.go @@ -301,10 +301,6 @@ func (pr Pairing) MuxGt(sel frontend.Variable, inputs ...*GTEl) *GTEl { return &ret } -func (pr Pairing) AssertIsOnCurve(P *G1Affine) { - pr.curve.AssertIsOnCurve(P) -} - // IsOnCurve returns a boolean indicating if the G1 point is in the curve. func (pr Pairing) IsOnCurve(P *G1Affine) frontend.Variable { left, right := pr.g1.computeCurveEquation(P) diff --git a/std/algebra/emulated/sw_bls12381/pairing_test.go b/std/algebra/emulated/sw_bls12381/pairing_test.go index a246d1aec1..6c69e12ac3 100644 --- a/std/algebra/emulated/sw_bls12381/pairing_test.go +++ b/std/algebra/emulated/sw_bls12381/pairing_test.go @@ -315,7 +315,7 @@ type MuxesCircuits struct { func (c *MuxesCircuits) Define(api frontend.API) error { g2api, err := NewG2(api) if err != nil { - panic(err) + return fmt.Errorf("new G2 struct: %w", err) } pairing, err := NewPairing(api) if err != nil { diff --git a/std/algebra/emulated/sw_bw6761/pairing.go b/std/algebra/emulated/sw_bw6761/pairing.go index e4c8e3ecdd..7baaeb32d3 100644 --- a/std/algebra/emulated/sw_bw6761/pairing.go +++ b/std/algebra/emulated/sw_bw6761/pairing.go @@ -223,6 +223,10 @@ func (pr Pairing) PairingCheck(P []*G1Affine, Q []*G2Affine) error { return nil } +func (pr Pairing) IsEqual(x, y *GTEl) frontend.Variable { + return pr.Ext6.IsEqual(x, y) +} + func (pr Pairing) AssertIsEqual(x, y *GTEl) { pr.Ext6.AssertIsEqual(x, y) } diff --git a/std/algebra/interfaces.go b/std/algebra/interfaces.go index 4608002475..555cb03ff9 100644 --- a/std/algebra/interfaces.go +++ b/std/algebra/interfaces.go @@ -115,4 +115,7 @@ type Pairing[G1El G1ElementT, G2El G2ElementT, GtEl GtElementT] interface { // most efficient for power of two lengths of the inputs, but works for any // number of inputs. MuxGt(sel frontend.Variable, inputs ...*GtEl) *GtEl + + // IsEqual checks if the two inputs are equal. It returns a frontend.Variable. + IsEqual(a, b *GtEl) frontend.Variable } diff --git a/std/algebra/native/sw_bls12377/pairing2.go b/std/algebra/native/sw_bls12377/pairing2.go index 122c61ff60..50f2366dca 100644 --- a/std/algebra/native/sw_bls12377/pairing2.go +++ b/std/algebra/native/sw_bls12377/pairing2.go @@ -106,6 +106,42 @@ func (c *Curve) AssertIsEqual(P, Q *G1Affine) { P.AssertIsEqual(c.api, *Q) } +func (c *Pairing) IsEqual(x, y *GT) frontend.Variable { + diff0 := c.api.Sub(&x.C0.B0.A0, &y.C0.B0.A0) + diff1 := c.api.Sub(&x.C0.B0.A1, &y.C0.B0.A1) + diff2 := c.api.Sub(&x.C0.B0.A0, &y.C0.B0.A0) + diff3 := c.api.Sub(&x.C0.B1.A1, &y.C0.B1.A1) + diff4 := c.api.Sub(&x.C0.B1.A0, &y.C0.B1.A0) + diff5 := c.api.Sub(&x.C0.B1.A1, &y.C0.B1.A1) + diff6 := c.api.Sub(&x.C1.B0.A0, &y.C1.B0.A0) + diff7 := c.api.Sub(&x.C1.B0.A1, &y.C1.B0.A1) + diff8 := c.api.Sub(&x.C1.B0.A0, &y.C1.B0.A0) + diff9 := c.api.Sub(&x.C1.B1.A1, &y.C1.B1.A1) + diff10 := c.api.Sub(&x.C1.B1.A0, &y.C1.B1.A0) + diff11 := c.api.Sub(&x.C1.B1.A1, &y.C1.B1.A1) + + isZero0 := c.api.IsZero(diff0) + isZero1 := c.api.IsZero(diff1) + isZero2 := c.api.IsZero(diff2) + isZero3 := c.api.IsZero(diff3) + isZero4 := c.api.IsZero(diff4) + isZero5 := c.api.IsZero(diff5) + isZero6 := c.api.IsZero(diff6) + isZero7 := c.api.IsZero(diff7) + isZero8 := c.api.IsZero(diff8) + isZero9 := c.api.IsZero(diff9) + isZero10 := c.api.IsZero(diff10) + isZero11 := c.api.IsZero(diff11) + + return c.api.And( + c.api.And( + c.api.And(c.api.And(isZero0, isZero1), c.api.And(isZero2, isZero3)), + c.api.And(c.api.And(isZero4, isZero5), c.api.And(isZero6, isZero7)), + ), + c.api.And(c.api.And(isZero8, isZero9), c.api.And(isZero10, isZero11)), + ) +} + // Neg negates P and returns the result. Does not modify P. func (c *Curve) Neg(P *G1Affine) *G1Affine { res := &G1Affine{ diff --git a/std/algebra/native/sw_bls24315/pairing2.go b/std/algebra/native/sw_bls24315/pairing2.go index 7751076c92..bdc3d21fa5 100644 --- a/std/algebra/native/sw_bls24315/pairing2.go +++ b/std/algebra/native/sw_bls24315/pairing2.go @@ -255,6 +255,75 @@ func NewPairing(api frontend.API) *Pairing { } } +func (c *Pairing) IsEqual(x, y *GT) frontend.Variable { + diff0 := c.api.Sub(&x.D0.C0.B0.A0, &y.D0.C0.B0.A0) + diff1 := c.api.Sub(&x.D0.C0.B0.A1, &y.D0.C0.B0.A1) + diff2 := c.api.Sub(&x.D0.C0.B0.A0, &y.D0.C0.B0.A0) + diff3 := c.api.Sub(&x.D0.C0.B1.A1, &y.D0.C0.B1.A1) + diff4 := c.api.Sub(&x.D0.C0.B1.A0, &y.D0.C0.B1.A0) + diff5 := c.api.Sub(&x.D0.C0.B1.A1, &y.D0.C0.B1.A1) + diff6 := c.api.Sub(&x.D0.C1.B0.A0, &y.D0.C1.B0.A0) + diff7 := c.api.Sub(&x.D0.C1.B0.A1, &y.D0.C1.B0.A1) + diff8 := c.api.Sub(&x.D0.C1.B0.A0, &y.D0.C1.B0.A0) + diff9 := c.api.Sub(&x.D0.C1.B1.A1, &y.D0.C1.B1.A1) + diff10 := c.api.Sub(&x.D0.C1.B1.A0, &y.D0.C1.B1.A0) + diff11 := c.api.Sub(&x.D0.C1.B1.A1, &y.D0.C1.B1.A1) + diff12 := c.api.Sub(&x.D1.C0.B0.A0, &y.D1.C0.B0.A0) + diff13 := c.api.Sub(&x.D1.C0.B0.A1, &y.D1.C0.B0.A1) + diff14 := c.api.Sub(&x.D1.C0.B0.A0, &y.D1.C0.B0.A0) + diff15 := c.api.Sub(&x.D1.C0.B1.A1, &y.D1.C0.B1.A1) + diff16 := c.api.Sub(&x.D1.C0.B1.A0, &y.D1.C0.B1.A0) + diff17 := c.api.Sub(&x.D1.C0.B1.A1, &y.D1.C0.B1.A1) + diff18 := c.api.Sub(&x.D1.C1.B0.A0, &y.D1.C1.B0.A0) + diff19 := c.api.Sub(&x.D1.C1.B0.A1, &y.D1.C1.B0.A1) + diff20 := c.api.Sub(&x.D1.C1.B0.A0, &y.D1.C1.B0.A0) + diff21 := c.api.Sub(&x.D1.C1.B1.A1, &y.D1.C1.B1.A1) + diff22 := c.api.Sub(&x.D1.C1.B1.A0, &y.D1.C1.B1.A0) + diff23 := c.api.Sub(&x.D1.C1.B1.A1, &y.D1.C1.B1.A1) + + isZero0 := c.api.IsZero(diff0) + isZero1 := c.api.IsZero(diff1) + isZero2 := c.api.IsZero(diff2) + isZero3 := c.api.IsZero(diff3) + isZero4 := c.api.IsZero(diff4) + isZero5 := c.api.IsZero(diff5) + isZero6 := c.api.IsZero(diff6) + isZero7 := c.api.IsZero(diff7) + isZero8 := c.api.IsZero(diff8) + isZero9 := c.api.IsZero(diff9) + isZero10 := c.api.IsZero(diff10) + isZero11 := c.api.IsZero(diff11) + isZero12 := c.api.IsZero(diff12) + isZero13 := c.api.IsZero(diff13) + isZero14 := c.api.IsZero(diff14) + isZero15 := c.api.IsZero(diff15) + isZero16 := c.api.IsZero(diff16) + isZero17 := c.api.IsZero(diff17) + isZero18 := c.api.IsZero(diff18) + isZero19 := c.api.IsZero(diff19) + isZero20 := c.api.IsZero(diff20) + isZero21 := c.api.IsZero(diff21) + isZero22 := c.api.IsZero(diff22) + isZero23 := c.api.IsZero(diff23) + + return c.api.And( + c.api.And( + c.api.And( + c.api.And(c.api.And(isZero0, isZero1), c.api.And(isZero2, isZero3)), + c.api.And(c.api.And(isZero4, isZero5), c.api.And(isZero6, isZero7)), + ), + c.api.And( + c.api.And(c.api.And(isZero8, isZero9), c.api.And(isZero10, isZero11)), + c.api.And(c.api.And(isZero12, isZero13), c.api.And(isZero14, isZero15)), + ), + ), + c.api.And( + c.api.And(c.api.And(isZero16, isZero17), c.api.And(isZero18, isZero19)), + c.api.And(c.api.And(isZero20, isZero21), c.api.And(isZero22, isZero23)), + ), + ) +} + // MillerLoop computes the Miller loop between the pairs of inputs. It doesn't // modify the inputs. It returns an error if there is a mismatch between the // lengths of the inputs. diff --git a/std/evmprecompiles/08-bnpairing.go b/std/evmprecompiles/08-bnpairing.go index c93c38d889..ec635c9960 100644 --- a/std/evmprecompiles/08-bnpairing.go +++ b/std/evmprecompiles/08-bnpairing.go @@ -41,6 +41,9 @@ func ECPair(api frontend.API, P []*sw_bn254.G1Affine, Q []*sw_bn254.G2Affine) { panic(err) } // 1- Check that Pᵢ are on G1 (done in the zkEVM ⚠️) + // N.B.: BN254 has a prime order so G1 membership boils down to curve + // membership only, which is checked in the zkEVM. + // // 2- Check that Qᵢ are on G2 (done in `computeLines` in `MillerLoopAndMul` and `MillerLoopAndFinalExpCheck`) // 3- Check that ∏ᵢ e(Pᵢ, Qᵢ) == 1 diff --git a/std/evmprecompiles/15-blspairing.go b/std/evmprecompiles/15-blspairing.go index ba007f2c6c..c1337815c7 100644 --- a/std/evmprecompiles/15-blspairing.go +++ b/std/evmprecompiles/15-blspairing.go @@ -43,6 +43,10 @@ func ECPairBLS(api frontend.API, P []*sw_bls12381.G1Affine, Q []*sw_bls12381.G2A for i := 0; i < n; i++ { // 1- Check that Pᵢ are on G1 pair.AssertIsOnG1(P[i]) + // N.B.: curve membership cannot be done in the zkEVM for BLS12-381 + // because the prime occupies 48 bytes and the zkEVM modular arithmetic + // module only supports 32 byte operations. + // // 2- Check that Qᵢ are on G2 (done in `computeLines` in `MillerLoopAndMul` and `MillerLoopAndFinalExpCheck`) } From b2c738b9422029fe4b76e966e564f9eecfd12f3d Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 08:01:38 +0000 Subject: [PATCH 055/105] XXX: work refactor hash to g2 --- .../emulated/sw_bls12381/hash_to_g2.go | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2.go b/std/algebra/emulated/sw_bls12381/hash_to_g2.go index 38b0dbcbd0..186d4883f7 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2.go @@ -4,6 +4,8 @@ import ( "math/big" "slices" + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/algebra/emulated/fields_bls12381" "github.com/consensys/gnark/std/hash/tofield" @@ -373,3 +375,112 @@ func clearCofactor(g2 *G2, fp *emulated.Field[emparams.BLS12381Fp], p *G2Affine) // 11. return Q return Q } + +func (g2 *G2) evalFixedPolynomial(monic bool, coefficients []bls12381.E2, x *fields_bls12381.E2) *fields_bls12381.E2 { + emuCoefficients := make([]*fields_bls12381.E2, len(coefficients)) + for i := 0; i < len(coefficients); i++ { + emuCoefficients[i] = &fields_bls12381.E2{ + A0: emulated.ValueOf[emparams.BLS12381Fp](coefficients[i].A0), + A1: emulated.ValueOf[emparams.BLS12381Fp](coefficients[i].A1), + } + } + var res *fields_bls12381.E2 + if monic { + res = g2.Add(emuCoefficients[0], x) + } else { + res = emuCoefficients[len(emuCoefficients)-1] + } + + for i := len(emuCoefficients) - 2; i >= 0; i-- { + res = g2.Mul(res, x) + res = g2.Add(res, emuCoefficients[i]) + } + + return res +} + +func (g2 *G2) isogeny(p *G2Affine) *G2Affine { + isogenyMap := hash_to_curve.G2IsogenyMap() + ydenom := g2.evalFixedPolynomial(true, isogenyMap[3], &p.P.X) + xdenom := g2.evalFixedPolynomial(true, isogenyMap[1], &p.P.X) + y := g2.evalFixedPolynomial(false, isogenyMap[2], &p.P.X) + y = g2.Mul(y, &p.P.Y) + x := g2.evalFixedPolynomial(false, isogenyMap[0], &p.P.X) + x = g2.DivUnchecked(x, xdenom) + y = g2.DivUnchecked(y, ydenom) + return &G2Affine{P: g2AffP{X: *x, Y: *y}} +} + +func (g2 *G2) sgn0(x *fields_bls12381.E2) frontend.Variable { + x0Bits := g2.fp.ToBitsCanonical(&x.A0) + x1Bits := g2.fp.ToBitsCanonical(&x.A1) + sign0 := x0Bits[0] + zero0 := g2.api.IsZero(sign0) + sign1 := x1Bits[0] + tv := g2.api.And(zero0, sign1) + s := g2.api.Or(sign0, tv) + return s +} + +func (g2 *G2) MapToCurve2(u *fields_bls12381.E2) *G2Affine { + // SSWU Steps: + // 1. tv1 = u^2 + tv1 := g2.Ext2.Square(u) + // 2. tv1 = Z * tv1 + tv1 = g2.Ext2.Mul(m.Z, tv1) + // 3. tv2 = tv1^2 + tv2 := g2.Ext2.Square(tv1) + // 4. tv2 = tv2 + tv1 + tv2 = g2.Ext2.Add(tv2, tv1) + // 5. tv3 = tv2 + 1 + tv3 := g2.Ext2.Add(tv2, g2.ext2.One()) + // 6. tv3 = B * tv3 + tv3 = g2.Ext2.Mul(&m.B, tv3) + // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) + s1 := g2.Ext2.IsZero(tv2) + tv4 := g2.Ext2.Select(s1, &m.Z, m.ext2.Neg(tv2)) + // 8. tv4 = A * tv4 + tv4 = g2.Ext2.Mul(&m.A, tv4) + // 9. tv2 = tv3^2 + tv2 = g2.Ext2.Square(tv3) + // 10. tv6 = tv4^2 + tv6 := g2.Ext2.Square(tv4) + // 11. tv5 = A * tv6 + tv5 := g2.Ext2.Mul(&m.A, tv6) + // 12. tv2 = tv2 + tv5 + tv2 = g2.Ext2.Add(tv2, tv5) + // 13. tv2 = tv2 * tv3 + tv2 = g2.Ext2.Mul(tv2, tv3) + // 14. tv6 = tv6 * tv4 + tv6 = g2.Ext2.Mul(tv6, tv4) + // 15. tv5 = B * tv6 + tv5 = g2.Ext2.Mul(&m.B, tv6) + // 16. tv2 = tv2 + tv5 + tv2 = g2.Ext2.Add(tv2, tv5) + // 17. x = tv1 * tv3 + x := g2.Ext2.Mul(tv1, tv3) + // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) + isGx1Square, y1 := m.sqrtRatio(tv2, tv6) + // 19. y = tv1 * u + y := g2.Ext2.Mul(tv1, &u) + // 20. y = y * y1 + y = g2.Ext2.Mul(y, y1) + // 21. x = CMOV(x, tv3, is_gx1_square) + x = g2.Ext2.Select(isGx1Square, tv3, x) + // 22. y = CMOV(y, y1, is_gx1_square) + y = g2.Ext2.Select(isGx1Square, y1, y) + // 23. e1 = sgn0(u) == sgn0(y) + sgn0U := g2.sgn0(&u) + sgn0Y := g2.sgn0(y) + diff := g2.api.Sub(sgn0U, sgn0Y) + e1 := g2.api.IsZero(diff) + // 24. y = CMOV(-y, y, e1) + yNeg := g2.Ext2.Neg(y) + y = g2.Ext2.Select(e1, y, yNeg) + // 25. x = x / tv4 + x = g2.Ext2.DivUnchecked(x, tv4) + // 26. return (x, y) + return &G2Affine{ + P: g2AffP{X: *x, Y: *y}, + } +} From c1d02d56d93212937b9847418bb0a25bb7daabd0 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 08:02:17 +0000 Subject: [PATCH 056/105] XXX: work refactor --- std/algebra/emulated/sw_bls12381/hash_to_g2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2.go b/std/algebra/emulated/sw_bls12381/hash_to_g2.go index 186d4883f7..a2955d45e6 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2.go @@ -433,7 +433,7 @@ func (g2 *G2) MapToCurve2(u *fields_bls12381.E2) *G2Affine { // 4. tv2 = tv2 + tv1 tv2 = g2.Ext2.Add(tv2, tv1) // 5. tv3 = tv2 + 1 - tv3 := g2.Ext2.Add(tv2, g2.ext2.One()) + tv3 := g2.Ext2.Add(tv2, g2.Ext2.One()) // 6. tv3 = B * tv3 tv3 = g2.Ext2.Mul(&m.B, tv3) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) From e6f9b6d521465248f57eb7171cba3d663aaa8d77 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 10:04:51 +0000 Subject: [PATCH 057/105] refactor: do not need error --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 2db5437478..0008099956 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -9,7 +9,7 @@ import ( "github.com/consensys/gnark/std/math/emulated" ) -func (g1 *G1) evalFixedPolynomial(monic bool, coefficients []fp.Element, x *baseEl) (*baseEl, error) { +func (g1 *G1) evalFixedPolynomial(monic bool, coefficients []fp.Element, x *baseEl) *baseEl { emuCoefficients := make([]*baseEl, len(coefficients)) for i := range coefficients { emulatedCoefficient := emulated.ValueOf[emulated.BLS12381Fp](coefficients[i]) @@ -26,29 +26,17 @@ func (g1 *G1) evalFixedPolynomial(monic bool, coefficients []fp.Element, x *base res = g1.curveF.Mul(res, x) res = g1.curveF.Add(res, emuCoefficients[i]) } - return res, nil + return res } func (g1 *G1) isogeny(p *G1Affine) (*G1Affine, error) { isogenyMap := hash_to_curve.G1IsogenyMap() - ydenom, err := g1.evalFixedPolynomial(true, isogenyMap[3], &p.X) - if err != nil { - return nil, fmt.Errorf("y denom: %w", err) - } - xdenom, err := g1.evalFixedPolynomial(true, isogenyMap[1], &p.X) - if err != nil { - return nil, fmt.Errorf("x denom: %w", err) - } - y, err := g1.evalFixedPolynomial(false, isogenyMap[2], &p.X) - if err != nil { - return nil, fmt.Errorf("y num: %w", err) - } + ydenom := g1.evalFixedPolynomial(true, isogenyMap[3], &p.X) + xdenom := g1.evalFixedPolynomial(true, isogenyMap[1], &p.X) + y := g1.evalFixedPolynomial(false, isogenyMap[2], &p.X) y = g1.curveF.Mul(y, &p.Y) - x, err := g1.evalFixedPolynomial(false, isogenyMap[0], &p.X) - if err != nil { - return nil, fmt.Errorf("x num: %w", err) - } + x := g1.evalFixedPolynomial(false, isogenyMap[0], &p.X) x = g1.curveF.Div(x, xdenom) y = g1.curveF.Div(y, ydenom) return &G1Affine{X: *x, Y: *y}, nil From 79bc1eec7bfe439b1fdc42342b1da5138ce7e75c Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 11:36:49 +0000 Subject: [PATCH 058/105] chore: go.mod update --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 8202427261..b86248ca81 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/blang/semver/v4 v4.0.0 github.com/consensys/bavard v0.1.31-0.20250406004941-2db259e4b582 github.com/consensys/compress v0.2.5 - github.com/consensys/gnark-crypto v0.17.1-0.20250407141002-d4f06e01a637 + github.com/consensys/gnark-crypto v0.17.1-0.20250409113232-424096f3f868 github.com/fxamacker/cbor/v2 v2.7.0 github.com/google/go-cmp v0.6.0 github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 diff --git a/go.sum b/go.sum index 8d81217dcd..f386d6806e 100644 --- a/go.sum +++ b/go.sum @@ -65,6 +65,8 @@ github.com/consensys/gnark-crypto v0.17.1-0.20250407123233-f3e34b715587 h1:7kACJ github.com/consensys/gnark-crypto v0.17.1-0.20250407123233-f3e34b715587/go.mod h1:n9v7xUdQOvDbzaM55cFQUi67FIyb9Rl+Ia6PaM62ig0= github.com/consensys/gnark-crypto v0.17.1-0.20250407141002-d4f06e01a637 h1:KiqiL4mcpZUrnJjfm7IDh3wlfbnc88eRgI93KoUJ3cg= github.com/consensys/gnark-crypto v0.17.1-0.20250407141002-d4f06e01a637/go.mod h1:n9v7xUdQOvDbzaM55cFQUi67FIyb9Rl+Ia6PaM62ig0= +github.com/consensys/gnark-crypto v0.17.1-0.20250409113232-424096f3f868 h1:ad3+TFnJhWOsgHhS/nw/gzOkPei9pd16J7eaJF01g4o= +github.com/consensys/gnark-crypto v0.17.1-0.20250409113232-424096f3f868/go.mod h1:n9v7xUdQOvDbzaM55cFQUi67FIyb9Rl+Ia6PaM62ig0= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= From ba58e1db4b8b925bafd32db5e675670d128b4d5f Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 11:37:53 +0000 Subject: [PATCH 059/105] refactor: use z value from gnark-crypto --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 0008099956..4d8ff88872 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -79,15 +79,16 @@ func (g1 *G1) ClearCofactor(q *G1Affine) *G1Affine { // See: https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-simplified-swu-method func (g1 *G1) MapToCurve1(u *baseEl) (*G1Affine, error) { one := emulated.ValueOf[emulated.BLS12381Fp]("1") - eleven := emulated.ValueOf[emulated.BLS12381Fp]("11") + z := emulated.ValueOf[emulated.BLS12381Fp](hash_to_curve.G1SSWUIsogenyZ()) - sswuIsoCurveCoeffA := emulated.ValueOf[emulated.BLS12381Fp]("0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d") - sswuIsoCurveCoeffB := emulated.ValueOf[emulated.BLS12381Fp]("0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0") + sswuIsoCurveCoeffAValue, sswuIsoCurveCoeffBValue := hash_to_curve.G1SSWUIsogenyCurveCoefficients() + sswuIsoCurveCoeffA := emulated.ValueOf[emulated.BLS12381Fp](sswuIsoCurveCoeffAValue) + sswuIsoCurveCoeffB := emulated.ValueOf[emulated.BLS12381Fp](sswuIsoCurveCoeffBValue) tv1 := g1.curveF.Mul(u, u) // 1. tv1 = u² //mul tv1 by Z ( g1MulByZ) - tv1 = g1.curveF.Mul(&eleven, tv1) + tv1 = g1.curveF.Mul(&z, tv1) // var tv2 fp.Element tv2 := g1.curveF.Mul(tv1, tv1) // 3. tv2 = tv1² @@ -103,9 +104,9 @@ func (g1 *G1) MapToCurve1(u *baseEl) (*G1Affine, error) { // tv4 = Z - tv2 = g1.curveF.Neg(tv2) // tv2.Neg(&tv2) - tv4 := g1.curveF.Select(tv2IsZero, &eleven, tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) - tv4 = g1.curveF.Mul(tv4, &sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 + tv2 = g1.curveF.Neg(tv2) // tv2.Neg(&tv2) + tv4 := g1.curveF.Select(tv2IsZero, &z, tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) + tv4 = g1.curveF.Mul(tv4, &sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 tv2 = g1.curveF.Mul(tv3, tv3) // 9. tv2 = tv3² @@ -136,7 +137,7 @@ func (g1 *G1) MapToCurve1(u *baseEl) (*G1Affine, error) { g1.api.AssertIsBoolean(gx1NSquare) y1Squarev := g1.curveF.Mul(y1, y1) y1Squarev = g1.curveF.Mul(y1Squarev, tv6) - uz := g1.curveF.Mul(tv2, &eleven) + uz := g1.curveF.Mul(tv2, &z) ysvMinusuz := g1.curveF.Sub(y1Squarev, uz) isQNRWitness := g1.curveF.IsZero(ysvMinusuz) cond1 := g1.api.And(isQNRWitness, gx1NSquare) From 16e24efeb6ab9952891c4eaf4638728da67ef91c Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 11:38:07 +0000 Subject: [PATCH 060/105] chore: use constant 1 (on one limb) --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 4d8ff88872..d61dc80787 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -78,7 +78,7 @@ func (g1 *G1) ClearCofactor(q *G1Affine) *G1Affine { // // See: https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-simplified-swu-method func (g1 *G1) MapToCurve1(u *baseEl) (*G1Affine, error) { - one := emulated.ValueOf[emulated.BLS12381Fp]("1") + one := g1.curveF.One() z := emulated.ValueOf[emulated.BLS12381Fp](hash_to_curve.G1SSWUIsogenyZ()) sswuIsoCurveCoeffAValue, sswuIsoCurveCoeffBValue := hash_to_curve.G1SSWUIsogenyCurveCoefficients() @@ -96,7 +96,7 @@ func (g1 *G1) MapToCurve1(u *baseEl) (*G1Affine, error) { // var tv3 fp.Element // var tv4 fp.Element - tv3 := g1.curveF.Add(tv2, &one) // 5. tv3 = tv2 + 1 + tv3 := g1.curveF.Add(tv2, one) // 5. tv3 = tv2 + 1 tv3 = g1.curveF.Mul(tv3, &sswuIsoCurveCoeffB) // 6. tv3 = B * tv3 // tv2NZero := g1NotZero(&tv2) From 6ee8bd329440c126041ebbd3f3ab97a05bda79eb Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 13:00:38 +0000 Subject: [PATCH 061/105] chore: remove done todo --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 1 - 1 file changed, 1 deletion(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index d61dc80787..75c53b518f 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -130,7 +130,6 @@ func (g1 *G1) MapToCurve1(u *baseEl) (*G1Affine, error) { y1 := hint[0] // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) - // TODO constrain gx1NSquare and y1 // (gx1NSquare==1 AND (u/v) QNR ) OR (gx1NSquare==0 AND (u/v) QR ) gx1NSquare := hint[1].Limbs[0] From 8499ee60fe01f4872c38a797b5c06abfef229c9f Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Wed, 9 Apr 2025 15:00:50 +0200 Subject: [PATCH 062/105] feat: added file --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 1 - 1 file changed, 1 deletion(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index d61dc80787..75c53b518f 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -130,7 +130,6 @@ func (g1 *G1) MapToCurve1(u *baseEl) (*G1Affine, error) { y1 := hint[0] // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) - // TODO constrain gx1NSquare and y1 // (gx1NSquare==1 AND (u/v) QNR ) OR (gx1NSquare==0 AND (u/v) QR ) gx1NSquare := hint[1].Limbs[0] From 193791d4634fe7edbaf8333d59cb6f1fec427c7c Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 13:01:38 +0000 Subject: [PATCH 063/105] refactor: use NewElement instead of ValueOf --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 75c53b518f..4f88c883a7 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -79,16 +79,16 @@ func (g1 *G1) ClearCofactor(q *G1Affine) *G1Affine { // See: https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-simplified-swu-method func (g1 *G1) MapToCurve1(u *baseEl) (*G1Affine, error) { one := g1.curveF.One() - z := emulated.ValueOf[emulated.BLS12381Fp](hash_to_curve.G1SSWUIsogenyZ()) + z := g1.curveF.NewElement(hash_to_curve.G1SSWUIsogenyZ()) sswuIsoCurveCoeffAValue, sswuIsoCurveCoeffBValue := hash_to_curve.G1SSWUIsogenyCurveCoefficients() - sswuIsoCurveCoeffA := emulated.ValueOf[emulated.BLS12381Fp](sswuIsoCurveCoeffAValue) - sswuIsoCurveCoeffB := emulated.ValueOf[emulated.BLS12381Fp](sswuIsoCurveCoeffBValue) + sswuIsoCurveCoeffA := g1.curveF.NewElement(sswuIsoCurveCoeffAValue) + sswuIsoCurveCoeffB := g1.curveF.NewElement(sswuIsoCurveCoeffBValue) tv1 := g1.curveF.Mul(u, u) // 1. tv1 = u² //mul tv1 by Z ( g1MulByZ) - tv1 = g1.curveF.Mul(&z, tv1) + tv1 = g1.curveF.Mul(z, tv1) // var tv2 fp.Element tv2 := g1.curveF.Mul(tv1, tv1) // 3. tv2 = tv1² @@ -96,30 +96,30 @@ func (g1 *G1) MapToCurve1(u *baseEl) (*G1Affine, error) { // var tv3 fp.Element // var tv4 fp.Element - tv3 := g1.curveF.Add(tv2, one) // 5. tv3 = tv2 + 1 - tv3 = g1.curveF.Mul(tv3, &sswuIsoCurveCoeffB) // 6. tv3 = B * tv3 + tv3 := g1.curveF.Add(tv2, one) // 5. tv3 = tv2 + 1 + tv3 = g1.curveF.Mul(tv3, sswuIsoCurveCoeffB) // 6. tv3 = B * tv3 // tv2NZero := g1NotZero(&tv2) tv2IsZero := g1.curveF.IsZero(tv2) // tv4 = Z - tv2 = g1.curveF.Neg(tv2) // tv2.Neg(&tv2) - tv4 := g1.curveF.Select(tv2IsZero, &z, tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) - tv4 = g1.curveF.Mul(tv4, &sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 + tv2 = g1.curveF.Neg(tv2) // tv2.Neg(&tv2) + tv4 := g1.curveF.Select(tv2IsZero, z, tv2) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) + tv4 = g1.curveF.Mul(tv4, sswuIsoCurveCoeffA) // 8. tv4 = A * tv4 tv2 = g1.curveF.Mul(tv3, tv3) // 9. tv2 = tv3² tv6 := g1.curveF.Mul(tv4, tv4) // 10. tv6 = tv4² - tv5 := g1.curveF.Mul(tv6, &sswuIsoCurveCoeffA) // 11. tv5 = A * tv6 + tv5 := g1.curveF.Mul(tv6, sswuIsoCurveCoeffA) // 11. tv5 = A * tv6 tv2 = g1.curveF.Add(tv2, tv5) // 12. tv2 = tv2 + tv5 tv2 = g1.curveF.Mul(tv2, tv3) // 13. tv2 = tv2 * tv3 tv6 = g1.curveF.Mul(tv6, tv4) // 14. tv6 = tv6 * tv4 - tv5 = g1.curveF.Mul(tv6, &sswuIsoCurveCoeffB) // 15. tv5 = B * tv6 - tv2 = g1.curveF.Add(tv2, tv5) // 16. tv2 = tv2 + tv5 + tv5 = g1.curveF.Mul(tv6, sswuIsoCurveCoeffB) // 15. tv5 = B * tv6 + tv2 = g1.curveF.Add(tv2, tv5) // 16. tv2 = tv2 + tv5 x := g1.curveF.Mul(tv1, tv3) // 17. x = tv1 * tv3 @@ -136,7 +136,7 @@ func (g1 *G1) MapToCurve1(u *baseEl) (*G1Affine, error) { g1.api.AssertIsBoolean(gx1NSquare) y1Squarev := g1.curveF.Mul(y1, y1) y1Squarev = g1.curveF.Mul(y1Squarev, tv6) - uz := g1.curveF.Mul(tv2, &z) + uz := g1.curveF.Mul(tv2, z) ysvMinusuz := g1.curveF.Sub(y1Squarev, uz) isQNRWitness := g1.curveF.IsZero(ysvMinusuz) cond1 := g1.api.And(isQNRWitness, gx1NSquare) From 2346fc64bb5efff8d49ab578aa06954faaed6925 Mon Sep 17 00:00:00 2001 From: Thomas Piellard Date: Wed, 9 Apr 2025 17:37:39 +0200 Subject: [PATCH 064/105] feat: add test map_to_g1 precompile --- std/evmprecompiles/bls_test.go | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/std/evmprecompiles/bls_test.go b/std/evmprecompiles/bls_test.go index 4f0e04e0b5..a5eb5eca0e 100644 --- a/std/evmprecompiles/bls_test.go +++ b/std/evmprecompiles/bls_test.go @@ -7,6 +7,7 @@ import ( "github.com/consensys/gnark-crypto/ecc" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" @@ -307,3 +308,37 @@ func TestECPairBLSBLSMulBatch(t *testing.T) { assert.NoError(err) } } + +// 16: mapToG1 check +type eCMapToG1BLSCircuit struct { + A emulated.Element[emulated.BLS12381Fp] + R sw_bls12381.G1Affine +} + +func (c *eCMapToG1BLSCircuit) Define(api frontend.API) error { + + g, err := sw_bls12381.NewG1(api) + if err != nil { + return fmt.Errorf("new G1: %w", err) + } + r := ECMapToG1BLS(api, &c.A) + g.AssertIsEqual(r, &c.R) + + return nil +} + +func TestECMapToG1(t *testing.T) { + + assert := test.NewAssert(t) + var a fp.Element + a.SetRandom() + g := bls12381.MapToG1(a) + + witness := eCMapToG1BLSCircuit{ + A: emulated.ValueOf[emulated.BLS12381Fp](a.String()), + R: sw_bls12381.NewG1Affine(g), + } + + err := test.IsSolved(&eCMapToG1BLSCircuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} From 6396084217cbaa0185d76137cbfae2f919d69341 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 21:33:51 +0000 Subject: [PATCH 065/105] refactor: embed sswu map info in G2 --- std/algebra/emulated/sw_bls12381/g2.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/std/algebra/emulated/sw_bls12381/g2.go b/std/algebra/emulated/sw_bls12381/g2.go index 6c0bd79130..b2ee3a9688 100644 --- a/std/algebra/emulated/sw_bls12381/g2.go +++ b/std/algebra/emulated/sw_bls12381/g2.go @@ -5,6 +5,7 @@ import ( "math/big" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/algebra/algopts" "github.com/consensys/gnark/std/algebra/emulated/fields_bls12381" @@ -19,6 +20,10 @@ type G2 struct { u1, w, w2 *emulated.Element[BaseField] eigenvalue *emulated.Element[ScalarField] v *fields_bls12381.E2 + + // SSWU map coefficients + sswuCoeffA, sswuCoeffB *fields_bls12381.E2 + sswuZ *fields_bls12381.E2 } type g2AffP struct { @@ -61,6 +66,20 @@ func NewG2(api frontend.API) (*G2, error) { A0: emulated.ValueOf[BaseField]("2973677408986561043442465346520108879172042883009249989176415018091420807192182638567116318576472649347015917690530"), A1: emulated.ValueOf[BaseField]("1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257"), } + sswuCoeffA, sswuCoeffB := hash_to_curve.G2SSWUIsogenyCurveCoefficients() + coeffA := &fields_bls12381.E2{ + A0: *fp.NewElement(sswuCoeffA.A0), + A1: *fp.NewElement(sswuCoeffA.A1), + } + coeffB := &fields_bls12381.E2{ + A0: *fp.NewElement(sswuCoeffB.A0), + A1: *fp.NewElement(sswuCoeffB.A1), + } + sswuZ := hash_to_curve.G2SSWUIsogenyZ() + z := &fields_bls12381.E2{ + A0: *fp.NewElement(sswuZ.A0), + A1: *fp.NewElement(sswuZ.A1), + } return &G2{ api: api, fp: fp, @@ -71,6 +90,10 @@ func NewG2(api frontend.API) (*G2, error) { eigenvalue: &eigenvalue, u1: &u1, v: &v, + // SSWU map + sswuCoeffA: coeffA, + sswuCoeffB: coeffB, + sswuZ: z, }, nil } From 7ee3b91ca19a619325b02c1bfe714d66997df5e0 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 21:36:56 +0000 Subject: [PATCH 066/105] refactor: map to g2 --- .../emulated/sw_bls12381/hash_to_g2.go | 413 ++++-------------- .../emulated/sw_bls12381/hash_to_g2_test.go | 30 +- 2 files changed, 114 insertions(+), 329 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2.go b/std/algebra/emulated/sw_bls12381/hash_to_g2.go index a2955d45e6..f18528e8dd 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2.go @@ -1,6 +1,7 @@ package sw_bls12381 import ( + "fmt" "math/big" "slices" @@ -18,18 +19,7 @@ const ( len_per_base_element = 64 ) -func HashToG2(api frontend.API, msg []uints.U8, dst []byte) (*G2Affine, error) { - fp, e := emulated.NewField[emulated.BLS12381Fp](api) - if e != nil { - return &G2Affine{}, e - } - ext2 := fields_bls12381.NewExt2(api) - mapper := newMapper(api, ext2, fp) - g2, err := NewG2(api) - if err != nil { - return nil, err - } - +func (g2 *G2) HashToG2(msg []uints.U8, dst []byte) (*G2Affine, error) { // Steps: // 1. u = hash_to_field(msg, 2) // 2. Q0 = map_to_curve(u[0]) @@ -39,25 +29,31 @@ func HashToG2(api frontend.API, msg []uints.U8, dst []byte) (*G2Affine, error) { // 6. return P lenPerBaseElement := len_per_base_element lenInBytes := lenPerBaseElement * 4 - uniformBytes, e := tofield.ExpandMsgXmd(api, msg, dst, lenInBytes) + uniformBytes, e := tofield.ExpandMsgXmd(g2.api, msg, dst, lenInBytes) if e != nil { return &G2Affine{}, e } - ele1 := bytesToElement(api, fp, uniformBytes[:lenPerBaseElement]) - ele2 := bytesToElement(api, fp, uniformBytes[lenPerBaseElement:lenPerBaseElement*2]) - ele3 := bytesToElement(api, fp, uniformBytes[lenPerBaseElement*2:lenPerBaseElement*3]) - ele4 := bytesToElement(api, fp, uniformBytes[lenPerBaseElement*3:]) + ele1 := bytesToElement(g2.api, g2.fp, uniformBytes[:lenPerBaseElement]) + ele2 := bytesToElement(g2.api, g2.fp, uniformBytes[lenPerBaseElement:lenPerBaseElement*2]) + ele3 := bytesToElement(g2.api, g2.fp, uniformBytes[lenPerBaseElement*2:lenPerBaseElement*3]) + ele4 := bytesToElement(g2.api, g2.fp, uniformBytes[lenPerBaseElement*3:]) // we will still do iso_map before point addition, as we do not have point addition in E' (yet) - Q0 := mapper.mapToCurve(fields_bls12381.E2{A0: *ele1, A1: *ele2}) - Q1 := mapper.mapToCurve(fields_bls12381.E2{A0: *ele3, A1: *ele4}) - Q0 = mapper.isogeny(&Q0.P.X, &Q0.P.Y) - Q1 = mapper.isogeny(&Q1.P.X, &Q1.P.Y) + Q0, err := g2.MapToCurve2(&fields_bls12381.E2{A0: *ele1, A1: *ele2}) + if err != nil { + return nil, fmt.Errorf("map to curve Q1: %w", err) + } + Q1, err := g2.MapToCurve2(&fields_bls12381.E2{A0: *ele3, A1: *ele4}) + if err != nil { + return nil, fmt.Errorf("map to curve Q2: %w", err) + } + Q0 = g2.isogeny(Q0) + Q1 = g2.isogeny(Q1) R := g2.AddUnified(Q0, Q1) - return clearCofactor(g2, fp, R), nil + return g2.ClearCofactor(R), nil } func bytesToElement(api frontend.API, fp *emulated.Field[emulated.BLS12381Fp], data []uints.U8) *emulated.Element[emulated.BLS12381Fp] { @@ -85,271 +81,89 @@ func bytesToElement(api frontend.API, fp *emulated.Field[emulated.BLS12381Fp], d return fp.Add(head, tail) } -type sswuMapper struct { - A, B, Z fields_bls12381.E2 - ext2 *fields_bls12381.Ext2 - fp *emulated.Field[emulated.BLS12381Fp] - api frontend.API - iso *isogeny -} - -func newMapper(api frontend.API, ext2 *fields_bls12381.Ext2, fp *emulated.Field[emulated.BLS12381Fp]) *sswuMapper { - coeff_a := fields_bls12381.E2{ - A0: emulated.ValueOf[emparams.BLS12381Fp](0), - A1: emulated.ValueOf[emparams.BLS12381Fp](240), +func (g2 *G2) evalFixedPolynomial(monic bool, coefficients []bls12381.E2, x *fields_bls12381.E2) *fields_bls12381.E2 { + emuCoefficients := make([]*fields_bls12381.E2, len(coefficients)) + for i := 0; i < len(coefficients); i++ { + emuCoefficients[i] = &fields_bls12381.E2{ + A0: emulated.ValueOf[emparams.BLS12381Fp](coefficients[i].A0), + A1: emulated.ValueOf[emparams.BLS12381Fp](coefficients[i].A1), + } } - coeff_b := fields_bls12381.E2{ - A0: emulated.ValueOf[emparams.BLS12381Fp](1012), - A1: emulated.ValueOf[emparams.BLS12381Fp](1012), + var res *fields_bls12381.E2 + if monic { + res = g2.Add(emuCoefficients[0], x) + } else { + res = emuCoefficients[len(emuCoefficients)-1] } - one := emulated.ValueOf[emulated.BLS12381Fp](1) - two := emulated.ValueOf[emulated.BLS12381Fp](2) - zeta := fields_bls12381.E2{ - A0: *fp.Neg(&two), - A1: *fp.Neg(&one), + for i := len(emuCoefficients) - 2; i >= 0; i-- { + res = g2.Mul(res, x) + res = g2.Add(res, emuCoefficients[i]) } - return &sswuMapper{ - A: coeff_a, - B: coeff_b, - Z: zeta, - ext2: ext2, - fp: fp, - api: api, - iso: newIsogeny(), - } + return res } -// Apply the Simplified SWU for the E' curve (RFC 9380 Section 6.6.3) -func (m sswuMapper) mapToCurve(u fields_bls12381.E2) *G2Affine { - // SSWU Steps: - // 1. tv1 = u^2 - tv1 := m.ext2.Square(&u) - // 2. tv1 = Z * tv1 - tv1 = m.ext2.Mul(&m.Z, tv1) - // 3. tv2 = tv1^2 - tv2 := m.ext2.Square(tv1) - // 4. tv2 = tv2 + tv1 - tv2 = m.ext2.Add(tv2, tv1) - // 5. tv3 = tv2 + 1 - tv3 := m.ext2.Add(tv2, m.ext2.One()) - // 6. tv3 = B * tv3 - tv3 = m.ext2.Mul(&m.B, tv3) - // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) - s1 := m.ext2.IsZero(tv2) - tv4 := m.ext2.Select(s1, &m.Z, m.ext2.Neg(tv2)) - // 8. tv4 = A * tv4 - tv4 = m.ext2.Mul(&m.A, tv4) - // 9. tv2 = tv3^2 - tv2 = m.ext2.Square(tv3) - // 10. tv6 = tv4^2 - tv6 := m.ext2.Square(tv4) - // 11. tv5 = A * tv6 - tv5 := m.ext2.Mul(&m.A, tv6) - // 12. tv2 = tv2 + tv5 - tv2 = m.ext2.Add(tv2, tv5) - // 13. tv2 = tv2 * tv3 - tv2 = m.ext2.Mul(tv2, tv3) - // 14. tv6 = tv6 * tv4 - tv6 = m.ext2.Mul(tv6, tv4) - // 15. tv5 = B * tv6 - tv5 = m.ext2.Mul(&m.B, tv6) - // 16. tv2 = tv2 + tv5 - tv2 = m.ext2.Add(tv2, tv5) - // 17. x = tv1 * tv3 - x := m.ext2.Mul(tv1, tv3) - // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) - isGx1Square, y1 := m.sqrtRatio(tv2, tv6) - // 19. y = tv1 * u - y := m.ext2.Mul(tv1, &u) - // 20. y = y * y1 - y = m.ext2.Mul(y, y1) - // 21. x = CMOV(x, tv3, is_gx1_square) - x = m.ext2.Select(isGx1Square, tv3, x) - // 22. y = CMOV(y, y1, is_gx1_square) - y = m.ext2.Select(isGx1Square, y1, y) - // 23. e1 = sgn0(u) == sgn0(y) - sgn0U := m.sgn0(&u) - sgn0Y := m.sgn0(y) - diff := m.api.Sub(sgn0U, sgn0Y) - e1 := m.api.IsZero(diff) - // 24. y = CMOV(-y, y, e1) - yNeg := m.ext2.Neg(y) - y = m.ext2.Select(e1, y, yNeg) - // 25. x = x / tv4 - x = m.ext2.DivUnchecked(x, tv4) - // 26. return (x, y) - return &G2Affine{ - P: g2AffP{X: *x, Y: *y}, - } +func (g2 *G2) isogeny(p *G2Affine) *G2Affine { + isogenyMap := hash_to_curve.G2IsogenyMap() + ydenom := g2.evalFixedPolynomial(true, isogenyMap[3], &p.P.X) + xdenom := g2.evalFixedPolynomial(true, isogenyMap[1], &p.P.X) + y := g2.evalFixedPolynomial(false, isogenyMap[2], &p.P.X) + y = g2.Mul(y, &p.P.Y) + x := g2.evalFixedPolynomial(false, isogenyMap[0], &p.P.X) + x = g2.DivUnchecked(x, xdenom) + y = g2.DivUnchecked(y, ydenom) + return &G2Affine{P: g2AffP{X: *x, Y: *y}} } -func (m sswuMapper) sgn0(x *fields_bls12381.E2) frontend.Variable { - // Steps for sgn0_m_eq_2 - // 1. sign_0 = x_0 mod 2 - A0 := m.fp.Reduce(&x.A0) - x0 := m.fp.ToBits(A0) - sign0 := x0[0] - // 2. zero_0 = x_0 == 0 - zero0 := m.fp.IsZero(&x.A0) - // 3. sign_1 = x_1 mod 2 - A1 := m.fp.Reduce(&x.A1) - x1 := m.fp.ToBits(A1) - sign1 := x1[0] - // 4. s = sign_0 OR (zero_0 AND sign_1) # Avoid short-circuit logic ops - tv := m.api.And(zero0, sign1) - s := m.api.Or(sign0, tv) - // 5. return s +func (g2 *G2) sgn0(x *fields_bls12381.E2) frontend.Variable { + x0Bits := g2.fp.ToBitsCanonical(&x.A0) + x1Bits := g2.fp.ToBitsCanonical(&x.A1) + sign0 := x0Bits[0] + zero0 := g2.api.IsZero(sign0) + sign1 := x1Bits[0] + tv := g2.api.And(zero0, sign1) + s := g2.api.Or(sign0, tv) return s } -// Let's not mechanically translate the spec algorithm (Section F.2.1) into R1CS circuits. -// We could simply compute the result as a hint, then apply proper constraints, which is: -// for output of (b, y) -// -// b1 := {b = True AND y^2 * v = u} -// b2 := {b = False AND y^2 * v = Z * u} -// AssertTrue: {b1 OR b2} -func (m sswuMapper) sqrtRatio(u, v *fields_bls12381.E2) (frontend.Variable, *fields_bls12381.E2) { +// sqrtRatio computes u/v and returns (isQR, y) where isQR indicates if the +// result is a quadratic residue. +func (g2 *G2) sqrtRatio(u, v *fields_bls12381.E2) (frontend.Variable, *fields_bls12381.E2, error) { // Steps // 1. extract the base values of u, v, then compute G2SqrtRatio with gnark-crypto - x, err := m.fp.NewHint(g2SqrtRatioHint, 3, &u.A0, &u.A1, &v.A0, &v.A1) + x, err := g2.fp.NewHint(g2SqrtRatioHint, 3, &u.A0, &u.A1, &v.A0, &v.A1) if err != nil { - panic("failed to calculate sqrtRatio with gnark-crypto " + err.Error()) + return nil, nil, fmt.Errorf("failed to calculate sqrtRatio with gnark-crypto: %w", err) } - b := m.fp.IsZero(x[0]) + b := g2.fp.IsZero(x[0]) y := fields_bls12381.E2{A0: *x[1], A1: *x[2]} // 2. apply constraints // b1 := {b = True AND y^2 * v = u} - m.api.AssertIsBoolean(b) - y2 := m.ext2.Square(&y) - y2v := m.ext2.Mul(y2, v) - bY2vu := m.ext2.IsZero(m.ext2.Sub(y2v, u)) - b1 := m.api.And(b, bY2vu) + g2.api.AssertIsBoolean(b) + y2 := g2.Ext2.Square(&y) + y2v := g2.Ext2.Mul(y2, v) + bY2vu := g2.Ext2.IsZero(g2.Ext2.Sub(y2v, u)) + b1 := g2.api.And(b, bY2vu) // b2 := {b = False AND y^2 * v = Z * u} - uZ := m.ext2.Mul(&m.Z, u) - bY2vZu := m.ext2.IsZero(m.ext2.Sub(y2v, uZ)) - nb := m.api.IsZero(b) - b2 := m.api.And(nb, bY2vZu) - - cmp := m.api.Or(b1, b2) - m.api.AssertIsEqual(cmp, 1) - - return b, &y -} - -type g2Polynomial []fields_bls12381.E2 - -func (p g2Polynomial) eval(m *sswuMapper, at fields_bls12381.E2) (pAt *fields_bls12381.E2) { - pAt = &p[len(p)-1] - - for i := len(p) - 2; i >= 0; i-- { - pAt = m.ext2.Mul(pAt, &at) - pAt = m.ext2.Add(pAt, &p[i]) - } - - return -} - -type isogeny struct { - x_numerator, x_denominator, y_numerator, y_denominator g2Polynomial -} - -func newIsogeny() *isogeny { - return &isogeny{ - x_numerator: g2Polynomial([]fields_bls12381.E2{ - *e2FromStrings( - "889424345604814976315064405719089812568196182208668418962679585805340366775741747653930584250892369786198727235542", - "889424345604814976315064405719089812568196182208668418962679585805340366775741747653930584250892369786198727235542"), - *e2FromStrings( - "0", - "2668273036814444928945193217157269437704588546626005256888038757416021100327225242961791752752677109358596181706522"), - *e2FromStrings( - "2668273036814444928945193217157269437704588546626005256888038757416021100327225242961791752752677109358596181706526", - "1334136518407222464472596608578634718852294273313002628444019378708010550163612621480895876376338554679298090853261"), - *e2FromStrings( - "3557697382419259905260257622876359250272784728834673675850718343221361467102966990615722337003569479144794908942033", - "0"), - }), - x_denominator: g2Polynomial([]fields_bls12381.E2{ - *e2FromStrings( - "0", - "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559715"), - *e2FromStrings( - "12", - "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559775"), - *e2FromStrings( - "1", - "0"), - }), - y_numerator: g2Polynomial([]fields_bls12381.E2{ - *e2FromStrings( - "3261222600550988246488569487636662646083386001431784202863158481286248011511053074731078808919938689216061999863558", - "3261222600550988246488569487636662646083386001431784202863158481286248011511053074731078808919938689216061999863558"), - *e2FromStrings( - "0", - "889424345604814976315064405719089812568196182208668418962679585805340366775741747653930584250892369786198727235518"), - *e2FromStrings( - "2668273036814444928945193217157269437704588546626005256888038757416021100327225242961791752752677109358596181706524", - "1334136518407222464472596608578634718852294273313002628444019378708010550163612621480895876376338554679298090853263"), - *e2FromStrings( - "2816510427748580758331037284777117739799287910327449993381818688383577828123182200904113516794492504322962636245776", - "0"), - }), - y_denominator: g2Polynomial([]fields_bls12381.E2{ - *e2FromStrings( - "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559355", - "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559355"), - *e2FromStrings( - "0", - "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559571"), - *e2FromStrings( - "18", - "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559769"), - *e2FromStrings( - "1", - "0"), - }), - } -} - -// Map the point from E' to E -func (m sswuMapper) isogeny(x, y *fields_bls12381.E2) *G2Affine { - xn := m.iso.x_numerator.eval(&m, *x) - - xd := m.iso.x_denominator.eval(&m, *x) - xdInv := m.ext2.Inverse(xd) - - yn := m.iso.y_numerator.eval(&m, *x) - yn = m.ext2.Mul(yn, y) - - yd := m.iso.y_denominator.eval(&m, *x) - ydInv := m.ext2.Inverse(yd) + uZ := g2.Ext2.Mul(g2.sswuZ, u) + bY2vZu := g2.Ext2.IsZero(g2.Ext2.Sub(y2v, uZ)) + nb := g2.api.IsZero(b) + b2 := g2.api.And(nb, bY2vZu) - return &G2Affine{ - P: g2AffP{ - X: *m.ext2.Mul(xn, xdInv), - Y: *m.ext2.Mul(yn, ydInv), - }, - } -} - -func e2FromStrings(x, y string) *fields_bls12381.E2 { - A0, _ := new(big.Int).SetString(x, 10) - A1, _ := new(big.Int).SetString(y, 10) - - a0 := emulated.ValueOf[emulated.BLS12381Fp](A0) - a1 := emulated.ValueOf[emulated.BLS12381Fp](A1) + cmp := g2.api.Or(b1, b2) + g2.api.AssertIsEqual(cmp, 1) - return &fields_bls12381.E2{A0: a0, A1: a1} + return b, &y, nil } -// Follow RFC 9380 Apendix G.3 to compute efficiently. -func clearCofactor(g2 *G2, fp *emulated.Field[emparams.BLS12381Fp], p *G2Affine) *G2Affine { +// ClearCofactor clears the cofactor of the point p in G2. +// +// See https://www.rfc-editor.org/rfc/rfc9380.html#name-cofactor-clearing-for-bls12 +func (g2 *G2) ClearCofactor(p *G2Affine) *G2Affine { // Steps: // 1. t1 = c1 * P // c1 = -15132376222941642752 @@ -376,58 +190,12 @@ func clearCofactor(g2 *G2, fp *emulated.Field[emparams.BLS12381Fp], p *G2Affine) return Q } -func (g2 *G2) evalFixedPolynomial(monic bool, coefficients []bls12381.E2, x *fields_bls12381.E2) *fields_bls12381.E2 { - emuCoefficients := make([]*fields_bls12381.E2, len(coefficients)) - for i := 0; i < len(coefficients); i++ { - emuCoefficients[i] = &fields_bls12381.E2{ - A0: emulated.ValueOf[emparams.BLS12381Fp](coefficients[i].A0), - A1: emulated.ValueOf[emparams.BLS12381Fp](coefficients[i].A1), - } - } - var res *fields_bls12381.E2 - if monic { - res = g2.Add(emuCoefficients[0], x) - } else { - res = emuCoefficients[len(emuCoefficients)-1] - } - - for i := len(emuCoefficients) - 2; i >= 0; i-- { - res = g2.Mul(res, x) - res = g2.Add(res, emuCoefficients[i]) - } - - return res -} - -func (g2 *G2) isogeny(p *G2Affine) *G2Affine { - isogenyMap := hash_to_curve.G2IsogenyMap() - ydenom := g2.evalFixedPolynomial(true, isogenyMap[3], &p.P.X) - xdenom := g2.evalFixedPolynomial(true, isogenyMap[1], &p.P.X) - y := g2.evalFixedPolynomial(false, isogenyMap[2], &p.P.X) - y = g2.Mul(y, &p.P.Y) - x := g2.evalFixedPolynomial(false, isogenyMap[0], &p.P.X) - x = g2.DivUnchecked(x, xdenom) - y = g2.DivUnchecked(y, ydenom) - return &G2Affine{P: g2AffP{X: *x, Y: *y}} -} - -func (g2 *G2) sgn0(x *fields_bls12381.E2) frontend.Variable { - x0Bits := g2.fp.ToBitsCanonical(&x.A0) - x1Bits := g2.fp.ToBitsCanonical(&x.A1) - sign0 := x0Bits[0] - zero0 := g2.api.IsZero(sign0) - sign1 := x1Bits[0] - tv := g2.api.And(zero0, sign1) - s := g2.api.Or(sign0, tv) - return s -} - -func (g2 *G2) MapToCurve2(u *fields_bls12381.E2) *G2Affine { +func (g2 *G2) MapToCurve2(u *fields_bls12381.E2) (*G2Affine, error) { // SSWU Steps: // 1. tv1 = u^2 tv1 := g2.Ext2.Square(u) // 2. tv1 = Z * tv1 - tv1 = g2.Ext2.Mul(m.Z, tv1) + tv1 = g2.Ext2.Mul(g2.sswuZ, tv1) // 3. tv2 = tv1^2 tv2 := g2.Ext2.Square(tv1) // 4. tv2 = tv2 + tv1 @@ -435,18 +203,18 @@ func (g2 *G2) MapToCurve2(u *fields_bls12381.E2) *G2Affine { // 5. tv3 = tv2 + 1 tv3 := g2.Ext2.Add(tv2, g2.Ext2.One()) // 6. tv3 = B * tv3 - tv3 = g2.Ext2.Mul(&m.B, tv3) + tv3 = g2.Ext2.Mul(g2.sswuCoeffB, tv3) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) s1 := g2.Ext2.IsZero(tv2) - tv4 := g2.Ext2.Select(s1, &m.Z, m.ext2.Neg(tv2)) + tv4 := g2.Ext2.Select(s1, g2.sswuZ, g2.Ext2.Neg(tv2)) // 8. tv4 = A * tv4 - tv4 = g2.Ext2.Mul(&m.A, tv4) + tv4 = g2.Ext2.Mul(g2.sswuCoeffA, tv4) // 9. tv2 = tv3^2 tv2 = g2.Ext2.Square(tv3) // 10. tv6 = tv4^2 tv6 := g2.Ext2.Square(tv4) // 11. tv5 = A * tv6 - tv5 := g2.Ext2.Mul(&m.A, tv6) + tv5 := g2.Ext2.Mul(g2.sswuCoeffA, tv6) // 12. tv2 = tv2 + tv5 tv2 = g2.Ext2.Add(tv2, tv5) // 13. tv2 = tv2 * tv3 @@ -454,15 +222,18 @@ func (g2 *G2) MapToCurve2(u *fields_bls12381.E2) *G2Affine { // 14. tv6 = tv6 * tv4 tv6 = g2.Ext2.Mul(tv6, tv4) // 15. tv5 = B * tv6 - tv5 = g2.Ext2.Mul(&m.B, tv6) + tv5 = g2.Ext2.Mul(g2.sswuCoeffB, tv6) // 16. tv2 = tv2 + tv5 tv2 = g2.Ext2.Add(tv2, tv5) // 17. x = tv1 * tv3 x := g2.Ext2.Mul(tv1, tv3) // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) - isGx1Square, y1 := m.sqrtRatio(tv2, tv6) + isGx1Square, y1, err := g2.sqrtRatio(tv2, tv6) + if err != nil { + return nil, fmt.Errorf("square ratio: %w", err) + } // 19. y = tv1 * u - y := g2.Ext2.Mul(tv1, &u) + y := g2.Ext2.Mul(tv1, u) // 20. y = y * y1 y = g2.Ext2.Mul(y, y1) // 21. x = CMOV(x, tv3, is_gx1_square) @@ -470,7 +241,7 @@ func (g2 *G2) MapToCurve2(u *fields_bls12381.E2) *G2Affine { // 22. y = CMOV(y, y1, is_gx1_square) y = g2.Ext2.Select(isGx1Square, y1, y) // 23. e1 = sgn0(u) == sgn0(y) - sgn0U := g2.sgn0(&u) + sgn0U := g2.sgn0(u) sgn0Y := g2.sgn0(y) diff := g2.api.Sub(sgn0U, sgn0Y) e1 := g2.api.IsZero(diff) @@ -482,5 +253,15 @@ func (g2 *G2) MapToCurve2(u *fields_bls12381.E2) *G2Affine { // 26. return (x, y) return &G2Affine{ P: g2AffP{X: *x, Y: *y}, + }, nil +} + +func (g2 *G2) MapToG2(u *fields_bls12381.E2) (*G2Affine, error) { + res, err := g2.MapToCurve2(u) + if err != nil { + return nil, fmt.Errorf("map to curve: %w", err) } + z := g2.isogeny(res) + z = g2.ClearCofactor(z) + return z, nil } diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go b/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go index 1675e2141f..2cf554bf67 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go @@ -80,19 +80,20 @@ type mapToCurveCircuit struct { func (c *mapToCurveCircuit) Define(api frontend.API) error { msg := uints.NewU8Array(c.Msg) fp, _ := emulated.NewField[emulated.BLS12381Fp](api) - ext2 := fields_bls12381.NewExt2(api) - mapper := newMapper(api, ext2, fp) + g2, err := NewG2(api) + if err != nil { + return err + } uniformBytes, _ := tofield.ExpandMsgXmd(api, msg, c.Dst, 128) ele1 := bytesToElement(api, fp, uniformBytes[:64]) ele2 := bytesToElement(api, fp, uniformBytes[64:]) e := fields_bls12381.E2{A0: *ele1, A1: *ele2} - affine := mapper.mapToCurve(e) - - g2, err := NewG2(api) + affine, err := g2.MapToCurve2(&e) if err != nil { return err } + g2.AssertIsEqual(affine, &c.Res) return nil @@ -133,8 +134,7 @@ func (c *clearCofactorCircuit) Define(api frontend.API) error { if err != nil { return err } - fp, _ := emulated.NewField[emulated.BLS12381Fp](api) - res := clearCofactor(g2, fp, &c.In) + res := g2.ClearCofactor(&c.In) g2.AssertIsEqual(res, &c.Res) return nil } @@ -165,15 +165,15 @@ type hashToG2Circuit struct { } func (c *hashToG2Circuit) Define(api frontend.API) error { - res, e := HashToG2(api, uints.NewU8Array(c.Msg), c.Dst) - if e != nil { - return e - } - g2, err := NewG2(api) if err != nil { return err } + res, e := g2.HashToG2(uints.NewU8Array(c.Msg), c.Dst) + if e != nil { + return e + } + g2.AssertIsEqual(res, &c.Res) return nil } @@ -208,7 +208,11 @@ type hashToG2BenchCircuit struct { } func (c *hashToG2BenchCircuit) Define(api frontend.API) error { - _, e := HashToG2(api, uints.NewU8Array(c.Msg), c.Dst) + g2, err := NewG2(api) + if err != nil { + return err + } + _, e := g2.HashToG2(uints.NewU8Array(c.Msg), c.Dst) return e } From 51e08f3f60bc3441a3a021f35a22c9bbcff181e6 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 21:51:59 +0000 Subject: [PATCH 067/105] fix: BLS signature api --- std/algebra/emulated/sw_bls12381/bls_sig.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/std/algebra/emulated/sw_bls12381/bls_sig.go b/std/algebra/emulated/sw_bls12381/bls_sig.go index 2969485391..68fd7b6ff2 100644 --- a/std/algebra/emulated/sw_bls12381/bls_sig.go +++ b/std/algebra/emulated/sw_bls12381/bls_sig.go @@ -29,7 +29,11 @@ func BlsAssertG2Verification(api frontend.API, pub G1Affine, sig G2Affine, msg [ g1GNeg.Neg(&g1Gen) g1GN := NewG1Affine(g1GNeg) - h, e := HashToG2(api, msg, []byte(g2_dst)) + g2, err := NewG2(api) + if err != nil { + return err + } + h, e := g2.HashToG2(msg, []byte(g2_dst)) if e != nil { return e } From 5526e69fc277f3e74bf64b0cbf654e95fc55f621 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 21:52:17 +0000 Subject: [PATCH 068/105] test: add isogeny and map to curve test --- .../emulated/sw_bls12381/hash_to_g2_test.go | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go b/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go index 2cf554bf67..ac966f53a3 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go @@ -8,6 +8,7 @@ import ( "github.com/consensys/gnark-crypto/ecc" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" bls12381fp "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" "github.com/consensys/gnark/constraint" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/frontend/cs/r1cs" @@ -124,6 +125,69 @@ func TestMapToCurveTestSolve(t *testing.T) { } } +type MapToCurve2CircuitDirect struct { + In fields_bls12381.E2 + Expected G2Affine +} + +func (c *MapToCurve2CircuitDirect) Define(api frontend.API) error { + g2, err := NewG2(api) + if err != nil { + return err + } + res, err := g2.MapToCurve2(&c.In) + if err != nil { + return err + } + g2.AssertIsEqual(res, &c.Expected) + return nil +} + +func TestMapToCurve2Direct(t *testing.T) { + assert := test.NewAssert(t) + var e2 bls12381.E2 + e2.A0.SetRandom() + e2.A1.SetRandom() + + res := bls12381.MapToCurve2(&e2) + + assignment := MapToCurve2CircuitDirect{ + In: fields_bls12381.FromE2(&e2), + Expected: NewG2Affine(res), + } + err := test.IsSolved(&MapToCurve2CircuitDirect{}, &assignment, ecc.BN254.ScalarField()) + assert.NoError(err) +} + +type TestG2IsogenyCircuit struct { + In G2Affine + Expected G2Affine +} + +func (c *TestG2IsogenyCircuit) Define(api frontend.API) error { + g2, err := NewG2(api) + if err != nil { + return err + } + res := g2.isogeny(&c.In) + g2.AssertIsEqual(res, &c.Expected) + return nil +} + +func TestG2Isogeny(t *testing.T) { + assert := test.NewAssert(t) + _, in := randomG1G2Affines() + var res bls12381.G2Affine + res.Set(&in) + hash_to_curve.G2Isogeny(&res.X, &res.Y) + assignment := TestG2IsogenyCircuit{ + In: NewG2Affine(in), + Expected: NewG2Affine(res), + } + err := test.IsSolved(&TestG2IsogenyCircuit{}, &assignment, ecc.BN254.ScalarField()) + assert.NoError(err) +} + type clearCofactorCircuit struct { In G2Affine Res G2Affine From 4647ef627b124402804a0c5147e724e1b890e65c Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 21:53:45 +0000 Subject: [PATCH 069/105] refactor: dont need return error --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 4f88c883a7..9b7efdedfc 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -30,7 +30,7 @@ func (g1 *G1) evalFixedPolynomial(monic bool, coefficients []fp.Element, x *base } -func (g1 *G1) isogeny(p *G1Affine) (*G1Affine, error) { +func (g1 *G1) isogeny(p *G1Affine) *G1Affine { isogenyMap := hash_to_curve.G1IsogenyMap() ydenom := g1.evalFixedPolynomial(true, isogenyMap[3], &p.X) xdenom := g1.evalFixedPolynomial(true, isogenyMap[1], &p.X) @@ -39,7 +39,7 @@ func (g1 *G1) isogeny(p *G1Affine) (*G1Affine, error) { x := g1.evalFixedPolynomial(false, isogenyMap[0], &p.X) x = g1.curveF.Div(x, xdenom) y = g1.curveF.Div(y, ydenom) - return &G1Affine{X: *x, Y: *y}, nil + return &G1Affine{X: *x, Y: *y} } // g1Sgn0 returns the parity of a @@ -178,13 +178,7 @@ func (g1 *G1) MapToG1(u *baseEl) (*G1Affine, error) { if err != nil { return nil, fmt.Errorf("map to curve: %w", err) } - - z, err := g1.isogeny(res) - if err != nil { - return nil, fmt.Errorf("isogeny: %w", err) - } - + z := g1.isogeny(res) z = g1.ClearCofactor(z) - return z, nil } From 24e8f78eabc146bd4b6010263fdccb8fd95ae843 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 21:57:29 +0000 Subject: [PATCH 070/105] test: add G1 isogeny test --- .../emulated/sw_bls12381/map_to_g1_test.go | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1_test.go b/std/algebra/emulated/sw_bls12381/map_to_g1_test.go index e6f853f2b8..24d63e5f35 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1_test.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1_test.go @@ -7,6 +7,7 @@ import ( "github.com/consensys/gnark-crypto/ecc" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/test" @@ -115,3 +116,32 @@ func TestMapToG1(t *testing.T) { err := test.IsSolved(&MapToG1Circuit{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } + +type IsogenyG1Circuit struct { + In G1Affine + Res G1Affine +} + +func (c *IsogenyG1Circuit) Define(api frontend.API) error { + g, err := NewG1(api) + if err != nil { + return err + } + res := g.isogeny(&c.In) + g.AssertIsEqual(res, &c.Res) + return nil +} + +func TestIsogenyG1(t *testing.T) { + assert := test.NewAssert(t) + in, _ := randomG1G2Affines() + var res bls12381.G1Affine + res.Set(&in) + hash_to_curve.G1Isogeny(&res.X, &res.Y) + witness := IsogenyG1Circuit{ + In: NewG1Affine(in), + Res: NewG1Affine(res), + } + err := test.IsSolved(&IsogenyG1Circuit{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} From 2aa451b8e1e0564b04870362a3c04088fdb036a6 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 22:13:57 +0000 Subject: [PATCH 071/105] refactor: use NewElement instead of ValueOf --- std/algebra/emulated/sw_bls12381/hash_to_g2.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2.go b/std/algebra/emulated/sw_bls12381/hash_to_g2.go index f18528e8dd..99e89a4b9c 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2.go @@ -11,7 +11,6 @@ import ( "github.com/consensys/gnark/std/algebra/emulated/fields_bls12381" "github.com/consensys/gnark/std/hash/tofield" "github.com/consensys/gnark/std/math/emulated" - "github.com/consensys/gnark/std/math/emulated/emparams" "github.com/consensys/gnark/std/math/uints" ) @@ -85,8 +84,8 @@ func (g2 *G2) evalFixedPolynomial(monic bool, coefficients []bls12381.E2, x *fie emuCoefficients := make([]*fields_bls12381.E2, len(coefficients)) for i := 0; i < len(coefficients); i++ { emuCoefficients[i] = &fields_bls12381.E2{ - A0: emulated.ValueOf[emparams.BLS12381Fp](coefficients[i].A0), - A1: emulated.ValueOf[emparams.BLS12381Fp](coefficients[i].A1), + A0: *g2.fp.NewElement(coefficients[i].A0), + A1: *g2.fp.NewElement(coefficients[i].A1), } } var res *fields_bls12381.E2 From 8d46753944edad2d2ce5b60aa659c620e8ee2a17 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 22:14:12 +0000 Subject: [PATCH 072/105] fix: monix fixed poly eval --- std/algebra/emulated/sw_bls12381/hash_to_g2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2.go b/std/algebra/emulated/sw_bls12381/hash_to_g2.go index 99e89a4b9c..54203e3a85 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2.go @@ -90,7 +90,7 @@ func (g2 *G2) evalFixedPolynomial(monic bool, coefficients []bls12381.E2, x *fie } var res *fields_bls12381.E2 if monic { - res = g2.Add(emuCoefficients[0], x) + res = g2.Add(emuCoefficients[len(emuCoefficients)-1], x) } else { res = emuCoefficients[len(emuCoefficients)-1] } From fc4267f5b36e8f367bdd1bd7a4f190866daa0464 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 22:14:28 +0000 Subject: [PATCH 073/105] test: remove benchmarking --- .../emulated/sw_bls12381/hash_to_g2_test.go | 80 ------------------- 1 file changed, 80 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go b/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go index ac966f53a3..984b176980 100644 --- a/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go +++ b/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go @@ -1,7 +1,6 @@ package sw_bls12381 import ( - "bytes" "encoding/hex" "testing" @@ -9,10 +8,7 @@ import ( bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" bls12381fp "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" - "github.com/consensys/gnark/constraint" "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/frontend/cs/r1cs" - "github.com/consensys/gnark/frontend/cs/scs" "github.com/consensys/gnark/std/algebra/emulated/fields_bls12381" "github.com/consensys/gnark/std/hash/tofield" "github.com/consensys/gnark/std/math/emulated" @@ -265,79 +261,3 @@ func TestHashToG2TestSolve(t *testing.T) { assert.NoError(err) } } - -type hashToG2BenchCircuit struct { - Msg []byte - Dst []byte -} - -func (c *hashToG2BenchCircuit) Define(api frontend.API) error { - g2, err := NewG2(api) - if err != nil { - return err - } - _, e := g2.HashToG2(uints.NewU8Array(c.Msg), c.Dst) - return e -} - -func BenchmarkHashToG2(b *testing.B) { - - dst := getDst() - - msg := "abcd" - witness := hashToG2BenchCircuit{ - Msg: []uint8(msg), - Dst: dst, - } - w, err := frontend.NewWitness(&witness, ecc.BN254.ScalarField()) - if err != nil { - b.Fatal(err) - } - var ccs constraint.ConstraintSystem - b.Run("compile scs", func(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - if ccs, err = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &hashToG2BenchCircuit{}); err != nil { - b.Fatal(err) - } - } - }) - var buf bytes.Buffer - _, err = ccs.WriteTo(&buf) - if err != nil { - b.Fatal(err) - } - b.Logf("scs size: %d (bytes), nb constraints %d, nbInstructions: %d", buf.Len(), ccs.GetNbConstraints(), ccs.GetNbInstructions()) - b.Run("solve scs", func(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - if _, err := ccs.Solve(w); err != nil { - b.Fatal(err) - } - } - }) - b.Run("compile r1cs", func(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - if ccs, err = frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &hashToG2BenchCircuit{}); err != nil { - b.Fatal(err) - } - } - }) - buf.Reset() - _, err = ccs.WriteTo(&buf) - if err != nil { - b.Fatal(err) - } - b.Logf("r1cs size: %d (bytes), nb constraints %d, nbInstructions: %d", buf.Len(), ccs.GetNbConstraints(), ccs.GetNbInstructions()) - - b.Run("solve r1cs", func(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - if _, err := ccs.Solve(w); err != nil { - b.Fatal(err) - } - } - }) - -} From 35df500638ff2ea5dfd13db845abb77e8915c597 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 9 Apr 2025 22:16:59 +0000 Subject: [PATCH 074/105] chore: rename hash to g2 file --- std/algebra/emulated/sw_bls12381/{hash_to_g2.go => map_to_g2.go} | 0 .../sw_bls12381/{hash_to_g2_test.go => map_to_g2_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename std/algebra/emulated/sw_bls12381/{hash_to_g2.go => map_to_g2.go} (100%) rename std/algebra/emulated/sw_bls12381/{hash_to_g2_test.go => map_to_g2_test.go} (100%) diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2.go b/std/algebra/emulated/sw_bls12381/map_to_g2.go similarity index 100% rename from std/algebra/emulated/sw_bls12381/hash_to_g2.go rename to std/algebra/emulated/sw_bls12381/map_to_g2.go diff --git a/std/algebra/emulated/sw_bls12381/hash_to_g2_test.go b/std/algebra/emulated/sw_bls12381/map_to_g2_test.go similarity index 100% rename from std/algebra/emulated/sw_bls12381/hash_to_g2_test.go rename to std/algebra/emulated/sw_bls12381/map_to_g2_test.go From cc3d5343a1a540d076c7b1c5421e4d9791a4376d Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Sun, 13 Apr 2025 23:31:09 +0000 Subject: [PATCH 075/105] chore: go mod update --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8e478acf0f..c39420201d 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/blang/semver/v4 v4.0.0 github.com/consensys/bavard v0.1.31-0.20250406004941-2db259e4b582 github.com/consensys/compress v0.2.5 - github.com/consensys/gnark-crypto v0.17.1-0.20250413230228-685fcabf9aee + github.com/consensys/gnark-crypto v0.17.1-0.20250413232955-6b6896cc6e82 github.com/fxamacker/cbor/v2 v2.7.0 github.com/google/go-cmp v0.6.0 github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 diff --git a/go.sum b/go.sum index cf13da9f38..bfe1bb317b 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,8 @@ github.com/consensys/bavard v0.1.31-0.20250406004941-2db259e4b582 h1:dTlIwEdFQml github.com/consensys/bavard v0.1.31-0.20250406004941-2db259e4b582/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= github.com/consensys/compress v0.2.5 h1:gJr1hKzbOD36JFsF1AN8lfXz1yevnJi1YolffY19Ntk= github.com/consensys/compress v0.2.5/go.mod h1:pyM+ZXiNUh7/0+AUjUf9RKUM6vSH7T/fsn5LLS0j1Tk= -github.com/consensys/gnark-crypto v0.17.1-0.20250413230228-685fcabf9aee h1:mAp4TNZdoGq+xwTutkjH5R/2Vfa13hijPMTwBovx5eg= -github.com/consensys/gnark-crypto v0.17.1-0.20250413230228-685fcabf9aee/go.mod h1:n9v7xUdQOvDbzaM55cFQUi67FIyb9Rl+Ia6PaM62ig0= +github.com/consensys/gnark-crypto v0.17.1-0.20250413232955-6b6896cc6e82 h1:co/pKIJskG4Um6YPu7DVutLBbnJp5FuJXT2LSwcK6Lg= +github.com/consensys/gnark-crypto v0.17.1-0.20250413232955-6b6896cc6e82/go.mod h1:n9v7xUdQOvDbzaM55cFQUi67FIyb9Rl+Ia6PaM62ig0= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= From f965e8c7fe56a7f908ab21927c47467ca41f866c Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Sun, 13 Apr 2025 23:37:13 +0000 Subject: [PATCH 076/105] chore: revert unrelated changes --- std/gkr/bn254_wrapper_api.go | 1 - std/permutation/poseidon2/gkr-poseidon2/gkr.go | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/std/gkr/bn254_wrapper_api.go b/std/gkr/bn254_wrapper_api.go index 375c7cb6df..a45109006f 100644 --- a/std/gkr/bn254_wrapper_api.go +++ b/std/gkr/bn254_wrapper_api.go @@ -3,7 +3,6 @@ package gkr import ( "errors" "fmt" - "github.com/consensys/gnark-crypto/ecc/bn254/fr" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/internal/gkr/bn254" diff --git a/std/permutation/poseidon2/gkr-poseidon2/gkr.go b/std/permutation/poseidon2/gkr-poseidon2/gkr.go index a5e6e9d1d5..ea135fc2c7 100644 --- a/std/permutation/poseidon2/gkr-poseidon2/gkr.go +++ b/std/permutation/poseidon2/gkr-poseidon2/gkr.go @@ -3,12 +3,11 @@ package gkr_poseidon2 import ( "errors" "fmt" + "github.com/consensys/gnark/std/permutation/poseidon2/gkr-poseidon2/internal" "hash" "math/big" "sync" - "github.com/consensys/gnark/std/permutation/poseidon2/gkr-poseidon2/internal" - "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark-crypto/ecc" From 8460c3f3fabd90ea2ea742e63daeeb15910dd973 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Sun, 13 Apr 2025 23:37:22 +0000 Subject: [PATCH 077/105] docs: fix typo for staticcehck --- std/evmprecompiles/16-blsmaptog1.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/evmprecompiles/16-blsmaptog1.go b/std/evmprecompiles/16-blsmaptog1.go index 0d9776fd4f..c71265bce8 100644 --- a/std/evmprecompiles/16-blsmaptog1.go +++ b/std/evmprecompiles/16-blsmaptog1.go @@ -7,7 +7,7 @@ import ( "github.com/consensys/gnark/std/math/emulated" ) -// ECMapToG1BLS implements[BLS12_MAP_FP_TO_G1] precompile contract at adress 0x10. +// ECMapToG1BLS implements[BLS12_MAP_FP_TO_G1] precompile contract at address 0x10. // // [ECMapToG1BLS]: https://eips.ethereum.org/EIPS/eip-2537 func ECMapToG1BLS(api frontend.API, u *emulated.Element[emulated.BLS12381Fp]) *sw_emulated.AffinePoint[emulated.BLS12381Fp] { From 5cedec3d8b4fe37efca36b31a2aafe21531726d2 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Sun, 13 Apr 2025 23:51:10 +0000 Subject: [PATCH 078/105] chore: stats --- internal/stats/latest_stats.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/stats/latest_stats.csv b/internal/stats/latest_stats.csv index bd212d6882..472da0d356 100644 --- a/internal/stats/latest_stats.csv +++ b/internal/stats/latest_stats.csv @@ -153,14 +153,14 @@ pairing_bls12377,bls24_315,plonk,0,0 pairing_bls12377,bls24_317,plonk,0,0 pairing_bls12377,bw6_761,plonk,51280,51280 pairing_bls12377,bw6_633,plonk,0,0 -pairing_bls12381,bn254,groth16,949444,1570802 +pairing_bls12381,bn254,groth16,949345,1570639 pairing_bls12381,bls12_377,groth16,0,0 pairing_bls12381,bls12_381,groth16,0,0 pairing_bls12381,bls24_315,groth16,0,0 pairing_bls12381,bls24_317,groth16,0,0 pairing_bls12381,bw6_761,groth16,0,0 pairing_bls12381,bw6_633,groth16,0,0 -pairing_bls12381,bn254,plonk,3649555,3239775 +pairing_bls12381,bn254,plonk,3649159,3239443 pairing_bls12381,bls12_377,plonk,0,0 pairing_bls12381,bls12_381,plonk,0,0 pairing_bls12381,bls24_315,plonk,0,0 From 69a391c263bab5cbe95fbb6a2b075bbb0ba7ec3c Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 14 Apr 2025 22:24:50 +0000 Subject: [PATCH 079/105] fix: use correct input for g2 sgn0 zero0 --- std/algebra/emulated/sw_bls12381/map_to_g2.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g2.go b/std/algebra/emulated/sw_bls12381/map_to_g2.go index 54203e3a85..63aee2a338 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g2.go @@ -116,14 +116,15 @@ func (g2 *G2) isogeny(p *G2Affine) *G2Affine { } func (g2 *G2) sgn0(x *fields_bls12381.E2) frontend.Variable { + // https://www.rfc-editor.org/rfc/rfc9380.html#name-the-sgn0-function case m=2 x0Bits := g2.fp.ToBitsCanonical(&x.A0) x1Bits := g2.fp.ToBitsCanonical(&x.A1) - sign0 := x0Bits[0] - zero0 := g2.api.IsZero(sign0) - sign1 := x1Bits[0] - tv := g2.api.And(zero0, sign1) - s := g2.api.Or(sign0, tv) - return s + + sign0 := x0Bits[0] // 1. sign_0 = x_0 mod 2 + zero0 := g2.fp.IsZero(&x.A0) // 2. zero_0 = x_0 == 0 + sign1 := x1Bits[0] // 3. sign_1 = x_1 mod 2 + sign := g2.api.Or(sign0, g2.api.And(zero0, sign1)) // 4. s = sign_0 OR (zero_0 AND sign_1) + return sign } // sqrtRatio computes u/v and returns (isQR, y) where isQR indicates if the From 4ed2847a8f1a4dd02472d1693ee5612a2c1bc6bb Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 14 Apr 2025 22:27:33 +0000 Subject: [PATCH 080/105] fix: use xor instead of diff and iszero --- std/algebra/emulated/sw_bls12381/map_to_g2.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g2.go b/std/algebra/emulated/sw_bls12381/map_to_g2.go index 63aee2a338..baaf781a41 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g2.go @@ -243,8 +243,7 @@ func (g2 *G2) MapToCurve2(u *fields_bls12381.E2) (*G2Affine, error) { // 23. e1 = sgn0(u) == sgn0(y) sgn0U := g2.sgn0(u) sgn0Y := g2.sgn0(y) - diff := g2.api.Sub(sgn0U, sgn0Y) - e1 := g2.api.IsZero(diff) + e1 := g2.api.Xor(sgn0U, sgn0Y) // we keep in mind that e1 = 1-(sgn0U == sgn0Y) as in gnark-crypto // 24. y = CMOV(-y, y, e1) yNeg := g2.Ext2.Neg(y) y = g2.Ext2.Select(e1, y, yNeg) From e99790c17110f151e2b20f129db4f46a06484f5e Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 14 Apr 2025 22:27:45 +0000 Subject: [PATCH 081/105] fix: select order --- std/algebra/emulated/sw_bls12381/map_to_g2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g2.go b/std/algebra/emulated/sw_bls12381/map_to_g2.go index baaf781a41..f87864d678 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g2.go @@ -246,7 +246,7 @@ func (g2 *G2) MapToCurve2(u *fields_bls12381.E2) (*G2Affine, error) { e1 := g2.api.Xor(sgn0U, sgn0Y) // we keep in mind that e1 = 1-(sgn0U == sgn0Y) as in gnark-crypto // 24. y = CMOV(-y, y, e1) yNeg := g2.Ext2.Neg(y) - y = g2.Ext2.Select(e1, y, yNeg) + y = g2.Ext2.Select(e1, yNeg, y) // contrary to gnark-crypto, if e1=1 we select yNeg and y otherwise // 25. x = x / tv4 x = g2.Ext2.DivUnchecked(x, tv4) // 26. return (x, y) From aa85b61210c5476f680d58638633e2d929e60aa3 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 7 Jul 2025 13:45:38 +0000 Subject: [PATCH 082/105] fix: duplicate declaration --- std/algebra/emulated/sw_bn254/pairing.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/std/algebra/emulated/sw_bn254/pairing.go b/std/algebra/emulated/sw_bn254/pairing.go index f7d25ac29a..c919923b78 100644 --- a/std/algebra/emulated/sw_bn254/pairing.go +++ b/std/algebra/emulated/sw_bn254/pairing.go @@ -74,10 +74,6 @@ func NewPairing(api frontend.API) (*Pairing, error) { if err != nil { return nil, fmt.Errorf("new g2: %w", err) } - g2, err := NewG2(api) - if err != nil { - return nil, fmt.Errorf("new g2: %w", err) - } return &Pairing{ api: api, Ext12: fields_bn254.NewExt12(api), From b34438e84f6ef12d49938fbcd7f4334721e0d0d7 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 7 Jul 2025 22:23:08 +0000 Subject: [PATCH 083/105] feat: implement BLS signature package with test --- std/algebra/emulated/sw_bls12381/bls_sig.go | 42 ---------- .../emulated/sw_bls12381/bls_sig_test.go | 83 ------------------- std/signature/bls/bls_g1.go | 73 ++++++++++++++++ std/signature/bls/blssig_test.go | 66 +++++++++++++++ std/signature/bls/doc.go | 25 ++++++ 5 files changed, 164 insertions(+), 125 deletions(-) delete mode 100644 std/algebra/emulated/sw_bls12381/bls_sig.go delete mode 100644 std/algebra/emulated/sw_bls12381/bls_sig_test.go create mode 100644 std/signature/bls/bls_g1.go create mode 100644 std/signature/bls/blssig_test.go create mode 100644 std/signature/bls/doc.go diff --git a/std/algebra/emulated/sw_bls12381/bls_sig.go b/std/algebra/emulated/sw_bls12381/bls_sig.go deleted file mode 100644 index 68fd7b6ff2..0000000000 --- a/std/algebra/emulated/sw_bls12381/bls_sig.go +++ /dev/null @@ -1,42 +0,0 @@ -package sw_bls12381 - -import ( - bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/std/math/uints" -) - -const g2_dst = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_" - -func BlsAssertG2Verification(api frontend.API, pub G1Affine, sig G2Affine, msg []uints.U8) error { - pairing, e := NewPairing(api) - if e != nil { - return e - } - - // public key cannot be infinity - xtest := pairing.g1.curveF.IsZero(&pub.X) - ytest := pairing.g1.curveF.IsZero(&pub.Y) - pubTest := api.Or(xtest, ytest) - api.AssertIsEqual(pubTest, 0) - - // prime order subgroup checks - pairing.AssertIsOnG1(&pub) - pairing.AssertIsOnG2(&sig) - - var g1GNeg bls12381.G1Affine - _, _, g1Gen, _ := bls12381.Generators() - g1GNeg.Neg(&g1Gen) - g1GN := NewG1Affine(g1GNeg) - - g2, err := NewG2(api) - if err != nil { - return err - } - h, e := g2.HashToG2(msg, []byte(g2_dst)) - if e != nil { - return e - } - - return pairing.PairingCheck([]*G1Affine{&g1GN, &pub}, []*G2Affine{&sig, h}) -} diff --git a/std/algebra/emulated/sw_bls12381/bls_sig_test.go b/std/algebra/emulated/sw_bls12381/bls_sig_test.go deleted file mode 100644 index 47827ab258..0000000000 --- a/std/algebra/emulated/sw_bls12381/bls_sig_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package sw_bls12381 - -import ( - "encoding/hex" - "testing" - - "github.com/consensys/gnark-crypto/ecc" - bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/std/math/uints" - "github.com/consensys/gnark/test" -) - -type blsG2SigCircuit struct { - Pub bls12381.G1Affine - msg []byte - Sig bls12381.G2Affine -} - -func (c *blsG2SigCircuit) Define(api frontend.API) error { - msg := uints.NewU8Array(c.msg) - return BlsAssertG2Verification(api, NewG1Affine(c.Pub), NewG2Affine(c.Sig), msg) -} - -// "pubkey": "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a", -// "message": "0x5656565656565656565656565656565656565656565656565656565656565656", -// "signature": "0x882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb"}, -// "output": true} -func TestBlsSigTestSolve(t *testing.T) { - assert := test.NewAssert(t) - - msgHex := "5656565656565656565656565656565656565656565656565656565656565656" - pubHex := "a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a" - sigHex := "882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb" - - msgBytes := make([]byte, len(msgHex)>>1) - hex.Decode(msgBytes, []byte(msgHex)) - pubBytes := make([]byte, len(pubHex)>>1) - hex.Decode(pubBytes, []byte(pubHex)) - sigBytes := make([]byte, len(sigHex)>>1) - hex.Decode(sigBytes, []byte(sigHex)) - - var pub bls12381.G1Affine - _, e := pub.SetBytes(pubBytes) - if e != nil { - t.Fail() - } - var sig bls12381.G2Affine - _, e = sig.SetBytes(sigBytes) - if e != nil { - t.Fail() - } - - var g1GNeg bls12381.G1Affine - _, _, g1Gen, _ := bls12381.Generators() - g1GNeg.Neg(&g1Gen) - - h, e := bls12381.HashToG2(msgBytes, []byte(g2_dst)) - if e != nil { - t.Fail() - } - - b, e := bls12381.PairingCheck([]bls12381.G1Affine{g1GNeg, pub}, []bls12381.G2Affine{sig, h}) - if e != nil { - t.Fail() - } - if !b { - t.Fail() // invalid inputs, won't verify - } - - circuit := blsG2SigCircuit{ - Pub: pub, - msg: msgBytes, - Sig: sig, - } - witness := blsG2SigCircuit{ - Pub: pub, - msg: msgBytes, - Sig: sig, - } - err := test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) -} diff --git a/std/signature/bls/bls_g1.go b/std/signature/bls/bls_g1.go new file mode 100644 index 0000000000..b4d88dad24 --- /dev/null +++ b/std/signature/bls/bls_g1.go @@ -0,0 +1,73 @@ +package bls + +import ( + "fmt" + + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" + "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/math/uints" +) + +const g2_dst = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_" + +// PublicKeyG1 is the public key for BLS signature scheme for minimal public key +// variant. The corresponding signature is [SignatureG2]. +type PublicKeyG1 sw_bls12381.G1Affine + +// SignatureG2 is the signature for BLS signature scheme for minimal public key +// variant. The corresponding public key is [PublicKeyG1]. +type SignatureG2 sw_bls12381.G2Affine + +// VerifyG2 asserts that the signature sig is valid for the prehashed message for the public key pk. +func (pk PublicKeyG1) VerifyG2(api frontend.API, sig *SignatureG2, prehashed *sw_bls12381.G2Affine) error { + pairing, err := sw_bls12381.NewPairing(api) + if err != nil { + return fmt.Errorf("new pairing: %w", err) + } + fp, err := emulated.NewField[sw_bls12381.BaseField](api) + if err != nil { + return fmt.Errorf("new field: %w", err) + } + + // public key cannot be infinity + xtest := fp.IsZero(&pk.X) + ytest := fp.IsZero(&pk.Y) + pubTest := api.Or(xtest, ytest) + api.AssertIsEqual(pubTest, 0) + + // prime order subgroup checks + pairing.AssertIsOnG1((*sw_bls12381.G1Affine)(&pk)) + pairing.AssertIsOnG2((*sw_bls12381.G2Affine)(sig)) + + var g1GNeg bls12381.G1Affine + _, _, g1Gen, _ := bls12381.Generators() + g1GNeg.Neg(&g1Gen) + g1GN := sw_bls12381.NewG1Affine(g1GNeg) + + if err := pairing.PairingCheck( + []*sw_bls12381.G1Affine{&g1GN, (*sw_bls12381.G1Affine)(&pk)}, + []*sw_bls12381.G2Affine{(*sw_bls12381.G2Affine)(sig), prehashed}); err != nil { + return fmt.Errorf("pairing check failed: %w", err) + } + return nil +} + +// Verify asserts that the signature sig verifies for the message msg and public +// key pk. Message is not assumed to be prehashed, it will be hashed to G2 using +// message expansion and hash to G2. +func (pk PublicKeyG1) Verify(api frontend.API, sig *SignatureG2, msg []uints.U8) error { + g2, err := sw_bls12381.NewG2(api) + if err != nil { + return fmt.Errorf("new G2: %w", err) + } + h, err := g2.HashToG2(msg, []byte(g2_dst)) + if err != nil { + return fmt.Errorf("hash to G2: %w", err) + } + if err := pk.VerifyG2(api, sig, h); err != nil { + return fmt.Errorf("verify G2: %w", err) + } + return nil +} diff --git a/std/signature/bls/blssig_test.go b/std/signature/bls/blssig_test.go new file mode 100644 index 0000000000..41bd396caa --- /dev/null +++ b/std/signature/bls/blssig_test.go @@ -0,0 +1,66 @@ +package bls + +import ( + "encoding/hex" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" + "github.com/consensys/gnark/std/math/uints" + "github.com/consensys/gnark/test" +) + +type blsG2SigCircuit struct { + Pub PublicKeyG1 + Msg []uints.U8 + Sig SignatureG2 +} + +func (c *blsG2SigCircuit) Define(api frontend.API) error { + return c.Pub.Verify(api, &c.Sig, c.Msg) +} + +var testCasesG2Sig = []struct { + pubkey string + msg string + sig string + output bool +}{ + { + "a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a", + "5656565656565656565656565656565656565656565656565656565656565656", + "882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb", + true, + }, +} + +func TestBlsSigTestSolve(t *testing.T) { + assert := test.NewAssert(t) + + for _, tc := range testCasesG2Sig { + pubB, err := hex.DecodeString(tc.pubkey) + assert.NoError(err, "failed to decode pubkey hex") + msgB, err := hex.DecodeString(tc.msg) + assert.NoError(err, "failed to decode msg hex") + sigB, err := hex.DecodeString(tc.sig) + assert.NoError(err, "failed to decode sig hex") + + var pub bls12381.G1Affine + n, err := pub.SetBytes(pubB) + assert.NoError(err, "failed to set pubkey bytes") + assert.Equal(n, len(pubB), "pubkey bytes length mismatch") + var sig bls12381.G2Affine + n, err = sig.SetBytes(sigB) + assert.NoError(err, "failed to set signature bytes") + assert.Equal(n, len(sigB), "signature bytes length mismatch") + + err = test.IsSolved(&blsG2SigCircuit{Msg: make([]uints.U8, len(msgB))}, &blsG2SigCircuit{ + Pub: PublicKeyG1(sw_bls12381.NewG1Affine(pub)), + Msg: uints.NewU8Array(msgB), + Sig: SignatureG2(sw_bls12381.NewG2Affine(sig)), + }, ecc.BN254.ScalarField()) + assert.NoError(err, "test solve failed") + } +} diff --git a/std/signature/bls/doc.go b/std/signature/bls/doc.go new file mode 100644 index 0000000000..3973652b0c --- /dev/null +++ b/std/signature/bls/doc.go @@ -0,0 +1,25 @@ +// Package bls implements a subset of the BLS (Boneh-Lynn-Shacham) signature +// scheme. +// +// BLS signature scheme is a pairing-based signature scheme with aggregration. +// It allows to aggregate multiple signatures or multiple public keys into a +// single signature or public key. +// +// This package implements the scheme over the BLS12-381 curve. It currently +// implements the minimal public key size variation, where the public key is a +// single G1 point and the signature is a single G2 point. It uses the SSWU map +// for mapping messages to G2 points. +// +// See [IETF BLS draft] for more details on the BLS signature scheme. +// +// NB! This is experimental and work in progress gadget. The API is not stable +// and will be extended to support minimal-signature variant. +// +// NB! We currently don't have the native implmentation for signing in +// gnark-crypto. See [PR 314]. +// +// [IETF draft]: +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature +// +// [PR 314]: https://github.com/Consensys/gnark-crypto/pull/314 +package bls From e947f1b9ae90bedc3451cfef7c74d983e8f4ed3e Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 7 Jul 2025 23:30:53 +0000 Subject: [PATCH 084/105] feat: implement BlockSize for binary hashers --- std/hash/hash.go | 4 ++++ std/hash/ripemd160/ripemd160.go | 4 ++++ std/hash/sha2/sha2.go | 2 ++ std/hash/sha3/sha3.go | 2 ++ 4 files changed, 12 insertions(+) diff --git a/std/hash/hash.go b/std/hash/hash.go index c077fd0d37..ab1f1ffdeb 100644 --- a/std/hash/hash.go +++ b/std/hash/hash.go @@ -52,6 +52,10 @@ type BinaryHasher interface { // Size returns the number of bytes this hash function returns in a call to // [BinaryHasher.Sum]. Size() int + + // BlockSize returns the internal block size of the hash function. NB! This + // is different from [BinaryHasher.Size] which indicates the output size. + BlockSize() int } // BinaryFixedLengthHasher is like [BinaryHasher], but assumes the length of the diff --git a/std/hash/ripemd160/ripemd160.go b/std/hash/ripemd160/ripemd160.go index 2195067ff1..c483d31fd1 100644 --- a/std/hash/ripemd160/ripemd160.go +++ b/std/hash/ripemd160/ripemd160.go @@ -80,3 +80,7 @@ func (d *digest) Reset() { func (d *digest) Size() int { return 20 } + +func (d *digest) BlockSize() int { + return 64 +} diff --git a/std/hash/sha2/sha2.go b/std/hash/sha2/sha2.go index 3ff359b2f9..b09c61ef76 100644 --- a/std/hash/sha2/sha2.go +++ b/std/hash/sha2/sha2.go @@ -187,6 +187,8 @@ func (d *digest) Reset() { func (d *digest) Size() int { return 32 } +func (d *digest) BlockSize() int { return 64 } + func (d *digest) mod64(v frontend.Variable) frontend.Variable { lower, _ := bitslice.Partition(d.api, v, 6, bitslice.WithNbDigits(64)) return lower diff --git a/std/hash/sha3/sha3.go b/std/hash/sha3/sha3.go index 4b044fa296..c41c96d960 100644 --- a/std/hash/sha3/sha3.go +++ b/std/hash/sha3/sha3.go @@ -27,6 +27,8 @@ func (d *digest) Write(in []uints.U8) { func (d *digest) Size() int { return d.outputLen } +func (d *digest) BlockSize() int { return d.rate } + func (d *digest) Reset() { d.in = nil d.state = newState() From 0af1c02fa411e15131110690e997ae171fd94d12 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 7 Jul 2025 23:31:32 +0000 Subject: [PATCH 085/105] feat: refactor expandMsgXmd to expand package --- std/algebra/emulated/sw_bls12381/map_to_g2.go | 4 +- std/hash/expand/doc.go | 15 +++ std/hash/expand/expand.go | 107 ++++++++++++++++++ std/hash/{tofield => expand}/expand_test.go | 27 ++--- std/hash/tofield/doc.go | 3 - std/hash/tofield/expand.go | 102 ----------------- 6 files changed, 136 insertions(+), 122 deletions(-) create mode 100644 std/hash/expand/doc.go create mode 100644 std/hash/expand/expand.go rename std/hash/{tofield => expand}/expand_test.go (92%) delete mode 100644 std/hash/tofield/doc.go delete mode 100644 std/hash/tofield/expand.go diff --git a/std/algebra/emulated/sw_bls12381/map_to_g2.go b/std/algebra/emulated/sw_bls12381/map_to_g2.go index a50fb58ada..1315c95307 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g2.go @@ -9,7 +9,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/algebra/emulated/fields_bls12381" - "github.com/consensys/gnark/std/hash/tofield" + "github.com/consensys/gnark/std/hash/expand" "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/std/math/uints" ) @@ -28,7 +28,7 @@ func (g2 *G2) HashToG2(msg []uints.U8, dst []byte) (*G2Affine, error) { // 6. return P lenPerBaseElement := len_per_base_element lenInBytes := lenPerBaseElement * 4 - uniformBytes, e := tofield.ExpandMsgXmd(g2.api, msg, dst, lenInBytes) + uniformBytes, e := expand.ExpandMsgXmd(g2.api, msg, dst, lenInBytes) if e != nil { return &G2Affine{}, e } diff --git a/std/hash/expand/doc.go b/std/hash/expand/doc.go new file mode 100644 index 0000000000..554122a390 --- /dev/null +++ b/std/hash/expand/doc.go @@ -0,0 +1,15 @@ +// Package expand implements message expansion according to RFC9380. +// +// This package implements the expand_message_xmd function from [RFC9380] +// Section 5.3.1. The package uses hard-coded hash function SHA2-256. +// +// Please open issue in [gnark repository] in case there is a need for using +// other hash functions beyond SHA2-256 or expand_message_xof using +// extendable-output function (SHAKE3 family etc.). We currently have +// implemented only specific instance for compatibility with gnark-crypto, which +// is available at [github.com/consensys/gnark-crypto/field/hash]. +// +// [RFC9380]: https://datatracker.ietf.org/doc/html/rfc9380#name-expand_message_xmd +// [gnark repository]: https://github.com/consensys/gnark +// [github.com/consensys/gnark-crypto/field/hash]: https://pkg.go.dev/github.com/consensys/gnark-crypto@master/field/hash +package expand diff --git a/std/hash/expand/expand.go b/std/hash/expand/expand.go new file mode 100644 index 0000000000..87f66a2caa --- /dev/null +++ b/std/hash/expand/expand.go @@ -0,0 +1,107 @@ +package expand + +import ( + "errors" + "fmt" + + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/hash/sha2" + "github.com/consensys/gnark/std/math/uints" +) + +// ExpandMsgXmd implements the expand_message_xmd function from [RFC9380 Section +// 5.3.1]. It is hardcoded to use SHA2-256 as the hash function (but can be made +// optionally configurable in the future). +// +// It expand a message `msg` with a fixed domain separation tag `dst` to a slice +// of length `lenInBytes`. +// +// For gnark-crypto implementation see [gnark-crypto]. +// +// [RFC9380 Section 5.3.1]: https://datatracker.ietf.org/doc/html/rfc9380#name-expand_message_xmd +// [gnark-crypto]: https://github.com/consensys/gnark-crypto/blob/master/field/hash/hashutils.go#L11 +func ExpandMsgXmd(api frontend.API, msg []uints.U8, dst []byte, lenInBytes int) ([]uints.U8, error) { + h, err := sha2.New(api) + if err != nil { + return nil, fmt.Errorf("new hasher: %w", err) + } + + ell := (lenInBytes + h.Size() - 1) / h.Size() // ceil(len_in_bytes / b_in_bytes) + if ell > 255 { + return nil, errors.New("invalid lenInBytes") + } + if len(dst) > 255 { + return nil, errors.New("invalid domain size (>255 bytes)") + } + sizeDomain := uint8(len(dst)) + + // Z_pad = I2OSP(0, r_in_bytes) + Z_pad := uints.NewU8Array(make([]uint8, h.BlockSize())) + // l_i_b_str = I2OSP(len_in_bytes, 2) + l_i_b_str := uints.NewU8Array([]uint8{ + uint8(lenInBytes >> 8), + uint8(lenInBytes), + }) + // DST_prime = DST ∥ I2OSP(len(DST), 1) + DST_prime := uints.NewU8Array(append(dst, sizeDomain)) + // b₀ = H(Z_pad ∥ msg ∥ l_i_b_str ∥ I2OSP(0, 1) ∥ DST_prime) + h.Write(Z_pad) + h.Write(msg) + h.Write(l_i_b_str) + h.Write([]uints.U8{uints.NewU8(0)}) + h.Write(DST_prime) + b0 := h.Sum() + + h, err = sha2.New(api) + if err != nil { + return nil, fmt.Errorf("new hasher for b1: %w", err) + } + // b₁ = H(b₀ ∥ I2OSP(1, 1) ∥ DST_prime) + h.Write(b0) + h.Write([]uints.U8{uints.NewU8(1)}) + h.Write(DST_prime) + b1 := h.Sum() + + res := make([]uints.U8, lenInBytes) + copy(res[:h.Size()], b1) + + for i := 2; i <= ell; i++ { + h, err = sha2.New(api) + if err != nil { + return nil, fmt.Errorf("new hasher for b%d: %w", i, err) + } + + // b_i = H(strxor(b₀, b_(i - 1)) ∥ I2OSP(i, 1) ∥ DST_prime) + strxor := make([]uints.U8, h.Size()) + for j := 0; j < h.Size(); j++ { + // TODO: use here uints.Bytes Xor when finally implemented + strxor[j], err = xor(api, b0[j], b1[j]) + if err != nil { + return res, err + } + } + h.Write(strxor) + h.Write([]uints.U8{uints.NewU8(uint8(i))}) + h.Write(DST_prime) + b1 = h.Sum() + copy(res[h.Size()*(i-1):min(h.Size()*i, len(res))], b1) + } + + return res, nil +} + +func xor(api frontend.API, a, b uints.U8) (uints.U8, error) { + aBits := api.ToBinary(a.Val, 8) + bBits := api.ToBinary(b.Val, 8) + cBits := make([]frontend.Variable, 8) + + for i := 0; i < 8; i++ { + cBits[i] = api.Xor(aBits[i], bBits[i]) + } + + uapi, err := uints.New[uints.U32](api) + if err != nil { + return uints.NewU8(255), err + } + return uapi.ByteValueOf(api.FromBinary(cBits...)), nil +} diff --git a/std/hash/tofield/expand_test.go b/std/hash/expand/expand_test.go similarity index 92% rename from std/hash/tofield/expand_test.go rename to std/hash/expand/expand_test.go index 1df5271c59..0ed09b4e58 100644 --- a/std/hash/tofield/expand_test.go +++ b/std/hash/expand/expand_test.go @@ -1,4 +1,4 @@ -package tofield +package expand import ( "encoding/hex" @@ -12,7 +12,7 @@ import ( type expandMsgXmdCircuit struct { Msg []uints.U8 - Dst []uint8 + Dst []byte Len int Expected []uints.U8 } @@ -42,6 +42,7 @@ func (c *expandMsgXmdCircuit) Define(api frontend.API) error { // adapted from gnark-crypto/field/hash/hashutils_test.go func TestExpandMsgXmd(t *testing.T) { + assert := test.NewAssert(t) //name := "expand_message_xmd" dst := "QUUX-V01-CS02-with-expander-SHA256-128" //hash := "SHA256" @@ -136,23 +137,19 @@ func TestExpandMsgXmd(t *testing.T) { } for _, testCase := range testCases { - uniformBytes := make([]uint8, len(testCase.uniformBytesHex)>>1) - hex.Decode(uniformBytes, []uint8(testCase.uniformBytesHex)) - witness := expandMsgXmdCircuit{ - Msg: uints.NewU8Array([]uint8(testCase.msg)), - Dst: []uint8(dst), - Len: testCase.lenInBytes, - Expected: uints.NewU8Array(uniformBytes), - } + uniformBytes, err := hex.DecodeString(testCase.uniformBytesHex) + assert.NoError(err, "failed to decode uniform bytes hex") circuit := expandMsgXmdCircuit{ - Msg: uints.NewU8Array(make([]uint8, len(testCase.msg))), + Msg: make([]uints.U8, len(testCase.msg)), Dst: []uint8(dst), Len: testCase.lenInBytes, - Expected: uints.NewU8Array(uniformBytes), + Expected: make([]uints.U8, len(uniformBytes)), } - err := test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) - if err != nil { - t.Fatal(err) + witness := expandMsgXmdCircuit{ + Msg: uints.NewU8Array([]byte(testCase.msg)), + Expected: uints.NewU8Array(uniformBytes), } + err = test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) + assert.NoError(err, "circuit should be solved") } } diff --git a/std/hash/tofield/doc.go b/std/hash/tofield/doc.go deleted file mode 100644 index b1612201a7..0000000000 --- a/std/hash/tofield/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package tofield provides ZKP circuits for expanding messages to field elements, according to -// RFC9380 (section 5.3.1). -package tofield diff --git a/std/hash/tofield/expand.go b/std/hash/tofield/expand.go deleted file mode 100644 index f6d3e07e1a..0000000000 --- a/std/hash/tofield/expand.go +++ /dev/null @@ -1,102 +0,0 @@ -package tofield - -import ( - "errors" - - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/std/hash/sha2" - "github.com/consensys/gnark/std/math/uints" -) - -const ( - block_size = 64 -) - -// ExpandMsgXmd expands msg to a slice of lenInBytes bytes according to RFC9380 (section 5.3.1) -// Spec: https://datatracker.ietf.org/doc/html/rfc9380#name-expand_message_xmd (hashutils.go) -// Implementation was adapted from gnark-crypto/field/hash.ExpandMsgXmd. -func ExpandMsgXmd(api frontend.API, msg []uints.U8, dst []byte, lenInBytes int) ([]uints.U8, error) { - h, e := sha2.New(api) - if e != nil { - return nil, e - } - - ell := (lenInBytes + h.Size() - 1) / h.Size() // ceil(len_in_bytes / b_in_bytes) - if ell > 255 { - return nil, errors.New("invalid lenInBytes") - } - if len(dst) > 255 { - return nil, errors.New("invalid domain size (>255 bytes)") - } - sizeDomain := uint8(len(dst)) - - dst_prime := make([]uints.U8, len(dst)+1) - copy(dst_prime, uints.NewU8Array(dst)) - dst_prime[len(dst)] = uints.NewU8(uint8(sizeDomain)) - - Z_pad_raw := make([]uint8, block_size) - Z_pad := uints.NewU8Array(Z_pad_raw) - h.Write(Z_pad) - h.Write(msg) - h.Write([]uints.U8{uints.NewU8(uint8(lenInBytes >> 8)), uints.NewU8(uint8(lenInBytes)), uints.NewU8(0)}) - h.Write(dst_prime) - b0 := h.Sum() - - h, e = sha2.New(api) - if e != nil { - return nil, e - } - h.Write(b0) - h.Write([]uints.U8{uints.NewU8(1)}) - h.Write(dst_prime) - b1 := h.Sum() - - res := make([]uints.U8, lenInBytes) - copy(res[:h.Size()], b1) - - for i := 2; i <= ell; i++ { - h, e = sha2.New(api) - if e != nil { - return nil, e - } - - // b_i = H(strxor(b₀, b_(i - 1)) ∥ I2OSP(i, 1) ∥ DST_prime) - strxor := make([]uints.U8, h.Size()) - for j := 0; j < h.Size(); j++ { - strxor[j], e = xor(api, b0[j], b1[j]) - if e != nil { - return res, e - } - } - h.Write(strxor) - h.Write([]uints.U8{uints.NewU8(uint8(i))}) - h.Write(dst_prime) - b1 = h.Sum() - copy(res[h.Size()*(i-1):min(h.Size()*i, len(res))], b1) - } - - return res, nil -} - -func xor(api frontend.API, a, b uints.U8) (uints.U8, error) { - aBits := api.ToBinary(a.Val, 8) - bBits := api.ToBinary(b.Val, 8) - cBits := make([]frontend.Variable, 8) - - for i := 0; i < 8; i++ { - cBits[i] = api.Xor(aBits[i], bBits[i]) - } - - uapi, err := uints.New[uints.U32](api) - if err != nil { - return uints.NewU8(255), err - } - return uapi.ByteValueOf(api.FromBinary(cBits...)), nil -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} From 81fc763dcb5dfbb788c51869ea73756885a8be19 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 8 Jul 2025 00:08:42 +0000 Subject: [PATCH 086/105] docs: add TODOs for now --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 9 +++++++++ std/hash/expand/expand.go | 1 + 2 files changed, 10 insertions(+) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 20c908c471..d45ae3c674 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -6,6 +6,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/uints" ) func (g1 *G1) evalFixedPolynomial(monic bool, coefficients []fp.Element, x *baseEl) *baseEl { @@ -180,3 +181,11 @@ func (g1 *G1) MapToG1(u *baseEl) (*G1Affine, error) { z = g1.ClearCofactor(z) return z, nil } + +func (g1 *G1) EncodeToG1(msg []uints.U8, dst []byte) (*G1Affine, error) { + panic("todo") +} + +func (g1 *G1) HashToG1(msg []uints.U8, dst []byte) (*G1Affine, error) { + panic("Todo") +} diff --git a/std/hash/expand/expand.go b/std/hash/expand/expand.go index 87f66a2caa..cb93879d00 100644 --- a/std/hash/expand/expand.go +++ b/std/hash/expand/expand.go @@ -91,6 +91,7 @@ func ExpandMsgXmd(api frontend.API, msg []uints.U8, dst []byte, lenInBytes int) } func xor(api frontend.API, a, b uints.U8) (uints.U8, error) { + // TODO: when done with conversion package then can remove this function aBits := api.ToBinary(a.Val, 8) bBits := api.ToBinary(b.Val, 8) cBits := make([]frontend.Variable, 8) From f38a57b4a3ee848d323a0a1cc590757fefa17705 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 8 Jul 2025 00:09:21 +0000 Subject: [PATCH 087/105] feat: refactor g2 hash to curve --- std/algebra/emulated/sw_bls12381/map_to_g2.go | 175 +++++++++++------- 1 file changed, 109 insertions(+), 66 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g2.go b/std/algebra/emulated/sw_bls12381/map_to_g2.go index 1315c95307..684711b3fb 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g2.go @@ -14,72 +14,6 @@ import ( "github.com/consensys/gnark/std/math/uints" ) -const ( - len_per_base_element = 64 -) - -func (g2 *G2) HashToG2(msg []uints.U8, dst []byte) (*G2Affine, error) { - // Steps: - // 1. u = hash_to_field(msg, 2) - // 2. Q0 = map_to_curve(u[0]) - // 3. Q1 = map_to_curve(u[1]) - // 4. R = Q0 + Q1 # Point addition - // 5. P = clear_cofactor(R) - // 6. return P - lenPerBaseElement := len_per_base_element - lenInBytes := lenPerBaseElement * 4 - uniformBytes, e := expand.ExpandMsgXmd(g2.api, msg, dst, lenInBytes) - if e != nil { - return &G2Affine{}, e - } - - ele1 := bytesToElement(g2.api, g2.fp, uniformBytes[:lenPerBaseElement]) - ele2 := bytesToElement(g2.api, g2.fp, uniformBytes[lenPerBaseElement:lenPerBaseElement*2]) - ele3 := bytesToElement(g2.api, g2.fp, uniformBytes[lenPerBaseElement*2:lenPerBaseElement*3]) - ele4 := bytesToElement(g2.api, g2.fp, uniformBytes[lenPerBaseElement*3:]) - - // we will still do iso_map before point addition, as we do not have point addition in E' (yet) - Q0, err := g2.MapToCurve2(&fields_bls12381.E2{A0: *ele1, A1: *ele2}) - if err != nil { - return nil, fmt.Errorf("map to curve Q1: %w", err) - } - Q1, err := g2.MapToCurve2(&fields_bls12381.E2{A0: *ele3, A1: *ele4}) - if err != nil { - return nil, fmt.Errorf("map to curve Q2: %w", err) - } - Q0 = g2.isogeny(Q0) - Q1 = g2.isogeny(Q1) - - R := g2.AddUnified(Q0, Q1) - - return g2.ClearCofactor(R), nil -} - -func bytesToElement(api frontend.API, fp *emulated.Field[emulated.BLS12381Fp], data []uints.U8) *emulated.Element[emulated.BLS12381Fp] { - // data in BE, need to convert to LE - slices.Reverse(data) - - bits := make([]frontend.Variable, len(data)*8) - for i := 0; i < len(data); i++ { - u8 := data[i] - u8Bits := api.ToBinary(u8.Val, 8) - for j := 0; j < 8; j++ { - bits[i*8+j] = u8Bits[j] - } - } - - cutoff := 17 - tailBits, headBits := bits[:cutoff*8], bits[cutoff*8:] - tail := fp.FromBits(tailBits...) - head := fp.FromBits(headBits...) - - byteMultiplier := big.NewInt(256) - headMultiplier := byteMultiplier.Exp(byteMultiplier, big.NewInt(int64(cutoff)), big.NewInt(0)) - head = fp.MulConst(head, headMultiplier) - - return fp.Add(head, tail) -} - func (g2 *G2) evalFixedPolynomial(monic bool, coefficients []bls12381.E2, x *fields_bls12381.E2) *fields_bls12381.E2 { emuCoefficients := make([]*fields_bls12381.E2, len(coefficients)) for i := 0; i < len(coefficients); i++ { @@ -264,3 +198,112 @@ func (g2 *G2) MapToG2(u *fields_bls12381.E2) (*G2Affine, error) { z = g2.ClearCofactor(z) return z, nil } + +// EncodeToG2 hashes a message to a point on the G2 curve using the SSWU map as +// defined in [RFC9380]. It is faster than [G2.HashToG2], but the result is not +// uniformly distributed. Unsuitable as a random oracle. +// +// dst stands for "domain separation tag", a string unique to the construction +// using the hash function +// +// // This method corresponds to the [bls12381.EncodeToG2] method in gnark-crypto. +// +// [RFC9380]: https://www.rfc-editor.org/rfc/rfc9380.html#roadmap +func (g2 *G2) EncodeToG2(msg []uints.U8, dst []byte) (*G2Affine, error) { + const L = 64 + els := make([]*emulated.Element[BaseField], 2) + uniformBytes, err := expand.ExpandMsgXmd(g2.api, msg, dst, len(els)*L) + if err != nil { + return nil, fmt.Errorf("expand msg: %w", err) + } + + for i := range els { + // TODO: use conversion package when done + els[i] = bytesToElement(g2.api, g2.fp, uniformBytes[i*L:(i+1)*L]) + } + R, err := g2.MapToCurve2(&fields_bls12381.E2{A0: *els[0], A1: *els[1]}) + if err != nil { + return nil, fmt.Errorf("map to curve: %w", err) + } + Q := g2.isogeny(R) + return g2.ClearCofactor(Q), nil +} + +// HashToG2 hashes a message to a point on the G2 curve using the SSWU map as +// defined in [RFC9380]. It is slower than [G2.EncodeToG2], but usable as a +// random oracle. +// +// dst stands for "domain separation tag", a string unique to the construction +// using the hash function. +// +// This method corresponds to the [bls12381.HashToG2] method in gnark-crypto. +// +// [RFC9380]: https://www.rfc-editor.org/rfc/rfc9380.html#roadmap +func (g2 *G2) HashToG2(msg []uints.U8, dst []byte) (*G2Affine, error) { + // Steps: + // 1. u = hash_to_field(msg, 4) + // 2. Q0 = map_to_curve({u[0], u[1]}) + // 3. Q1 = map_to_curve({u[2], u[3]}) + // 4. R = Q0 + Q1 # Point addition + // 5. P = clear_cofactor(R) + // 6. return P + + // we use 64 bytes per base element, this is 48 bytes for the base field and + // 16 bytes for security parameter. Copied from gnark-crypto + // + // corresponds to fp.Hash method which we don't explicitly have in gnark + const L = 64 + els := make([]*emulated.Element[BaseField], 4) + uniformBytes, err := expand.ExpandMsgXmd(g2.api, msg, dst, len(els)*L) + if err != nil { + return nil, fmt.Errorf("expand msg: %w", err) + } + for i := range els { + // TODO: use conversion package when done + els[i] = bytesToElement(g2.api, g2.fp, uniformBytes[i*L:(i+1)*L]) + } + + // we will still do iso_map before point addition, as we do not have point addition in E' (yet) + Q0, err := g2.MapToCurve2(&fields_bls12381.E2{A0: *els[0], A1: *els[1]}) + if err != nil { + return nil, fmt.Errorf("map to curve Q1: %w", err) + } + Q1, err := g2.MapToCurve2(&fields_bls12381.E2{A0: *els[2], A1: *els[3]}) + if err != nil { + return nil, fmt.Errorf("map to curve Q2: %w", err) + } + Q0 = g2.isogeny(Q0) + Q1 = g2.isogeny(Q1) + + R := g2.AddUnified(Q0, Q1) + + return g2.ClearCofactor(R), nil + // R := g2.AddUnified(Q0, Q1) + // return g2.ClearCofactor(R), nil +} + +func bytesToElement(api frontend.API, fp *emulated.Field[emulated.BLS12381Fp], data []uints.U8) *emulated.Element[BaseField] { + // TODO: remove when conversion package is done + // data in BE, need to convert to LE + slices.Reverse(data) + + bits := make([]frontend.Variable, len(data)*8) + for i := 0; i < len(data); i++ { + u8 := data[i] + u8Bits := api.ToBinary(u8.Val, 8) + for j := 0; j < 8; j++ { + bits[i*8+j] = u8Bits[j] + } + } + + cutoff := 17 + tailBits, headBits := bits[:cutoff*8], bits[cutoff*8:] + tail := fp.FromBits(tailBits...) + head := fp.FromBits(headBits...) + + byteMultiplier := big.NewInt(256) + headMultiplier := byteMultiplier.Exp(byteMultiplier, big.NewInt(int64(cutoff)), big.NewInt(0)) + head = fp.MulConst(head, headMultiplier) + + return fp.Add(head, tail) +} From 6f7316d711088894b9500254b9fe096f37e2f9fd Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 8 Jul 2025 08:42:51 +0000 Subject: [PATCH 088/105] feat: implement encode and hash to G1 --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 56 ++++++++++++- .../emulated/sw_bls12381/map_to_g1_test.go | 78 +++++++++++++++++++ 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index d45ae3c674..332559d7e9 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -6,6 +6,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/hash/expand" "github.com/consensys/gnark/std/math/uints" ) @@ -182,10 +183,61 @@ func (g1 *G1) MapToG1(u *baseEl) (*G1Affine, error) { return z, nil } +// EncodeToG1 hashes a message to a point on the G1 curve using the SSWU map as +// defined in [RFC9380]. It is faster than [G1.HashToG1], but the result is not +// uniformly distributed. Unsuitable as a random oracle. +// +// dst stands for "domain separation tag", a string unique to the construction +// using the hash function +// +// // This method corresponds to the [bls12381.EncodeToG1] method in gnark-crypto. +// +// [RFC9380]: https://www.rfc-editor.org/rfc/rfc9380.html#roadmap func (g1 *G1) EncodeToG1(msg []uints.U8, dst []byte) (*G1Affine, error) { - panic("todo") + uniformBytes, err := expand.ExpandMsgXmd(g1.api, msg, dst, secureBaseElementLen) + if err != nil { + return nil, fmt.Errorf("expand msg: %w", err) + } + el := bytesToElement(g1.api, g1.curveF, uniformBytes) + R, err := g1.MapToCurve1(el) + if err != nil { + return nil, fmt.Errorf("map to curve: %w", err) + } + R = g1.isogeny(R) + R = g1.ClearCofactor(R) + return R, nil } +// HashToG1 hashes a message to a point on the G1 curve using the SSWU map as +// defined in [RFC9380]. It is slower than [G1.EncodeToG1], but usable as a +// random oracle. +// +// dst stands for "domain separation tag", a string unique to the construction +// using the hash function. +// +// This method corresponds to the [bls12381.HashToG1] method in gnark-crypto. +// +// [RFC9380]: https://www.rfc-editor.org/rfc/rfc9380.html#roadmap func (g1 *G1) HashToG1(msg []uints.U8, dst []byte) (*G1Affine, error) { - panic("Todo") + els := make([]*baseEl, 2) + uniformBytes, err := expand.ExpandMsgXmd(g1.api, msg, dst, len(els)*secureBaseElementLen) + if err != nil { + return nil, fmt.Errorf("expand msg: %w", err) + } + for i := range els { + els[i] = bytesToElement(g1.api, g1.curveF, uniformBytes[i*secureBaseElementLen:(i+1)*secureBaseElementLen]) + } + Q0, err := g1.MapToCurve1(els[0]) + if err != nil { + return nil, fmt.Errorf("map to curve Q0: %w", err) + } + Q1, err := g1.MapToCurve1(els[1]) + if err != nil { + return nil, fmt.Errorf("map to curve Q1: %w", err) + } + R0 := g1.isogeny(Q0) + R1 := g1.isogeny(Q1) + R := g1.add(R0, R1) + R = g1.ClearCofactor(R) + return R, nil } diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1_test.go b/std/algebra/emulated/sw_bls12381/map_to_g1_test.go index 24d63e5f35..39b8507f06 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1_test.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1_test.go @@ -1,6 +1,7 @@ package sw_bls12381 import ( + "crypto/rand" "fmt" "testing" @@ -10,6 +11,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/math/uints" "github.com/consensys/gnark/test" ) @@ -145,3 +147,79 @@ func TestIsogenyG1(t *testing.T) { err := test.IsSolved(&IsogenyG1Circuit{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } + +type EncodeToG1Circuit struct { + Msg []uints.U8 + Res G1Affine + Dst []byte +} + +func (c *EncodeToG1Circuit) Define(api frontend.API) error { + g, err := NewG1(api) + if err != nil { + return fmt.Errorf("new G1: %w", err) + } + res, err := g.EncodeToG1(c.Msg, []byte(c.Dst)) + if err != nil { + return fmt.Errorf("encode to G1: %w", err) + } + + g.AssertIsEqual(res, &c.Res) + return nil +} + +func TestEncodeToG1(t *testing.T) { + assert := test.NewAssert(t) + dst := []byte("BLS12381G1Test") + for _, msgLen := range []int{0, 1, 31, 32, 33, 63, 64, 65} { + assert.Run(func(assert *test.Assert) { + msg := make([]byte, msgLen) + _, err := rand.Reader.Read(msg) + assert.NoError(err, "failed to generate random message") + res, err := bls12381.EncodeToG1(msg, dst) + assert.NoError(err, "failed to encode message to G1") + circuit := EncodeToG1Circuit{Msg: make([]uints.U8, msgLen), Dst: dst} + witness := EncodeToG1Circuit{Msg: uints.NewU8Array(msg), Res: NewG1Affine(res)} + err = test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) + assert.NoError(err, "solving failed") + }, fmt.Sprintf("msgLen=%d", msgLen)) + } +} + +type HashToG1Circuit struct { + Msg []uints.U8 + Res G1Affine + Dst []byte +} + +func (c *HashToG1Circuit) Define(api frontend.API) error { + g, err := NewG1(api) + if err != nil { + return fmt.Errorf("new G1: %w", err) + } + res, err := g.HashToG1(c.Msg, []byte(c.Dst)) + if err != nil { + return fmt.Errorf("hash to G1: %w", err) + } + + g.AssertIsEqual(res, &c.Res) + return nil +} + +func TestHashToG1(t *testing.T) { + assert := test.NewAssert(t) + dst := []byte("BLS12381G1Test") + for _, msgLen := range []int{0, 1, 31, 32, 33, 63, 64, 65} { + assert.Run(func(assert *test.Assert) { + msg := make([]byte, msgLen) + _, err := rand.Reader.Read(msg) + assert.NoError(err, "failed to generate random message") + res, err := bls12381.HashToG1(msg, dst) + assert.NoError(err, "failed to hash message to G1") + circuit := HashToG1Circuit{Msg: make([]uints.U8, msgLen), Dst: dst} + witness := HashToG1Circuit{Msg: uints.NewU8Array(msg), Res: NewG1Affine(res)} + err = test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) + assert.NoError(err, "solving failed") + }, fmt.Sprintf("msgLen=%d", msgLen)) + } +} From 5b3c2543ed9dfc55ba0450484b7bb7c6f3310bb3 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 8 Jul 2025 08:43:20 +0000 Subject: [PATCH 089/105] refactor: global const for secure base element sampling len --- std/algebra/emulated/sw_bls12381/map_to_g2.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g2.go b/std/algebra/emulated/sw_bls12381/map_to_g2.go index 684711b3fb..993bc41a8b 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g2.go @@ -211,7 +211,7 @@ func (g2 *G2) MapToG2(u *fields_bls12381.E2) (*G2Affine, error) { // [RFC9380]: https://www.rfc-editor.org/rfc/rfc9380.html#roadmap func (g2 *G2) EncodeToG2(msg []uints.U8, dst []byte) (*G2Affine, error) { const L = 64 - els := make([]*emulated.Element[BaseField], 2) + els := make([]*baseEl, 2) uniformBytes, err := expand.ExpandMsgXmd(g2.api, msg, dst, len(els)*L) if err != nil { return nil, fmt.Errorf("expand msg: %w", err) @@ -229,6 +229,11 @@ func (g2 *G2) EncodeToG2(msg []uints.U8, dst []byte) (*G2Affine, error) { return g2.ClearCofactor(Q), nil } +// secureBaseElementLen is the length of bytes for sampling uniform element from +// the base field. We use 64 bytes per base element, this is 48 bytes for the +// base field and 16 bytes for security parameter. Copied from gnark-crypto +const secureBaseElementLen = 64 + // HashToG2 hashes a message to a point on the G2 curve using the SSWU map as // defined in [RFC9380]. It is slower than [G2.EncodeToG2], but usable as a // random oracle. @@ -248,19 +253,15 @@ func (g2 *G2) HashToG2(msg []uints.U8, dst []byte) (*G2Affine, error) { // 5. P = clear_cofactor(R) // 6. return P - // we use 64 bytes per base element, this is 48 bytes for the base field and - // 16 bytes for security parameter. Copied from gnark-crypto - // // corresponds to fp.Hash method which we don't explicitly have in gnark - const L = 64 - els := make([]*emulated.Element[BaseField], 4) - uniformBytes, err := expand.ExpandMsgXmd(g2.api, msg, dst, len(els)*L) + els := make([]*baseEl, 4) + uniformBytes, err := expand.ExpandMsgXmd(g2.api, msg, dst, len(els)*secureBaseElementLen) if err != nil { return nil, fmt.Errorf("expand msg: %w", err) } for i := range els { // TODO: use conversion package when done - els[i] = bytesToElement(g2.api, g2.fp, uniformBytes[i*L:(i+1)*L]) + els[i] = bytesToElement(g2.api, g2.fp, uniformBytes[i*secureBaseElementLen:(i+1)*secureBaseElementLen]) } // we will still do iso_map before point addition, as we do not have point addition in E' (yet) From 78c37dc2928ebb1102592cf5920a5d084123bac0 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 8 Jul 2025 08:43:37 +0000 Subject: [PATCH 090/105] test: implement encode/hash to G2 tests --- .../emulated/sw_bls12381/map_to_g2_test.go | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g2_test.go b/std/algebra/emulated/sw_bls12381/map_to_g2_test.go index 3b66b3db71..dc96bc45dc 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g2_test.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g2_test.go @@ -1,6 +1,8 @@ package sw_bls12381 import ( + "crypto/rand" + "fmt" "testing" "github.com/consensys/gnark-crypto/ecc" @@ -8,6 +10,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/algebra/emulated/fields_bls12381" + "github.com/consensys/gnark/std/math/uints" "github.com/consensys/gnark/test" ) @@ -141,3 +144,79 @@ func TestMapToG2(t *testing.T) { err := test.IsSolved(&MapToG2Circuit{}, &assignment, ecc.BN254.ScalarField()) assert.NoError(err) } + +type EncodeToG2Circuit struct { + Msg []uints.U8 + Res G2Affine + Dst []byte +} + +func (c *EncodeToG2Circuit) Define(api frontend.API) error { + g, err := NewG2(api) + if err != nil { + return fmt.Errorf("new G1: %w", err) + } + res, err := g.EncodeToG2(c.Msg, []byte(c.Dst)) + if err != nil { + return fmt.Errorf("encode to G1: %w", err) + } + + g.AssertIsEqual(res, &c.Res) + return nil +} + +func TestEncodeToG2(t *testing.T) { + assert := test.NewAssert(t) + dst := []byte("BLS12381G1Test") + for _, msgLen := range []int{0, 1, 31, 32, 33, 63, 64, 65} { + assert.Run(func(assert *test.Assert) { + msg := make([]byte, msgLen) + _, err := rand.Reader.Read(msg) + assert.NoError(err, "failed to generate random message") + res, err := bls12381.EncodeToG2(msg, dst) + assert.NoError(err, "failed to encode message to G2") + circuit := EncodeToG2Circuit{Msg: make([]uints.U8, msgLen), Dst: dst} + witness := EncodeToG2Circuit{Msg: uints.NewU8Array(msg), Res: NewG2Affine(res)} + err = test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) + assert.NoError(err, "solving failed") + }, fmt.Sprintf("msgLen=%d", msgLen)) + } +} + +type HashToG2Circuit struct { + Msg []uints.U8 + Res G2Affine + Dst []byte +} + +func (c *HashToG2Circuit) Define(api frontend.API) error { + g, err := NewG2(api) + if err != nil { + return fmt.Errorf("new G1: %w", err) + } + res, err := g.HashToG2(c.Msg, []byte(c.Dst)) + if err != nil { + return fmt.Errorf("hash to G1: %w", err) + } + + g.AssertIsEqual(res, &c.Res) + return nil +} + +func TestHashToG2(t *testing.T) { + assert := test.NewAssert(t) + dst := []byte("BLS12381G1Test") + for _, msgLen := range []int{0, 1, 31, 32, 33, 63, 64, 65} { + assert.Run(func(assert *test.Assert) { + msg := make([]byte, msgLen) + _, err := rand.Reader.Read(msg) + assert.NoError(err, "failed to generate random message") + res, err := bls12381.HashToG2(msg, dst) + assert.NoError(err, "failed to hash message to G2") + circuit := HashToG2Circuit{Msg: make([]uints.U8, msgLen), Dst: dst} + witness := HashToG2Circuit{Msg: uints.NewU8Array(msg), Res: NewG2Affine(res)} + err = test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) + assert.NoError(err, "solving failed") + }, fmt.Sprintf("msgLen=%d", msgLen)) + } +} From d7c7fe66ddb8d667a4c085525e0380770e468ba0 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 8 Jul 2025 08:43:50 +0000 Subject: [PATCH 091/105] refactor: rename BLS sig test --- std/signature/bls/blssig_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/signature/bls/blssig_test.go b/std/signature/bls/blssig_test.go index 41bd396caa..68a329c499 100644 --- a/std/signature/bls/blssig_test.go +++ b/std/signature/bls/blssig_test.go @@ -36,7 +36,7 @@ var testCasesG2Sig = []struct { }, } -func TestBlsSigTestSolve(t *testing.T) { +func TestMinimalPublicKeyTestSolve(t *testing.T) { assert := test.NewAssert(t) for _, tc := range testCasesG2Sig { From c3f287c8ba23f8743fab1acceb85d64fe145f1e4 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 8 Jul 2025 08:50:39 +0000 Subject: [PATCH 092/105] test: use single conditional circuit for unified add --- std/algebra/emulated/sw_bls12381/g2_test.go | 141 ++++++++++---------- 1 file changed, 71 insertions(+), 70 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/g2_test.go b/std/algebra/emulated/sw_bls12381/g2_test.go index 08025043dd..2c674d4262 100644 --- a/std/algebra/emulated/sw_bls12381/g2_test.go +++ b/std/algebra/emulated/sw_bls12381/g2_test.go @@ -51,8 +51,9 @@ func TestScalarMulG2TestSolve(t *testing.T) { } type addG2Circuit struct { - In1, In2 G2Affine - Res G2Affine + In1, In2 G2Affine + Res G2Affine + unifiedAdd bool // if true, use the unified addition method } func (c *addG2Circuit) Define(api frontend.API) error { @@ -60,7 +61,12 @@ func (c *addG2Circuit) Define(api frontend.API) error { if err != nil { return fmt.Errorf("new G2 struct: %w", err) } - res := g2.add(&c.In1, &c.In2) + var res *G2Affine + if c.unifiedAdd { + res = g2.AddUnified(&c.In1, &c.In2) + } else { + res = g2.add(&c.In1, &c.In2) + } g2.AssertIsEqual(res, &c.Res) return nil } @@ -76,8 +82,8 @@ func TestAddG2TestSolve(t *testing.T) { In2: NewG2Affine(in2), Res: NewG2Affine(res), } - err := test.IsSolved(&addG2Circuit{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) + err := test.IsSolved(&addG2Circuit{unifiedAdd: false}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err, "expected success for random inputs") } func TestAddG2FailureCaseTestSolve(t *testing.T) { @@ -90,24 +96,9 @@ func TestAddG2FailureCaseTestSolve(t *testing.T) { In2: NewG2Affine(in1), Res: NewG2Affine(res), } - err := test.IsSolved(&addG2Circuit{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&addG2Circuit{unifiedAdd: false}, &witness, ecc.BN254.ScalarField()) // the add() function cannot handle identical inputs - assert.Error(err) -} - -type addG2UnifiedCircuit struct { - In1, In2 G2Affine - Res G2Affine -} - -func (c *addG2UnifiedCircuit) Define(api frontend.API) error { - g2, err := NewG2(api) - if err != nil { - return err - } - res := g2.AddUnified(&c.In1, &c.In2) - g2.AssertIsEqual(res, &c.Res) - return nil + assert.Error(err, "expected solver error for identical inputs") } func TestAddG2UnifiedTestSolveAdd(t *testing.T) { @@ -116,12 +107,12 @@ func TestAddG2UnifiedTestSolveAdd(t *testing.T) { _, in2 := randomG1G2Affines() var res bls12381.G2Affine res.Add(&in1, &in2) - witness := addG2UnifiedCircuit{ + witness := addG2Circuit{ In1: NewG2Affine(in1), In2: NewG2Affine(in2), Res: NewG2Affine(res), } - err := test.IsSolved(&addG2UnifiedCircuit{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&addG2Circuit{unifiedAdd: true}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } @@ -130,12 +121,12 @@ func TestAddG2UnifiedTestSolveDbl(t *testing.T) { _, in1 := randomG1G2Affines() var res bls12381.G2Affine res.Double(&in1) - witness := addG2UnifiedCircuit{ + witness := addG2Circuit{ In1: NewG2Affine(in1), In2: NewG2Affine(in1), Res: NewG2Affine(res), } - err := test.IsSolved(&addG2UnifiedCircuit{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&addG2Circuit{unifiedAdd: true}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } @@ -146,50 +137,60 @@ func TestAddG2UnifiedTestSolveEdgeCases(t *testing.T) { np.Neg(&p) zero.Sub(&p, &p) - // p + (-p) == (0, 0) - witness := addG2UnifiedCircuit{ - In1: NewG2Affine(p), - In2: NewG2Affine(np), - Res: NewG2Affine(zero), - } - err := test.IsSolved(&addG2UnifiedCircuit{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - - // (-p) + p == (0, 0) - witness2 := addG2UnifiedCircuit{ - In1: NewG2Affine(np), - In2: NewG2Affine(p), - Res: NewG2Affine(zero), - } - err2 := test.IsSolved(&addG2UnifiedCircuit{}, &witness2, ecc.BN254.ScalarField()) - assert.NoError(err2) - - // p + (0, 0) == p - witness3 := addG2UnifiedCircuit{ - In1: NewG2Affine(p), - In2: NewG2Affine(zero), - Res: NewG2Affine(p), - } - err3 := test.IsSolved(&addG2UnifiedCircuit{}, &witness3, ecc.BN254.ScalarField()) - assert.NoError(err3) - - // (0, 0) + p == p - witness4 := addG2UnifiedCircuit{ - In1: NewG2Affine(zero), - In2: NewG2Affine(p), - Res: NewG2Affine(p), - } - err4 := test.IsSolved(&addG2UnifiedCircuit{}, &witness4, ecc.BN254.ScalarField()) - assert.NoError(err4) - - // (0, 0) + (0, 0) == (0, 0) - witness5 := addG2UnifiedCircuit{ - In1: NewG2Affine(zero), - In2: NewG2Affine(zero), - Res: NewG2Affine(zero), - } - err5 := test.IsSolved(&addG2UnifiedCircuit{}, &witness5, ecc.BN254.ScalarField()) - assert.NoError(err5) + assert.Run(func(assert *test.Assert) { + // p + (-p) == (0, 0) + witness := addG2Circuit{ + In1: NewG2Affine(p), + In2: NewG2Affine(np), + Res: NewG2Affine(zero), + } + err := test.IsSolved(&addG2Circuit{unifiedAdd: true}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + }, "case=inverse") + + assert.Run(func(assert *test.Assert) { + // (-p) + p == (0, 0) + witness2 := addG2Circuit{ + In1: NewG2Affine(np), + In2: NewG2Affine(p), + Res: NewG2Affine(zero), + } + err := test.IsSolved(&addG2Circuit{unifiedAdd: true}, &witness2, ecc.BN254.ScalarField()) + assert.NoError(err) + }, "case=inverse2") + + assert.Run(func(assert *test.Assert) { + // p + (0, 0) == p + witness3 := addG2Circuit{ + In1: NewG2Affine(p), + In2: NewG2Affine(zero), + Res: NewG2Affine(p), + } + err := test.IsSolved(&addG2Circuit{unifiedAdd: true}, &witness3, ecc.BN254.ScalarField()) + assert.NoError(err) + }, "case=zero") + + assert.Run(func(assert *test.Assert) { + // (0, 0) + p == p + witness4 := addG2Circuit{ + In1: NewG2Affine(zero), + In2: NewG2Affine(p), + Res: NewG2Affine(p), + } + err := test.IsSolved(&addG2Circuit{unifiedAdd: true}, &witness4, ecc.BN254.ScalarField()) + assert.NoError(err) + }, "case=zero2") + + assert.Run(func(assert *test.Assert) { + // (0, 0) + (0, 0) == (0, 0) + witness5 := addG2Circuit{ + In1: NewG2Affine(zero), + In2: NewG2Affine(zero), + Res: NewG2Affine(zero), + } + err5 := test.IsSolved(&addG2Circuit{unifiedAdd: true}, &witness5, ecc.BN254.ScalarField()) + assert.NoError(err5) + }, "case=zero3") } From 0a192e7280289f855a4e6e7ba3b7b75c781e6ab5 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 8 Jul 2025 09:09:47 +0000 Subject: [PATCH 093/105] fix: infinity point check --- std/signature/bls/bls_g1.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/signature/bls/bls_g1.go b/std/signature/bls/bls_g1.go index b4d88dad24..d6f8803cdf 100644 --- a/std/signature/bls/bls_g1.go +++ b/std/signature/bls/bls_g1.go @@ -31,10 +31,10 @@ func (pk PublicKeyG1) VerifyG2(api frontend.API, sig *SignatureG2, prehashed *sw return fmt.Errorf("new field: %w", err) } - // public key cannot be infinity + // public key cannot be infinity. Thus either coordinate has to be non-zero. xtest := fp.IsZero(&pk.X) ytest := fp.IsZero(&pk.Y) - pubTest := api.Or(xtest, ytest) + pubTest := api.And(xtest, ytest) api.AssertIsEqual(pubTest, 0) // prime order subgroup checks From 5154b23130d07116f4e604fda84a06575308fbc2 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 8 Jul 2025 09:10:40 +0000 Subject: [PATCH 094/105] fix: error message coordinate --- std/algebra/emulated/sw_bls12381/map_to_g2.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g2.go b/std/algebra/emulated/sw_bls12381/map_to_g2.go index 993bc41a8b..5745defea3 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g2.go @@ -267,11 +267,11 @@ func (g2 *G2) HashToG2(msg []uints.U8, dst []byte) (*G2Affine, error) { // we will still do iso_map before point addition, as we do not have point addition in E' (yet) Q0, err := g2.MapToCurve2(&fields_bls12381.E2{A0: *els[0], A1: *els[1]}) if err != nil { - return nil, fmt.Errorf("map to curve Q1: %w", err) + return nil, fmt.Errorf("map to curve Q0: %w", err) } Q1, err := g2.MapToCurve2(&fields_bls12381.E2{A0: *els[2], A1: *els[3]}) if err != nil { - return nil, fmt.Errorf("map to curve Q2: %w", err) + return nil, fmt.Errorf("map to curve Q1: %w", err) } Q0 = g2.isogeny(Q0) Q1 = g2.isogeny(Q1) From 5ae6e4cb3d6c8d42ffa45e2a71150bf7c8e4142e Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 8 Jul 2025 09:15:59 +0000 Subject: [PATCH 095/105] test: rename G1 to G2 in G2 tests --- std/algebra/emulated/sw_bls12381/map_to_g2_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g2_test.go b/std/algebra/emulated/sw_bls12381/map_to_g2_test.go index dc96bc45dc..e38b70a909 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g2_test.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g2_test.go @@ -154,11 +154,11 @@ type EncodeToG2Circuit struct { func (c *EncodeToG2Circuit) Define(api frontend.API) error { g, err := NewG2(api) if err != nil { - return fmt.Errorf("new G1: %w", err) + return fmt.Errorf("new G2: %w", err) } res, err := g.EncodeToG2(c.Msg, []byte(c.Dst)) if err != nil { - return fmt.Errorf("encode to G1: %w", err) + return fmt.Errorf("encode to G2: %w", err) } g.AssertIsEqual(res, &c.Res) @@ -167,7 +167,7 @@ func (c *EncodeToG2Circuit) Define(api frontend.API) error { func TestEncodeToG2(t *testing.T) { assert := test.NewAssert(t) - dst := []byte("BLS12381G1Test") + dst := []byte("BLS12381G2Test") for _, msgLen := range []int{0, 1, 31, 32, 33, 63, 64, 65} { assert.Run(func(assert *test.Assert) { msg := make([]byte, msgLen) @@ -192,11 +192,11 @@ type HashToG2Circuit struct { func (c *HashToG2Circuit) Define(api frontend.API) error { g, err := NewG2(api) if err != nil { - return fmt.Errorf("new G1: %w", err) + return fmt.Errorf("new G2: %w", err) } res, err := g.HashToG2(c.Msg, []byte(c.Dst)) if err != nil { - return fmt.Errorf("hash to G1: %w", err) + return fmt.Errorf("hash to G2: %w", err) } g.AssertIsEqual(res, &c.Res) @@ -205,7 +205,7 @@ func (c *HashToG2Circuit) Define(api frontend.API) error { func TestHashToG2(t *testing.T) { assert := test.NewAssert(t) - dst := []byte("BLS12381G1Test") + dst := []byte("BLS12381G2Test") for _, msgLen := range []int{0, 1, 31, 32, 33, 63, 64, 65} { assert.Run(func(assert *test.Assert) { msg := make([]byte, msgLen) From 82d14a1245169df0ae2f98b993c476a909a322ba Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 8 Jul 2025 09:21:33 +0000 Subject: [PATCH 096/105] docs: cleanup and explicit mention --- std/algebra/emulated/sw_bls12381/map_to_g2.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g2.go b/std/algebra/emulated/sw_bls12381/map_to_g2.go index 5745defea3..556844f8db 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g2.go @@ -279,12 +279,13 @@ func (g2 *G2) HashToG2(msg []uints.U8, dst []byte) (*G2Affine, error) { R := g2.AddUnified(Q0, Q1) return g2.ClearCofactor(R), nil - // R := g2.AddUnified(Q0, Q1) - // return g2.ClearCofactor(R), nil } func bytesToElement(api frontend.API, fp *emulated.Field[emulated.BLS12381Fp], data []uints.U8) *emulated.Element[BaseField] { - // TODO: remove when conversion package is done + // TODO(ivokub) NB! This function is a temporary workaround to convert bytes to an element. We will replace it soon. + // + // NB! it modifies data in place, but in the current usage it is not an issue as we only use it once per bytes + // data in BE, need to convert to LE slices.Reverse(data) From 9cf0c0d40112b5caa70ff4cc8522cf1a5b5bc448 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Fri, 23 May 2025 20:43:23 +0000 Subject: [PATCH 097/105] refactor: separate bytes from Long API --- std/math/uints/bytes.go | 102 ++++++++++++++++++++++++++++++ std/math/uints/uint8.go | 136 +++++++++++++++++----------------------- 2 files changed, 158 insertions(+), 80 deletions(-) create mode 100644 std/math/uints/bytes.go diff --git a/std/math/uints/bytes.go b/std/math/uints/bytes.go new file mode 100644 index 0000000000..90623a3323 --- /dev/null +++ b/std/math/uints/bytes.go @@ -0,0 +1,102 @@ +package uints + +import ( + "fmt" + + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/internal/kvstore" + "github.com/consensys/gnark/std/internal/logderivprecomp" + "github.com/consensys/gnark/std/rangecheck" +) + +// ctxKey is used to store the API in the key-value store. In case we +// re-initialize using [NewBytes] method, then we can reuse the existing API. This +// ensures we don't rebuild any of the tables. +type ctxKey struct{} + +// Bytes implements methods for manipulating bytes in circuit. Use [NewBytes] to +// create a new instance. +type Bytes struct { + api frontend.API + xorT, andT, orT *logderivprecomp.Precomputed + rchecker frontend.Rangechecker + allOne U8 +} + +// NewBytes creates a new [Bytes] instance which can manipulate bytes and byte +// arrays. For manipulating long integers, use [BinaryField] instead. +// +// This is a caching constructor, meaning that it will return the same instance +// if called multiple times. This is useful as internally it uses lookup tables +// for bitwise operations and it amortizes the cost of creating these lookup +// tables. +func NewBytes(api frontend.API) (*Bytes, error) { + if kv, ok := api.Compiler().(kvstore.Store); ok { + uapi := kv.GetKeyValue(ctxKey{}) + if tuapi, ok := uapi.(*Bytes); ok { + return tuapi, nil + } + } + xorT, err := logderivprecomp.New(api, xorHint, []uint{8}) + if err != nil { + return nil, fmt.Errorf("new xor table: %w", err) + } + andT, err := logderivprecomp.New(api, andHint, []uint{8}) + if err != nil { + return nil, fmt.Errorf("new and table: %w", err) + } + orT, err := logderivprecomp.New(api, orHint, []uint{8}) + if err != nil { + return nil, fmt.Errorf("new or table: %w", err) + } + rchecker := rangecheck.New(api) + bf := &Bytes{ + api: api, + xorT: xorT, + andT: andT, + orT: orT, + rchecker: rchecker, + } + // TODO: this is const. add way to init constants + bf.allOne = bf.ValueOf(0xff) + + // store the API in the key-value store so that can be easily reused + if kv, ok := api.(kvstore.Store); ok { + kv.SetKeyValue(ctxKey{}, bf) + } + return bf, nil +} + +// ValueOf returns a constrainted [U8] variable. For a constant value, use +// [NewU8] instead. +func (bf *Bytes) ValueOf(a frontend.Variable) U8 { + bf.rchecker.Check(a, 8) + return U8{Val: a, internal: true} +} + +func (bf *Bytes) twoArgFn(tbl *logderivprecomp.Precomputed, a ...U8) U8 { + if len(a) == 0 { + return NewU8(0) + } + if len(a) == 1 { + return a[0] + } + ret := tbl.Query(a[0].Val, a[1].Val)[0] + for i := 2; i < len(a); i++ { + ret = tbl.Query(ret, a[i].Val)[0] + } + return U8{Val: ret} +} + +func (bf *Bytes) Not(a U8) U8 { + ret := bf.xorT.Query(a.Val, bf.allOne.Val) + return U8{Val: ret[0]} +} + +func (bf *Bytes) And(a ...U8) U8 { return bf.twoArgFn(bf.andT, a...) } +func (bf *Bytes) Or(a ...U8) U8 { return bf.twoArgFn(bf.orT, a...) } +func (bf *Bytes) Xor(a ...U8) U8 { return bf.twoArgFn(bf.xorT, a...) } + +func (bf *Bytes) AssertIsEqual(a, b U8) { + bf.api.AssertIsEqual(a.Val, b.Val) +} diff --git a/std/math/uints/uint8.go b/std/math/uints/uint8.go index aaa86fef8e..4506a58adb 100644 --- a/std/math/uints/uint8.go +++ b/std/math/uints/uint8.go @@ -30,42 +30,19 @@ import ( "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/internal/logderivprecomp" "github.com/consensys/gnark/std/math/bitslice" - "github.com/consensys/gnark/std/rangecheck" ) -// TODO: if internal then enforce range check! - // TODO: all operations can take rand linear combinations instead. Then instead // of one check can perform multiple at the same time. -// TODO: implement versions which take multiple inputs. Maybe can combine multiple together - -// TODO: instantiate tables only when we first query. Maybe do not need to build! - // TODO: maybe can store everything in a single table? Later! Or if we have a // lot of queries then makes sense to extract into separate table? -// TODO: in ValueOf ensure consistency - -// TODO: distinguish between when we set constant in-circuit or witness -// assignment. For constant we don't have to range check but for witness -// assignment we have to. - -// TODO: add something which allows to store array in native element - -// TODO: add methods for checking if U8/Long is constant. - -// TODO: should something for byte-only ops. Implement a type and then embed it in BinaryField - // TODO: add helper method to call hints which allows to pass in uint8s (bytes) // and returns bytes. Then can to byte array manipulation nicely. It is useful // for X509. For the implementation we want to pack as much bytes into a field // element as possible. -// TODO: methods for converting uint array into emulated element and native -// element. Most probably should add the implementation for non-native in its -// package, but for native we should add it here. - type U8 struct { Val frontend.Variable internal bool @@ -84,45 +61,47 @@ type U32 [4]U8 type Long interface{ U32 | U64 } -type BinaryField[T U32 | U64] struct { - api frontend.API - xorT, andT, orT *logderivprecomp.Precomputed - rchecker frontend.Rangechecker - allOne U8 +type BinaryField[T Long] struct { + *Bytes } -func New[T Long](api frontend.API) (*BinaryField[T], error) { - xorT, err := logderivprecomp.New(api, xorHint, []uint{8}) - if err != nil { - return nil, fmt.Errorf("new xor table: %w", err) - } - andT, err := logderivprecomp.New(api, andHint, []uint{8}) - if err != nil { - return nil, fmt.Errorf("new and table: %w", err) - } - orT, err := logderivprecomp.New(api, orHint, []uint{8}) +// NewBinaryField creates a new [BinaryField] for the given integer type T +// specified by parameter [Long]. It allows to manipulate long integers in +// circuit. +func NewBinaryField[T Long](api frontend.API) (*BinaryField[T], error) { + bts, err := NewBytes(api) if err != nil { - return nil, fmt.Errorf("new or table: %w", err) + return nil, fmt.Errorf("new bytes: %w", err) } - rchecker := rangecheck.New(api) - bf := &BinaryField[T]{ - api: api, - xorT: xorT, - andT: andT, - orT: orT, - rchecker: rchecker, - } - // TODO: this is const. add way to init constants - allOne := bf.ByteValueOf(0xff) - bf.allOne = allOne - return bf, nil + return &BinaryField[T]{Bytes: bts}, nil +} + +// New is an alias to [NewBinaryField]. It is retained for backwards +// compatibility. New uses should use [NewBinaryField] instead. +func New[T Long](api frontend.API) (*BinaryField[T], error) { + return NewBinaryField[T](api) } +// NewU8 creates a new [U8] value. It represents a single byte. It can both be +// used in-circuit to initialize a constant or as a witness assignment. For +// in-circuit initialization use [Bytes.ValueOf] method instead which ensures +// that the value is range checked. func NewU8(v uint8) U8 { - // TODO: don't have to check constants + // if NewU8 is used inside the circuit, then this means that the input is a + // constant and this ensures that the value is already range checked by + // default (as the argument is uint8). If it is used as a witness + // assignment, then the flag `internal` is not set for the actual witness + // value inside the circuit, as witness parser only copies + // [frontend.Variable] part of U8. And the `internal=false` is set in the + // [U8.Initialize] method. return U8{Val: v, internal: true} } +// NewU32 creates a new [U32] value. It represents a 32-bit unsigned integer +// which is split into 4 bytes. It can both be used in-circuit to initialize a +// constant or as a witness assignment. For in-circuit initialization use +// [BinaryField.ValueOf] method instead which ensures that the value is range +// checked. func NewU32(v uint32) U32 { return [4]U8{ NewU8(uint8((v >> (0 * 8)) & 0xff)), @@ -132,6 +111,11 @@ func NewU32(v uint32) U32 { } } +// NewU64 creates a new [U64] value. It represents a 64-bit unsigned integer +// which is split into 4 bytes. It can both be used in-circuit to initialize a +// constant or as a witness assignment. For in-circuit initialization use +// [BinaryField.ValueOf] method instead which ensures that the value is range +// checked. func NewU64(v uint64) U64 { return [8]U8{ NewU8(uint8((v >> (0 * 8)) & 0xff)), @@ -145,6 +129,8 @@ func NewU64(v uint64) U64 { } } +// NewU8Array is a utility method to create a slice of [U8] from a slice of +// uint8. func NewU8Array(v []uint8) []U8 { ret := make([]U8, len(v)) for i := range v { @@ -153,6 +139,8 @@ func NewU8Array(v []uint8) []U8 { return ret } +// NewU32Array is a utility method to create a slice of [U32] from a slice of +// uint32. func NewU32Array(v []uint32) []U32 { ret := make([]U32, len(v)) for i := range v { @@ -161,6 +149,8 @@ func NewU32Array(v []uint32) []U32 { return ret } +// NewU64Array is a utility method to create a slice of [U64] from a slice of +// uint64. func NewU64Array(v []uint64) []U64 { ret := make([]U64, len(v)) for i := range v { @@ -170,19 +160,18 @@ func NewU64Array(v []uint64) []U64 { } func (bf *BinaryField[T]) ByteValueOf(a frontend.Variable) U8 { - bf.rchecker.Check(a, 8) - return U8{Val: a, internal: true} + return bf.Bytes.ValueOf(a) } func (bf *BinaryField[T]) ValueOf(a frontend.Variable) T { var r T - bts, err := bf.api.Compiler().NewHint(toBytes, len(r), len(r), a) + bts, err := bf.api.Compiler().NewHint(toBytes, bf.lenBts(), bf.lenBts(), a) if err != nil { panic(err) } for i := range bts { - r[i] = bf.ByteValueOf(bts[i]) + r[i] = bf.Bytes.ValueOf(bts[i]) } expectedValue := bf.ToValue(r) bf.api.AssertIsEqual(a, expectedValue) @@ -217,8 +206,8 @@ func (bf *BinaryField[T]) PackLSB(a ...U8) T { func (bf *BinaryField[T]) UnpackMSB(a T) []U8 { ret := make([]U8, bf.lenBts()) - for i := 0; i < len(ret); i++ { - ret[len(a)-i-1] = a[i] + for i := range ret { + ret[bf.lenBts()-i-1] = a[i] } return ret } @@ -226,23 +215,15 @@ func (bf *BinaryField[T]) UnpackMSB(a T) []U8 { func (bf *BinaryField[T]) UnpackLSB(a T) []U8 { // cannot deduce that a can be cast to []U8 ret := make([]U8, bf.lenBts()) - for i := 0; i < len(ret); i++ { + for i := range ret { ret[i] = a[i] } return ret } -func (bf *BinaryField[T]) twoArgFn(tbl *logderivprecomp.Precomputed, a ...U8) U8 { - ret := tbl.Query(a[0].Val, a[1].Val)[0] - for i := 2; i < len(a); i++ { - ret = tbl.Query(ret, a[i].Val)[0] - } - return U8{Val: ret} -} - func (bf *BinaryField[T]) twoArgWideFn(tbl *logderivprecomp.Precomputed, a ...T) T { var r T - for i, v := range reslice(a) { + for i, v := range bf.reslice(a) { r[i] = bf.twoArgFn(tbl, v...) } return r @@ -252,15 +233,10 @@ func (bf *BinaryField[T]) And(a ...T) T { return bf.twoArgWideFn(bf.andT, a...) func (bf *BinaryField[T]) Xor(a ...T) T { return bf.twoArgWideFn(bf.xorT, a...) } func (bf *BinaryField[T]) Or(a ...T) T { return bf.twoArgWideFn(bf.orT, a...) } -func (bf *BinaryField[T]) not(a U8) U8 { - ret := bf.xorT.Query(a.Val, bf.allOne.Val) - return U8{Val: ret[0]} -} - func (bf *BinaryField[T]) Not(a T) T { var r T - for i := 0; i < len(a); i++ { - r[i] = bf.not(a[i]) + for i := 0; i < bf.lenBts(); i++ { + r[i] = bf.Bytes.Not(a[i]) } return r } @@ -332,7 +308,7 @@ func (bf *BinaryField[T]) Rshift(a T, c int) T { } func (bf *BinaryField[T]) ByteAssertEq(a, b U8) { - bf.api.AssertIsEqual(a.Val, b.Val) + bf.Bytes.AssertIsEqual(a, b) } func (bf *BinaryField[T]) AssertEq(a, b T) { @@ -346,16 +322,16 @@ func (bf *BinaryField[T]) lenBts() int { return len(a) } -func reslice[T U32 | U64](in []T) [][]U8 { +func (bf *BinaryField[T]) reslice(in []T) [][]U8 { if len(in) == 0 { panic("zero-length input") } - ret := make([][]U8, len(in[0])) + ret := make([][]U8, bf.lenBts()) for i := range ret { ret[i] = make([]U8, len(in)) } - for i := 0; i < len(in); i++ { - for j := 0; j < len(in[0]); j++ { + for i := range in { + for j := range bf.lenBts() { ret[j][i] = in[i][j] } } From 225109743ae07aef55ace2e41c33e2936255079d Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 14 Jul 2025 22:45:23 +0000 Subject: [PATCH 098/105] refactor: use bytes api --- std/hash/ripemd160/ripemd160_test.go | 4 ++-- std/hash/sha2/sha2_test.go | 8 ++++---- std/hash/sha3/sha3_test.go | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/std/hash/ripemd160/ripemd160_test.go b/std/hash/ripemd160/ripemd160_test.go index 87db38d626..7271be60c8 100644 --- a/std/hash/ripemd160/ripemd160_test.go +++ b/std/hash/ripemd160/ripemd160_test.go @@ -21,7 +21,7 @@ func (c *ripemd160Circuit) Define(api frontend.API) error { if err != nil { return err } - uapi, err := uints.New[uints.U32](api) + uapi, err := uints.NewBytes(api) if err != nil { return err } @@ -31,7 +31,7 @@ func (c *ripemd160Circuit) Define(api frontend.API) error { return fmt.Errorf("not 20 bytes") } for i := range c.Expected { - uapi.ByteAssertEq(c.Expected[i], res[i]) + uapi.AssertIsEqual(c.Expected[i], res[i]) } return nil } diff --git a/std/hash/sha2/sha2_test.go b/std/hash/sha2/sha2_test.go index 5f3572ac8a..b0253dfd2a 100644 --- a/std/hash/sha2/sha2_test.go +++ b/std/hash/sha2/sha2_test.go @@ -23,7 +23,7 @@ func (c *sha2Circuit) Define(api frontend.API) error { if err != nil { return err } - uapi, err := uints.New[uints.U32](api) + uapi, err := uints.NewBytes(api) if err != nil { return err } @@ -33,7 +33,7 @@ func (c *sha2Circuit) Define(api frontend.API) error { return fmt.Errorf("not 32 bytes") } for i := range c.Expected { - uapi.ByteAssertEq(c.Expected[i], res[i]) + uapi.AssertIsEqual(c.Expected[i], res[i]) } return nil } @@ -65,7 +65,7 @@ func (c *sha2FixedLengthCircuit) Define(api frontend.API) error { if err != nil { return err } - uapi, err := uints.New[uints.U32](api) + uapi, err := uints.NewBytes(api) if err != nil { return err } @@ -75,7 +75,7 @@ func (c *sha2FixedLengthCircuit) Define(api frontend.API) error { return fmt.Errorf("not 32 bytes") } for i := range c.Expected { - uapi.ByteAssertEq(c.Expected[i], res[i]) + uapi.AssertIsEqual(c.Expected[i], res[i]) } return nil } diff --git a/std/hash/sha3/sha3_test.go b/std/hash/sha3/sha3_test.go index 9e8e7c75c4..3b60c59548 100644 --- a/std/hash/sha3/sha3_test.go +++ b/std/hash/sha3/sha3_test.go @@ -44,7 +44,7 @@ func (c *sha3Circuit) Define(api frontend.API) error { if err != nil { return err } - uapi, err := uints.New[uints.U64](api) + uapi, err := uints.NewBytes(api) if err != nil { return err } @@ -53,7 +53,7 @@ func (c *sha3Circuit) Define(api frontend.API) error { res := h.Sum() for i := range c.Expected { - uapi.ByteAssertEq(c.Expected[i], res[i]) + uapi.AssertIsEqual(c.Expected[i], res[i]) } return nil } @@ -109,7 +109,7 @@ func (c *sha3FixedLengthSumCircuit) Define(api frontend.API) error { if err != nil { return err } - uapi, err := uints.New[uints.U64](api) + uapi, err := uints.NewBytes(api) if err != nil { return err } @@ -117,7 +117,7 @@ func (c *sha3FixedLengthSumCircuit) Define(api frontend.API) error { res := h.FixedLengthSum(c.Length) for i := range c.Expected { - uapi.ByteAssertEq(c.Expected[i], res[i]) + uapi.AssertIsEqual(c.Expected[i], res[i]) } return nil } From 4c3cb24e206b2a9be126983ba5f710dd6f42a539 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 14 Jul 2025 14:50:27 +0000 Subject: [PATCH 099/105] feat: handle edge cases directly in rangechecker --- std/rangecheck/rangecheck_commit.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/std/rangecheck/rangecheck_commit.go b/std/rangecheck/rangecheck_commit.go index acccb6bcca..caabcd1de1 100644 --- a/std/rangecheck/rangecheck_commit.go +++ b/std/rangecheck/rangecheck_commit.go @@ -24,6 +24,8 @@ type checkedVariable struct { } type commitChecker struct { + api frontend.API + collected []checkedVariable closed bool } @@ -41,7 +43,7 @@ func newCommitRangechecker(api frontend.API) *commitChecker { panic("stored rangechecker is not valid") } } - cht := &commitChecker{} + cht := &commitChecker{api: api} kv.SetKeyValue(ctxCheckerKey{}, cht) api.Compiler().Defer(cht.commit) return cht @@ -51,7 +53,14 @@ func (c *commitChecker) Check(in frontend.Variable, bits int) { if c.closed { panic("checker already closed") } - c.collected = append(c.collected, checkedVariable{v: in, bits: bits}) + switch bits { + case 0: + c.api.AssertIsEqual(in, 0) + case 1: + c.api.AssertIsBoolean(in) + default: + c.collected = append(c.collected, checkedVariable{v: in, bits: bits}) + } } func (c *commitChecker) buildTable(nbTable int) []frontend.Variable { From 57ef0ff5e0edbda32f28399559df34f8b01878c7 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Fri, 23 May 2025 20:43:41 +0000 Subject: [PATCH 100/105] test: test kvstore --- internal/kvstore/kvstore_test.go | 73 ++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 internal/kvstore/kvstore_test.go diff --git a/internal/kvstore/kvstore_test.go b/internal/kvstore/kvstore_test.go new file mode 100644 index 0000000000..8be8e7e9fa --- /dev/null +++ b/internal/kvstore/kvstore_test.go @@ -0,0 +1,73 @@ +package kvstore_test + +import ( + "fmt" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" + "github.com/consensys/gnark/frontend/cs/scs" + "github.com/consensys/gnark/internal/kvstore" + "github.com/consensys/gnark/test" +) + +type ctxKey[T comparable] struct{} +type toStore[T comparable] struct { + Value T +} + +type Circuit[T comparable] struct { + A frontend.Variable +} + +func (c *Circuit[T]) Define(api frontend.API) error { + kv, ok := api.(kvstore.Store) + if !ok { + panic("builder should implement key-value store") + } + stored1 := kv.GetKeyValue(ctxKey[T]{}) + if stored1 != nil { + // should be nil + return fmt.Errorf("expected nil, got %v", stored1) + } + if tStored1, ok := stored1.(*toStore[T]); ok { + // should be nil interface + return fmt.Errorf("expected nil, got %v", tStored1) + } + // store something + var t T + stored2 := &toStore[T]{Value: t} + kv.SetKeyValue(ctxKey[T]{}, stored2) + stored3 := kv.GetKeyValue(ctxKey[T]{}) + if stored3 == nil { + return fmt.Errorf("expected non nil, got nil") + } + tStored3, ok := stored3.(*toStore[T]) + if !ok { + return fmt.Errorf("expected toStore[T], got %T", stored3) + } + if tStored3.Value != t { + return fmt.Errorf("expected %v, got %v", t, tStored3.Value) + } + return nil +} + +func TestKeyValue(t *testing.T) { + assert := test.NewAssert(t) + // test with int + err := test.IsSolved(&Circuit[int]{}, &Circuit[int]{A: 1}, ecc.BN254.ScalarField()) + assert.NoError(err) + // test with uint + err = test.IsSolved(&Circuit[uint]{}, &Circuit[uint]{A: 1}, ecc.BN254.ScalarField()) + assert.NoError(err) + // test with string + err = test.IsSolved(&Circuit[string]{}, &Circuit[string]{A: "1234"}, ecc.BN254.ScalarField()) + assert.NoError(err) + + // test during compilation + _, err = frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &Circuit[int]{}) + assert.NoError(err) + _, err = frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, &Circuit[int]{}) + assert.NoError(err) +} From 617c7ee2b243d85c9193426491d3ef2b985266fd Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 3 Jul 2025 07:37:26 +0000 Subject: [PATCH 101/105] chore: include all hints automatic registration --- std/hints.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/std/hints.go b/std/hints.go index 710e52ba09..2053984fd0 100644 --- a/std/hints.go +++ b/std/hints.go @@ -7,11 +7,13 @@ import ( "github.com/consensys/gnark/std/algebra/emulated/fields_bls12381" "github.com/consensys/gnark/std/algebra/emulated/fields_bn254" "github.com/consensys/gnark/std/algebra/emulated/fields_bw6761" + "github.com/consensys/gnark/std/algebra/emulated/sw_bw6761" "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" "github.com/consensys/gnark/std/algebra/native/fields_bls12377" "github.com/consensys/gnark/std/algebra/native/fields_bls24315" "github.com/consensys/gnark/std/algebra/native/sw_bls12377" "github.com/consensys/gnark/std/algebra/native/sw_bls24315" + "github.com/consensys/gnark/std/algebra/native/twistededwards" "github.com/consensys/gnark/std/evmprecompiles" "github.com/consensys/gnark/std/hash/sha3" "github.com/consensys/gnark/std/internal/logderivarg" @@ -19,6 +21,7 @@ import ( "github.com/consensys/gnark/std/math/bitslice" "github.com/consensys/gnark/std/math/cmp" "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/math/uints" "github.com/consensys/gnark/std/rangecheck" "github.com/consensys/gnark/std/selector" ) @@ -44,6 +47,7 @@ func registerHints() { solver.RegisterHint(logderivarg.GetHints()...) solver.RegisterHint(bitslice.GetHints()...) solver.RegisterHint(sha3.GetHints()...) + solver.RegisterHint(uints.GetHints()...) // emulated fields solver.RegisterHint(fields_bls12381.GetHints()...) solver.RegisterHint(fields_bn254.GetHints()...) @@ -51,8 +55,10 @@ func registerHints() { // native fields solver.RegisterHint(fields_bls12377.GetHints()...) solver.RegisterHint(fields_bls24315.GetHints()...) + solver.RegisterHint(twistededwards.GetHints()...) // emulated curves solver.RegisterHint(sw_emulated.GetHints()...) + solver.RegisterHint(sw_bw6761.GetHints()...) // native curves solver.RegisterHint(sw_bls12377.GetHints()...) solver.RegisterHint(sw_bls24315.GetHints()...) From 786608173f59eb9f665c68a48a28a0fad65a6c12 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 14 Jul 2025 22:29:31 +0000 Subject: [PATCH 102/105] feat: implement conversion package --- std/conversion/conversion.go | 316 ++++++++++++++++++++++ std/conversion/conversion_test.go | 425 ++++++++++++++++++++++++++++++ std/conversion/hints.go | 36 +++ std/hints.go | 2 + 4 files changed, 779 insertions(+) create mode 100644 std/conversion/conversion.go create mode 100644 std/conversion/conversion_test.go create mode 100644 std/conversion/hints.go diff --git a/std/conversion/conversion.go b/std/conversion/conversion.go new file mode 100644 index 0000000000..d2dc400e5f --- /dev/null +++ b/std/conversion/conversion.go @@ -0,0 +1,316 @@ +// Package conversion provides methods for converting between different primitive types. +// +// gnark provides different wrappers for extending the usage beyond native field +// elements (bytes, bits, non-native elements etc.). This package implements +// some conversion methods between these types. +// +// It is still work in progress and interfaces may change in the future. +package conversion + +import ( + "fmt" + "math/big" + "math/bits" + + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/math/uints" + "github.com/consensys/gnark/std/rangecheck" +) + +// should have: +// - convert from bytes to native field element ✅ +// - convert from bytes to emulated field element ✅ +// * same, but allow for overflow? maybe could add an option ✅ +// - convert from native field element to bytes ✅ +// - convert from emulated field element to bytes ✅ +// - convert from native field element to emulated field element +// - convert from emulated field element to another emulated field element (ECDSA) +// - convert from bits to native field element (duplicate existing method, for completeness) +// - convert from bits to emulated field element (? duplicate existing method, for completeness) + +// Option allows to configure the conversion functions behavior. +type Option func(*config) error + +type config struct { + allowOverflow bool +} + +func newConfig(opts ...Option) (*config, error) { + c := new(config) + for _, opt := range opts { + if opt == nil { + return nil, fmt.Errorf("nil conversion option provided") + } + if err := opt(c); err != nil { + return nil, fmt.Errorf("apply conversion option: %w", err) + } + } + return c, nil +} + +// WithAllowOverflow allows for overflowing the modulus when converting bytes to +// emulated field element. When not set, then we assert that the constructed +// element is strictly less than the modulus. +func WithAllowOverflow() Option { + return func(c *config) error { + c.allowOverflow = true + return nil + } +} + +// BytesToNative converts the bytes in MSB order to a native field element. If +// the option [WithAllowOverflow] is set, then the method does not check that +// the input is strictly less than the modulus of the field. Otherwise, it +// checks that the input is strictly less than the modulus of the field. +// +// It errors when the the provided bytes slice is too large to fit into a native +// field element. +func BytesToNative(api frontend.API, b []uints.U8, opts ...Option) (frontend.Variable, error) { + if (api.Compiler().Field().BitLen() + 7) < 8*len(b) { + return nil, fmt.Errorf("input too large to fit into field element") + } + cfg, err := newConfig(opts...) + if err != nil { + return nil, fmt.Errorf("new config: %w", err) + } + res := bytesToNative(api, b) + // check that the input was in range of the field modulus. Omit if cfg.allowOverflow is set. + if !cfg.allowOverflow { + assertBytesLeq(api, b, api.Compiler().Field()) + } + return res, nil +} + +func bytesToNative(api frontend.API, b []uints.U8) frontend.Variable { + var res frontend.Variable = 0 + shift := big.NewInt(1) + for i := len(b) - 1; i >= 0; i-- { + res = api.Add(res, api.Mul(b[i].Val, shift)) + // shift the value to the left by 8 bits + shift.Lsh(shift, 8) + } + return res +} + +// BytesToEmulated converts the bytes in MSB order to an emulated field element. +// If the option [WithAllowOverflow] is set, then the method does not check that +// the input is strictly less than the modulus of the field. Otherwise, it +// checks that the input is strictly less than the modulus of the field. +// +// It errors when the provided bytes slice is too large to fit into an emulated +// field element. +// +// NB! Currently it supports only the case when the emulated field element limb +// width is divisible by 8 bits. If the limb width is not divisible by 8 bits, +// then the method panics. Please open an issue if you need this functionality. +// Otherwise, we will implement it when needed. +func BytesToEmulated[T emulated.FieldParams](api frontend.API, b []uints.U8, opts ...Option) (*emulated.Element[T], error) { + // panics when couldn't fit + // we can have several approaches - when the limb width is divisible by 8, then we can just compose the limbs without needing to move to bits + // otherwise if not, we construct the limb and then move the excess part over + + // first we check that the bytes can fit in the field element. But we don't check yet if the byte representation is smaller than the modulus. + // we do it later after we have already constructed the field element + effNbLimbs, effNbBits := emulated.GetEffectiveFieldParams[T](api.Compiler().Field()) + if effNbLimbs*effNbBits < uint(len(b))*8 { + return nil, fmt.Errorf("input too large to fit into field element") + } + + // 0 - we don't support when the emulated element limb width is smaller than 8 bits + if effNbBits < 8 { + return nil, fmt.Errorf("bytes to emulated conversion not supported for field with limb width smaller than 8 bits, got %d bits", effNbBits) + } + + cfg, err := newConfig(opts...) + if err != nil { + return nil, fmt.Errorf("new config: %w", err) + } + + // 1 - handle the case where the limb width is divisible by 8 + if effNbBits%8 == 0 { + return bytesToEmulatedDivisible[T](cfg, api, b) + } + // 2 - handle the case where the limb width is not divisible by 8 + return bytesToEmulatedNotDivisible[T](cfg, api, b) +} + +func bytesToEmulatedDivisible[T emulated.FieldParams](cfg *config, api frontend.API, b []uints.U8) (*emulated.Element[T], error) { + effNbLimbs, effNbBits := emulated.GetEffectiveFieldParams[T](api.Compiler().Field()) + // pad the input bytes to be exactly the number of bytes needed for the emulated field element + paddingLen := int(effNbLimbs*effNbBits/8) - len(b) + bPadded := make([]uints.U8, 0, paddingLen+len(b)) + // we left-pad with zeros + bPadded = append(bPadded, uints.NewU8Array(make([]uint8, paddingLen))...) + // now we append the original bytes + bPadded = append(bPadded, b...) + limbs := make([]frontend.Variable, effNbLimbs) + for i := range limbs { + limbs[i] = 0 + shift := big.NewInt(1) + for j := range int(effNbBits) / 8 { + limbs[i] = api.Add(limbs[i], api.Mul(bPadded[len(bPadded)-1-i*int(effNbBits)/8-j].Val, shift)) + shift.Lsh(shift, 8) + } + } + f, err := emulated.NewField[T](api) + if err != nil { + return nil, fmt.Errorf("new field: %w", err) + } + e := f.NewElement(limbs) + if !cfg.allowOverflow { + f.AssertIsInRange(e) + } + return e, nil +} + +func bytesToEmulatedNotDivisible[T emulated.FieldParams](cfg *config, api frontend.API, b []uints.U8) (*emulated.Element[T], error) { + panic("todo") +} + +// NativeToBytes converts a native field element to a slice of bytes in MSB order. +// The number of bytes is determined by the field bit length, rounded up to the +// nearest byte. The method returns a slice of [uints.U8] values, which +// represent the bytes of the native field element. +// +// If the option [WithAllowOverflow] is set, then the method does not check that +// the input is strictly less than the modulus of the field. This may happen in case +// of malicious hint execution. The user could bypass the overflow checking if it is done later, +// i.e. when composing the bytes back to a native field element using [BytesToNative]. +func NativeToBytes(api frontend.API, v frontend.Variable, opts ...Option) ([]uints.U8, error) { + cfg, err := newConfig(opts...) + if err != nil { + return nil, fmt.Errorf("new config: %w", err) + } + nbBytes := (api.Compiler().Field().BitLen() + 7) / 8 + res, err := api.NewHint(nativeToBytesHint, nbBytes, v) + if err != nil { + return nil, fmt.Errorf("new hint: %w", err) + } + if len(res) != nbBytes { + return nil, fmt.Errorf("expected %d bytes, got %d", nbBytes, len(res)) + } + resU8 := make([]uints.U8, nbBytes) + uapi, err := uints.NewBytes(api) + if err != nil { + return nil, fmt.Errorf("new uints: %w", err) + } + for i := range nbBytes { + resU8[i] = uapi.ValueOf(res[i]) + } + // check that the decomposed bytes compose to the original value + computed := bytesToNative(api, resU8) + api.AssertIsEqual(v, computed) + // assert that the bytes are in range of the field modulus. We can omit the + // check if we don't care about the uniqueness (in case later when composing + // back to native element the check is done there). + if !cfg.allowOverflow { + assertBytesLeq(api, resU8, api.Compiler().Field()) + } + return resU8, nil +} + +// EmulatedToBytes converts an emulated field element to a slice of bytes in MSB +// order. The number of bytes is determined by the emulated field element bit +// length, rounded up to the nearest byte. The method returns a slice of +// [uints.U8] values, which represent the bytes of the emulated field element. +// +// If the option [WithAllowOverflow] is set, then the method does not check that +// the input is strictly less than the modulus of the field. This may happen in case +// of malicious hint execution. The user could bypass the overflow checking if it +// is done later, i.e. when composing the bytes back to a native field element +// using [BytesToEmulated]. +// +// NB! Currently it supports only the case when the emulated field element limb +// width is divisible by 8 bits. If the limb width is not divisible by 8 bits, +// then the method panics. Please open an issue if you need this functionality. +func EmulatedToBytes[T emulated.FieldParams](api frontend.API, v *emulated.Element[T], opts ...Option) ([]uints.U8, error) { + var fr T + if fr.BitsPerLimb()%8 != 0 { + panic("EmulatedToBytes: not supported for field with limb width not divisible by 8 bits") + } + f, err := emulated.NewField[T](api) + if err != nil { + return nil, fmt.Errorf("new field: %w", err) + } + cfg, err := newConfig(opts...) + if err != nil { + return nil, fmt.Errorf("new config: %w", err) + } + var vr *emulated.Element[T] + if cfg.allowOverflow { + vr = f.Reduce(v) + } else { + vr = f.ReduceStrict(v) + } + + nbBytes := (api.Compiler().Field().BitLen() + 7) / 8 + nbLimbBytes := fr.BitsPerLimb() / 8 + resU8 := make([]uints.U8, (fr.Modulus().BitLen()+7)/8) + uapi, err := uints.NewBytes(api) + if err != nil { + return nil, fmt.Errorf("new uints: %w", err) + } + for i := range vr.Limbs { + res, err := api.NewHint(nativeToBytesHint, nbBytes, vr.Limbs[len(vr.Limbs)-i-1]) + if err != nil { + return nil, fmt.Errorf("new hint: %w", err) + } + if len(res) != nbBytes { + return nil, fmt.Errorf("expected %d bytes, got %d", nbBytes, len(res)) + } + res = res[uint(nbBytes)-nbLimbBytes:] // take only the last nbLimbBytes bytes + for j := range nbLimbBytes { + resU8[uint(i)*nbLimbBytes+j] = uapi.ValueOf(res[j]) + } + computed := bytesToNative(api, resU8[uint(i)*nbLimbBytes:uint(i+1)*nbLimbBytes]) + api.AssertIsEqual(vr.Limbs[len(vr.Limbs)-i-1], computed) + } + return resU8, nil +} + +// func NativeToEmulated[T emulated.FieldParams](api frontend.API, v frontend.Variable) *emulated.Element[T] { +// panic("todo") +// } + +// assertBytesLeq checks that the bytes in MSB order are less or equal than the +// bound. The method internally decomposes the bound into MSB bytes. +func assertBytesLeq(api frontend.API, b []uints.U8, bound *big.Int) { + // we check that the bytes are in range of the field modulus + // we do it by checking that the first byte is smaller than the first byte of the modulus + + // for this, we first decompose the modulus into bytes + mBytes := bound.Bytes() + // if there are less bytes than the modulus, then we don't need to perform the check, it is always smaller + if len(b) < len(mBytes) { + return // nothing to check + } + // if there are more bytes than the modulus, then we need to check that the high bytes are zero + for i := 0; i < len(b)-len(mBytes); i++ { + api.AssertIsEqual(b[i].Val, 0) + } + bb := b[len(b)-len(mBytes):] // take the last bytes that correspond to the modulus length + rchecker := rangecheck.New(api) + // now we can check the bytes against modulus bytes. The method is + // generalization of bitwise comparison, but we compare byte-wise. We need + // to have that for every either + // - b[i] < mBytes[i]. If this happens then for the rest of the bytes b[j] (j < i) + // we don't have any restrictions. For this we're setting the eq_i variable to 0 + // to indicate that we have found a byte that is smaller than the modulus byte. + // - b[i] == mBytes[i]. If this happens then we need to have the same checks for + // b[i-i] and mBytes[i-1] etc. + // + // Now, to check that b[i] <= mBytes[i] we can use the range checker gadget + // to ensure that the difference mBytes[i]-b[i] is non-negative by checking + // that it has up to |mBytes[i]| bits. + var eq_i frontend.Variable = 1 + for i := range mBytes { + // compute the difference + diff := api.Sub(mBytes[i], bb[i].Val) + // check that the difference is non-negative. Compute the number of bits to represent the modulus byte + nbBits := bits.Len8(mBytes[i]) + rchecker.Check(api.Mul(eq_i, diff), nbBits) + isEq := api.IsZero(diff) + eq_i = api.Mul(eq_i, isEq) + } +} diff --git a/std/conversion/conversion_test.go b/std/conversion/conversion_test.go new file mode 100644 index 0000000000..b8de66c688 --- /dev/null +++ b/std/conversion/conversion_test.go @@ -0,0 +1,425 @@ +package conversion + +import ( + "crypto/rand" + "fmt" + "math/big" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + fp_bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + fr_bn254 "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/math/emulated/emparams" + "github.com/consensys/gnark/std/math/uints" + "github.com/consensys/gnark/test" +) + +type BytesToEmulatedCircuit[T emulated.FieldParams] struct { + In []uints.U8 + Expected emulated.Element[T] + + allowOverflow bool +} + +func (c *BytesToEmulatedCircuit[T]) Define(api frontend.API) error { + var opts []Option + if c.allowOverflow { + opts = append(opts, WithAllowOverflow()) + } + res, err := BytesToEmulated[T](api, c.In, opts...) + if err != nil { + return fmt.Errorf("to emulated: %w", err) + } + f, err := emulated.NewField[T](api) + if err != nil { + return fmt.Errorf("new field: %w", err) + } + f.AssertIsEqual(&c.Expected, res) + return nil +} + +func TestBytesToEmulatedDivisible(t *testing.T) { + assert := test.NewAssert(t) + + // case when the number of bytes is exactly the length of the emulated field element + assert.Run(func(assert *test.Assert) { + var S fp_bls12381.Element + S.MustSetRandom() + sbytes := S.Marshal() + sint := S.BigInt(new(big.Int)) + err := test.IsSolved( + &BytesToEmulatedCircuit[emparams.BLS12381Fp]{In: make([]uints.U8, len(sbytes))}, + &BytesToEmulatedCircuit[emparams.BLS12381Fp]{In: uints.NewU8Array(sbytes), Expected: emulated.ValueOf[emparams.BLS12381Fp](sint)}, + ecc.BLS12_377.ScalarField(), + ) + assert.NoError(err) + }, "length=exact") + + // case when the number of bytes is smaller than the length of the emulated field element + assert.Run(func(assert *test.Assert) { + sint := new(big.Int).SetUint64(0xffffffffffffffff) + sbytes := sint.Bytes() + err := test.IsSolved( + &BytesToEmulatedCircuit[emparams.BLS12381Fp]{In: make([]uints.U8, len(sbytes))}, + &BytesToEmulatedCircuit[emparams.BLS12381Fp]{In: uints.NewU8Array(sbytes), Expected: emulated.ValueOf[emparams.BLS12381Fp](sint)}, + ecc.BLS12_377.ScalarField(), + ) + assert.NoError(err) + }, "length=smaller") + + // case when the number of bytes is larger than the length of the emulated field element + assert.Run(func(assert *test.Assert) { + var S fp_bls12381.Element + S.MustSetRandom() + sbytes := S.Marshal() + sbytes = append([]byte{0x00}, sbytes...) + sint := S.BigInt(new(big.Int)) + err := test.IsSolved( + &BytesToEmulatedCircuit[emparams.BLS12381Fp]{In: make([]uints.U8, len(sbytes))}, + &BytesToEmulatedCircuit[emparams.BLS12381Fp]{In: uints.NewU8Array(sbytes), Expected: emulated.ValueOf[emparams.BLS12381Fp](sint)}, + ecc.BLS12_377.ScalarField(), + ) + assert.Error(err) + }, "length=larger") + + // case where everything is good, but the bytes represent element larger than the modulus + assert.Run(func(assert *test.Assert) { + smallValue := big.NewInt(5) + S := new(big.Int).Add(smallValue, fp_bls12381.Modulus()) + sbytes := S.Bytes() + err := test.IsSolved( + &BytesToEmulatedCircuit[emparams.BLS12381Fp]{In: make([]uints.U8, len(sbytes))}, + &BytesToEmulatedCircuit[emparams.BLS12381Fp]{In: uints.NewU8Array(sbytes), Expected: emulated.ValueOf[emparams.BLS12381Fp](smallValue)}, + ecc.BLS12_377.ScalarField(), + ) + assert.Error(err, "expected error when bytes represent element larger than the modulus") + }, "length=overflow") + + // case where everything is good, but the bytes represent element larger than the modulus, but we allow overflow + assert.Run(func(assert *test.Assert) { + smallValue := big.NewInt(5) + S := new(big.Int).Add(smallValue, fp_bls12381.Modulus()) + sbytes := S.Bytes() + err := test.IsSolved( + &BytesToEmulatedCircuit[emparams.BLS12381Fp]{In: make([]uints.U8, len(sbytes)), allowOverflow: true}, + &BytesToEmulatedCircuit[emparams.BLS12381Fp]{In: uints.NewU8Array(sbytes), Expected: emulated.ValueOf[emparams.BLS12381Fp](smallValue)}, + ecc.BLS12_377.ScalarField(), + ) + assert.NoError(err, "expected no error when allowing overflow") + }, "length=overflow-allow") +} + +type BytesToNativeCircuit struct { + In []uints.U8 + Expected frontend.Variable + + allowOverflow bool +} + +func (c *BytesToNativeCircuit) Define(api frontend.API) error { + var opts []Option + if c.allowOverflow { + opts = append(opts, WithAllowOverflow()) + } + res, err := BytesToNative(api, c.In, opts...) + if err != nil { + return fmt.Errorf("to native: %w", err) + } + api.AssertIsEqual(res, c.Expected) + return nil +} + +func TestBytesToNative(t *testing.T) { + assert := test.NewAssert(t) + + // case when the number of bytes is exactly the length of the native field element + assert.Run(func(assert *test.Assert) { + var S fr_bn254.Element + S.MustSetRandom() + sbytes := S.Marshal() + sint := S.BigInt(new(big.Int)) + err := test.IsSolved( + &BytesToNativeCircuit{In: make([]uints.U8, len(sbytes))}, + &BytesToNativeCircuit{In: uints.NewU8Array(sbytes), Expected: sint}, + ecc.BN254.ScalarField(), + ) + assert.NoError(err) + }, "length=exact") + + // case when the number of bytes is smaller than the length of the native field element + assert.Run(func(assert *test.Assert) { + sint := new(big.Int).SetUint64(0xffffffffffffffff) + sbytes := sint.Bytes() + err := test.IsSolved( + &BytesToNativeCircuit{In: make([]uints.U8, len(sbytes))}, + &BytesToNativeCircuit{In: uints.NewU8Array(sbytes), Expected: sint}, + ecc.BN254.ScalarField(), + ) + assert.NoError(err) + }, "length=smaller") + + // case when the number of bytes is larger than the length of the native field element + assert.Run(func(assert *test.Assert) { + var S fr_bn254.Element + S.MustSetRandom() + sbytes := S.Marshal() + sbytes = append([]byte{0x00}, sbytes...) + sint := S.BigInt(new(big.Int)) + err := test.IsSolved( + &BytesToNativeCircuit{In: make([]uints.U8, len(sbytes))}, + &BytesToNativeCircuit{In: uints.NewU8Array(sbytes), Expected: sint}, + ecc.BN254.ScalarField(), + ) + assert.Error(err) + }, "length=larger") + + // case where everything is good, but the bytes represent element larger than the modulus + assert.Run(func(assert *test.Assert) { + smallValue := big.NewInt(5) + S := new(big.Int).Add(smallValue, fr_bn254.Modulus()) + sbytes := S.Bytes() + err := test.IsSolved( + &BytesToNativeCircuit{In: make([]uints.U8, len(sbytes))}, + &BytesToNativeCircuit{In: uints.NewU8Array(sbytes), Expected: smallValue}, + ecc.BN254.ScalarField(), + ) + assert.Error(err, "expected error when bytes represent element larger than the modulus") + }, "length=overflow") + + // case where everything is good, but the bytes represent element larger than the modulus, but we allow overflow + assert.Run(func(assert *test.Assert) { + smallValue := big.NewInt(5) + S := new(big.Int).Add(smallValue, fr_bn254.Modulus()) + sbytes := S.Bytes() + err := test.IsSolved( + &BytesToNativeCircuit{In: make([]uints.U8, len(sbytes)), allowOverflow: true}, + &BytesToNativeCircuit{In: uints.NewU8Array(sbytes), Expected: smallValue}, + ecc.BN254.ScalarField(), + ) + assert.NoError(err, "expected no error when allowing overflow") + }, "length=overflow-allow") +} + +type NativeToBytesCircuit struct { + In frontend.Variable + Expected []uints.U8 +} + +func (c *NativeToBytesCircuit) Define(api frontend.API) error { + res, err := NativeToBytes(api, c.In) + if err != nil { + return fmt.Errorf("to bytes: %w", err) + } + if len(res) != len(c.Expected) { + return fmt.Errorf("expected %d bytes, got %d", len(c.Expected), len(res)) + } + uapi, err := uints.NewBytes(api) + if err != nil { + return fmt.Errorf("new bytes: %w", err) + } + for i := range res { + uapi.AssertIsEqual(res[i], c.Expected[i]) + } + return nil +} + +func TestNativeToBytes(t *testing.T) { + assert := test.NewAssert(t) + + // case when the number of bytes is exactly the length of the emulated field element + assert.Run(func(assert *test.Assert) { + var S fr_bn254.Element + S.MustSetRandom() + sbytes := S.Marshal() + sint := S.BigInt(new(big.Int)) + err := test.IsSolved( + &NativeToBytesCircuit{Expected: make([]uints.U8, len(sbytes))}, + &NativeToBytesCircuit{In: sint, Expected: uints.NewU8Array(sbytes)}, + ecc.BN254.ScalarField(), + ) + assert.NoError(err) + + sbuf := make([]byte, fr_bn254.Bytes) + sint.FillBytes(sbuf) + err = test.IsSolved( + &NativeToBytesCircuit{Expected: make([]uints.U8, len(sbuf))}, + &NativeToBytesCircuit{In: sint, Expected: uints.NewU8Array(sbuf)}, + ecc.BN254.ScalarField(), + ) + assert.NoError(err) + }, "length=exact") + + // case when the number of bytes is smaller than the length of the emulated field element + assert.Run(func(assert *test.Assert) { + bound := new(big.Int).Lsh(big.NewInt(1), fr_bn254.Bytes-1) + sint, err := rand.Int(rand.Reader, bound) + assert.NoError(err, "failed to generate random int") + sbytes := make([]byte, fr_bn254.Bytes) + sint.FillBytes(sbytes) + err = test.IsSolved( + &NativeToBytesCircuit{Expected: make([]uints.U8, len(sbytes))}, + &NativeToBytesCircuit{In: sint, Expected: uints.NewU8Array(sbytes)}, + ecc.BN254.ScalarField(), + ) + assert.NoError(err) + }, "length=smaller") +} + +type EmulatedToBytesCircuit[T emulated.FieldParams] struct { + In emulated.Element[T] + Expected []uints.U8 +} + +func (c *EmulatedToBytesCircuit[T]) Define(api frontend.API) error { + res, err := EmulatedToBytes(api, &c.In) + if err != nil { + return fmt.Errorf("to bytes: %w", err) + } + if len(res) != len(c.Expected) { + return fmt.Errorf("expected %d bytes, got %d", len(c.Expected), len(res)) + } + uapi, err := uints.NewBytes(api) + if err != nil { + return fmt.Errorf("new bytes: %w", err) + } + for i := range res { + uapi.AssertIsEqual(res[i], c.Expected[i]) + } + return nil +} + +func TestEmulatedToBytes(t *testing.T) { + assert := test.NewAssert(t) + + // case when the number of bytes is exactly the length of the emulated field element + assert.Run(func(assert *test.Assert) { + var S fp_bls12381.Element + S.MustSetRandom() + sbytes := S.Marshal() + sint := S.BigInt(new(big.Int)) + err := test.IsSolved( + &EmulatedToBytesCircuit[emparams.BLS12381Fp]{Expected: make([]uints.U8, len(sbytes))}, + &EmulatedToBytesCircuit[emparams.BLS12381Fp]{In: emulated.ValueOf[emparams.BLS12381Fp](sint), Expected: uints.NewU8Array(sbytes)}, + ecc.BLS12_377.ScalarField(), + ) + assert.NoError(err) + + sbuf := make([]byte, fp_bls12381.Bytes) + sint.FillBytes(sbuf) + err = test.IsSolved( + &EmulatedToBytesCircuit[emparams.BLS12381Fp]{Expected: make([]uints.U8, len(sbuf))}, + &EmulatedToBytesCircuit[emparams.BLS12381Fp]{In: emulated.ValueOf[emparams.BLS12381Fp](sint), Expected: uints.NewU8Array(sbuf)}, + ecc.BLS12_377.ScalarField(), + ) + assert.NoError(err) + }, "length=exact") + + // case when the number of bytes is smaller than the length of the emulated field element + assert.Run(func(assert *test.Assert) { + bound := new(big.Int).Lsh(big.NewInt(1), fp_bls12381.Bytes-1) + sint, err := rand.Int(rand.Reader, bound) + assert.NoError(err, "failed to generate random int") + sbytes := make([]byte, fp_bls12381.Bytes) + sint.FillBytes(sbytes) + err = test.IsSolved( + &EmulatedToBytesCircuit[emparams.BLS12381Fp]{Expected: make([]uints.U8, len(sbytes))}, + &EmulatedToBytesCircuit[emparams.BLS12381Fp]{In: emulated.ValueOf[emparams.BLS12381Fp](sint), Expected: uints.NewU8Array(sbytes)}, + ecc.BLS12_377.ScalarField(), + ) + assert.NoError(err) + }, "length=smaller") +} + +type AssertBytesLeq struct { + In []uints.U8 + bound *big.Int +} + +func (c *AssertBytesLeq) Define(api frontend.API) error { + assertBytesLeq(api, c.In, c.bound) + return nil +} + +func TestAssertBytesLeq(t *testing.T) { + // all in MSB order, how big.Int is represented in bytes (most significant byte first) + assert := test.NewAssert(t) + + tc := func(assert *test.Assert, bound []byte, val []byte, isSuccess bool) { + assert.Run(func(assert *test.Assert) { + boundInt := new(big.Int).SetBytes(bound) + circuit := &AssertBytesLeq{In: make([]uints.U8, len(val)), bound: boundInt} + witness := &AssertBytesLeq{In: uints.NewU8Array(val)} + var opts []test.TestingOption + if isSuccess { + opts = append(opts, test.WithValidAssignment(witness)) + } else { + opts = append(opts, test.WithInvalidAssignment(witness)) + } + assert.CheckCircuit(circuit, opts...) + assert.Run(func(assert *test.Assert) { + // sanity check that actual big ints compare the same way. We do it in a separate test to avoid + // shadowing the circuit check test failure. + valInt := new(big.Int).SetBytes(val) + if boundInt.Cmp(valInt) >= 0 != isSuccess { + fmt.Println("boundInt:", boundInt, "valInt:", valInt, "expected:", isSuccess) + assert.Fail("boundInt.Cmp(valInt) >= 0 != expected") + } + }, "sanity") + }, fmt.Sprintf("bound=0x%x/val=0x%x/expected=%t", bound, val, isSuccess)) + } + + // -- first byte is smaller than the bound + // - second byte is smaller than the bound + tc(assert, []byte{253, 253}, []byte{252, 252}, true) + tc(assert, []byte{253, 253}, []byte{0, 252, 252}, true) + tc(assert, []byte{253, 253}, []byte{1, 252, 252}, false) + // - second byte is equal to the bound + tc(assert, []byte{253, 253}, []byte{252, 253}, true) + tc(assert, []byte{253, 253}, []byte{0, 252, 253}, true) + tc(assert, []byte{253, 253}, []byte{1, 252, 253}, false) + // - second byte is bigger than the bound + tc(assert, []byte{253, 253}, []byte{252, 254}, true) + tc(assert, []byte{253, 253}, []byte{0, 252, 254}, true) + tc(assert, []byte{253, 253}, []byte{1, 252, 254}, false) + + // -- first byte is equal to the bound + // - second byte is smaller than the bound + tc(assert, []byte{253, 253}, []byte{253, 252}, true) + tc(assert, []byte{253, 253}, []byte{0, 253, 252}, true) + tc(assert, []byte{253, 253}, []byte{1, 253, 252}, false) + // - second byte is equal to the bound + tc(assert, []byte{253, 253}, []byte{253, 253}, true) + tc(assert, []byte{253, 253}, []byte{0, 253, 253}, true) + tc(assert, []byte{253, 253}, []byte{1, 253, 253}, false) + // - second byte is bigger than the bound + tc(assert, []byte{253, 253}, []byte{253, 254}, false) + tc(assert, []byte{253, 253}, []byte{0, 253, 254}, false) + tc(assert, []byte{253, 253}, []byte{1, 253, 254}, false) + + // -- first byte is bigger than the bound + // - second byte is smaller than the bound + tc(assert, []byte{253, 253}, []byte{254, 252}, false) + tc(assert, []byte{253, 253}, []byte{0, 254, 252}, false) + tc(assert, []byte{253, 253}, []byte{1, 254, 252}, false) + // - second byte is equal to the bound + tc(assert, []byte{253, 253}, []byte{254, 253}, false) + tc(assert, []byte{253, 253}, []byte{0, 254, 253}, false) + tc(assert, []byte{253, 253}, []byte{1, 254, 253}, false) + // - second byte is bigger than the bound + tc(assert, []byte{253, 253}, []byte{254, 254}, false) + tc(assert, []byte{253, 253}, []byte{0, 254, 254}, false) + tc(assert, []byte{253, 253}, []byte{1, 254, 254}, false) + + // -- bound longer than the value + // - first byte is smaller than the bound + tc(assert, []byte{253, 253, 253}, []byte{252, 252}, true) + tc(assert, []byte{253, 253, 253}, []byte{0, 252, 252}, true) + // - first byte is equal to the bound + tc(assert, []byte{253, 253, 253}, []byte{253, 252}, true) + tc(assert, []byte{253, 253, 253}, []byte{0, 253, 252}, true) + // - first byte is bigger than the bound + tc(assert, []byte{253, 253, 253}, []byte{254, 252}, true) + tc(assert, []byte{253, 253, 253}, []byte{0, 254, 252}, true) +} diff --git a/std/conversion/hints.go b/std/conversion/hints.go new file mode 100644 index 0000000000..8604919f04 --- /dev/null +++ b/std/conversion/hints.go @@ -0,0 +1,36 @@ +package conversion + +import ( + "errors" + "fmt" + "math/big" + + "github.com/consensys/gnark/constraint/solver" +) + +func init() { + solver.RegisterHint(GetHints()...) +} + +func GetHints() []solver.Hint { + return []solver.Hint{ + nativeToBytesHint, + } +} + +func nativeToBytesHint(mod *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 1 { + return errors.New("expecting one input") + } + // we expect that we have exactly the number of outputs to represent the input. + nbBytes := (mod.BitLen() + 7) / 8 + if len(outputs) != nbBytes { + return fmt.Errorf("expecting %d outputs, got %d", nbBytes, len(outputs)) + } + buf := make([]byte, nbBytes) + inputs[0].FillBytes(buf) + for i := range nbBytes { + outputs[i].SetUint64(uint64(buf[i])) + } + return nil +} diff --git a/std/hints.go b/std/hints.go index 2053984fd0..740cb89647 100644 --- a/std/hints.go +++ b/std/hints.go @@ -14,6 +14,7 @@ import ( "github.com/consensys/gnark/std/algebra/native/sw_bls12377" "github.com/consensys/gnark/std/algebra/native/sw_bls24315" "github.com/consensys/gnark/std/algebra/native/twistededwards" + "github.com/consensys/gnark/std/conversion" "github.com/consensys/gnark/std/evmprecompiles" "github.com/consensys/gnark/std/hash/sha3" "github.com/consensys/gnark/std/internal/logderivarg" @@ -48,6 +49,7 @@ func registerHints() { solver.RegisterHint(bitslice.GetHints()...) solver.RegisterHint(sha3.GetHints()...) solver.RegisterHint(uints.GetHints()...) + solver.RegisterHint(conversion.GetHints()...) // emulated fields solver.RegisterHint(fields_bls12381.GetHints()...) solver.RegisterHint(fields_bn254.GetHints()...) From 48c07eb0581339addca35e7f4641c89369d3625f Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 15 Jul 2025 11:56:37 +0000 Subject: [PATCH 103/105] refactor: use conversion package for mapping to field --- std/algebra/emulated/sw_bls12381/map_to_g1.go | 39 +++++++++++++++- std/algebra/emulated/sw_bls12381/map_to_g2.go | 44 ++++--------------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/map_to_g1.go b/std/algebra/emulated/sw_bls12381/map_to_g1.go index 332559d7e9..d42275c6a5 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g1.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g1.go @@ -6,7 +6,9 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/conversion" "github.com/consensys/gnark/std/hash/expand" + "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/std/math/uints" ) @@ -198,7 +200,10 @@ func (g1 *G1) EncodeToG1(msg []uints.U8, dst []byte) (*G1Affine, error) { if err != nil { return nil, fmt.Errorf("expand msg: %w", err) } - el := bytesToElement(g1.api, g1.curveF, uniformBytes) + el, err := secureBytesToElement(g1.api, g1.curveF, uniformBytes) + if err != nil { + return nil, fmt.Errorf("bytes to element: %w", err) + } R, err := g1.MapToCurve1(el) if err != nil { return nil, fmt.Errorf("map to curve: %w", err) @@ -225,7 +230,10 @@ func (g1 *G1) HashToG1(msg []uints.U8, dst []byte) (*G1Affine, error) { return nil, fmt.Errorf("expand msg: %w", err) } for i := range els { - els[i] = bytesToElement(g1.api, g1.curveF, uniformBytes[i*secureBaseElementLen:(i+1)*secureBaseElementLen]) + els[i], err = secureBytesToElement(g1.api, g1.curveF, uniformBytes[i*secureBaseElementLen:(i+1)*secureBaseElementLen]) + if err != nil { + return nil, fmt.Errorf("bytes to element: %w", err) + } } Q0, err := g1.MapToCurve1(els[0]) if err != nil { @@ -241,3 +249,30 @@ func (g1 *G1) HashToG1(msg []uints.U8, dst []byte) (*G1Affine, error) { R = g1.ClearCofactor(R) return R, nil } + +func secureBytesToElement(api frontend.API, f *emulated.Field[BaseField], data []uints.U8) (*emulated.Element[BaseField], error) { + // we have sampled more bytes than is needed to serialize to field element. + // The goal is to have more uniform distribution of the output. + // + // However, we cannot easily initialize non-native elements with more than 48 bytes. So we instead look at + // x * 2^376 + y, where x are first 17 bytes and y are the last 47 bytes. + if len(data) != secureBaseElementLen { + return nil, fmt.Errorf("expected %d bytes, got %d", secureBaseElementLen, len(data)) + } + + hib := data[:secureBaseElementLen-fp.Bytes+1] + lob := data[secureBaseElementLen-fp.Bytes+1 : secureBaseElementLen] + // coeff is 2^376 % Fp + coeff := f.NewElement("153914086704665934422965000391185991426092731525255651046673021110334850669910978950836977558144201721900890587136") + hi, err := conversion.BytesToEmulated[BaseField](api, hib, conversion.WithAllowOverflow()) + if err != nil { + return nil, fmt.Errorf("convert hi bytes to emulated element: %w", err) + } + lo, err := conversion.BytesToEmulated[BaseField](api, lob, conversion.WithAllowOverflow()) + if err != nil { + return nil, fmt.Errorf("convert lo bytes to emulated element: %w", err) + } + res := f.Mul(hi, coeff) + res = f.Add(res, lo) + return res, nil +} diff --git a/std/algebra/emulated/sw_bls12381/map_to_g2.go b/std/algebra/emulated/sw_bls12381/map_to_g2.go index 556844f8db..63d51ef2f6 100644 --- a/std/algebra/emulated/sw_bls12381/map_to_g2.go +++ b/std/algebra/emulated/sw_bls12381/map_to_g2.go @@ -2,15 +2,12 @@ package sw_bls12381 import ( "fmt" - "math/big" - "slices" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/consensys/gnark-crypto/ecc/bls12-381/hash_to_curve" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/algebra/emulated/fields_bls12381" "github.com/consensys/gnark/std/hash/expand" - "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/std/math/uints" ) @@ -218,8 +215,10 @@ func (g2 *G2) EncodeToG2(msg []uints.U8, dst []byte) (*G2Affine, error) { } for i := range els { - // TODO: use conversion package when done - els[i] = bytesToElement(g2.api, g2.fp, uniformBytes[i*L:(i+1)*L]) + els[i], err = secureBytesToElement(g2.api, g2.fp, uniformBytes[i*L:(i+1)*L]) + if err != nil { + return nil, fmt.Errorf("convert bytes to emulated element: %w", err) + } } R, err := g2.MapToCurve2(&fields_bls12381.E2{A0: *els[0], A1: *els[1]}) if err != nil { @@ -260,8 +259,10 @@ func (g2 *G2) HashToG2(msg []uints.U8, dst []byte) (*G2Affine, error) { return nil, fmt.Errorf("expand msg: %w", err) } for i := range els { - // TODO: use conversion package when done - els[i] = bytesToElement(g2.api, g2.fp, uniformBytes[i*secureBaseElementLen:(i+1)*secureBaseElementLen]) + els[i], err = secureBytesToElement(g2.api, g2.fp, uniformBytes[i*secureBaseElementLen:(i+1)*secureBaseElementLen]) + if err != nil { + return nil, fmt.Errorf("convert bytes to emulated element: %w", err) + } } // we will still do iso_map before point addition, as we do not have point addition in E' (yet) @@ -280,32 +281,3 @@ func (g2 *G2) HashToG2(msg []uints.U8, dst []byte) (*G2Affine, error) { return g2.ClearCofactor(R), nil } - -func bytesToElement(api frontend.API, fp *emulated.Field[emulated.BLS12381Fp], data []uints.U8) *emulated.Element[BaseField] { - // TODO(ivokub) NB! This function is a temporary workaround to convert bytes to an element. We will replace it soon. - // - // NB! it modifies data in place, but in the current usage it is not an issue as we only use it once per bytes - - // data in BE, need to convert to LE - slices.Reverse(data) - - bits := make([]frontend.Variable, len(data)*8) - for i := 0; i < len(data); i++ { - u8 := data[i] - u8Bits := api.ToBinary(u8.Val, 8) - for j := 0; j < 8; j++ { - bits[i*8+j] = u8Bits[j] - } - } - - cutoff := 17 - tailBits, headBits := bits[:cutoff*8], bits[cutoff*8:] - tail := fp.FromBits(tailBits...) - head := fp.FromBits(headBits...) - - byteMultiplier := big.NewInt(256) - headMultiplier := byteMultiplier.Exp(byteMultiplier, big.NewInt(int64(cutoff)), big.NewInt(0)) - head = fp.MulConst(head, headMultiplier) - - return fp.Add(head, tail) -} From 17185612bfe7c324e0cefb60b582f837a82e0a09 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 15 Jul 2025 12:09:13 +0000 Subject: [PATCH 104/105] refactor: use xor directly --- std/hash/expand/expand.go | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/std/hash/expand/expand.go b/std/hash/expand/expand.go index cb93879d00..4f65c0b3aa 100644 --- a/std/hash/expand/expand.go +++ b/std/hash/expand/expand.go @@ -21,6 +21,10 @@ import ( // [RFC9380 Section 5.3.1]: https://datatracker.ietf.org/doc/html/rfc9380#name-expand_message_xmd // [gnark-crypto]: https://github.com/consensys/gnark-crypto/blob/master/field/hash/hashutils.go#L11 func ExpandMsgXmd(api frontend.API, msg []uints.U8, dst []byte, lenInBytes int) ([]uints.U8, error) { + uapi, err := uints.NewBytes(api) + if err != nil { + return nil, fmt.Errorf("new uints.Bytes: %w", err) + } h, err := sha2.New(api) if err != nil { return nil, fmt.Errorf("new hasher: %w", err) @@ -74,11 +78,7 @@ func ExpandMsgXmd(api frontend.API, msg []uints.U8, dst []byte, lenInBytes int) // b_i = H(strxor(b₀, b_(i - 1)) ∥ I2OSP(i, 1) ∥ DST_prime) strxor := make([]uints.U8, h.Size()) for j := 0; j < h.Size(); j++ { - // TODO: use here uints.Bytes Xor when finally implemented - strxor[j], err = xor(api, b0[j], b1[j]) - if err != nil { - return res, err - } + strxor[j] = uapi.Xor(b0[j], b1[j]) } h.Write(strxor) h.Write([]uints.U8{uints.NewU8(uint8(i))}) @@ -89,20 +89,3 @@ func ExpandMsgXmd(api frontend.API, msg []uints.U8, dst []byte, lenInBytes int) return res, nil } - -func xor(api frontend.API, a, b uints.U8) (uints.U8, error) { - // TODO: when done with conversion package then can remove this function - aBits := api.ToBinary(a.Val, 8) - bBits := api.ToBinary(b.Val, 8) - cBits := make([]frontend.Variable, 8) - - for i := 0; i < 8; i++ { - cBits[i] = api.Xor(aBits[i], bBits[i]) - } - - uapi, err := uints.New[uints.U32](api) - if err != nil { - return uints.NewU8(255), err - } - return uapi.ByteValueOf(api.FromBinary(cBits...)), nil -} From 44ab4051ac0868f05ef1dfe6b2fd4119ec5eb588 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 4 Sep 2025 12:54:52 +0000 Subject: [PATCH 105/105] test: add all test cases from eth --- std/signature/bls/blssig_test.go | 51 +++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/std/signature/bls/blssig_test.go b/std/signature/bls/blssig_test.go index 68a329c499..dd0fba851c 100644 --- a/std/signature/bls/blssig_test.go +++ b/std/signature/bls/blssig_test.go @@ -22,24 +22,55 @@ func (c *blsG2SigCircuit) Define(api frontend.API) error { return c.Pub.Verify(api, &c.Sig, c.Msg) } -var testCasesG2Sig = []struct { +type testCaseG2 struct { pubkey string msg string sig string output bool -}{ - { - "a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a", - "5656565656565656565656565656565656565656565656565656565656565656", - "882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb", - true, - }, +} + +// testCasesG2Sig is a list of test vectors for BLS signature verification on G2. Obtained from +// https://github.com/ethereum/bls12-381-tests by parsing tests in `verify/` folder +var testCasesG2Sig = []testCaseG2{ + {"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "1212121212121212121212121212121212121212121212121212121212121212", "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", false}, + {"b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f", "abababababababababababababababababababababababababababababababab", "ae82747ddeefe4fd64cf9cedb9b04ae3e8a43420cd255e3c7cd06a8d88b7c7f8638543719981c5d16fa3527c468c25f0026704a6951bde891360c7e8d12ddee0559004ccdbe6046b55bae1b257ee97f7cdb955773d7cf29adf3ccbb9ffffffff", false}, + {"a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a", "5656565656565656565656565656565656565656565656565656565656565656", "882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972ffffffff", false}, + {"b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81", "0000000000000000000000000000000000000000000000000000000000000000", "b23c46be3a001c63ca711f87a005c200cc550b9429d5f4eb38d74322144f1b63926da3388979e5321012fb1a0526bcd100b5ef5fe72628ce4cd5e904aeaa3279527843fae5ca9ca675f4f51ed8f83bbf7155da9ecc9663100a885d5dffffffff", false}, + {"b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81", "5656565656565656565656565656565656565656565656565656565656565656", "af1390c3c47acdb37131a51216da683c509fce0e954328a59f93aebda7e4ff974ba208d9a4a2a2389f892a9d418d618418dd7f7a6bc7aa0da999a9d3a5b815bc085e14fd001f6a1948768a3f4afefc8b8240dda329f984cb345c6363ffffffff", false}, + {"b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f", "5656565656565656565656565656565656565656565656565656565656565656", "a4efa926610b8bd1c8330c918b7a5e9bf374e53435ef8b7ec186abf62e1b1f65aeaaeb365677ac1d1172a1f5b44b4e6d022c252c58486c0a759fbdc7de15a756acc4d343064035667a594b4c2a6f0b0b421975977f297dba63ee2f63ffffffff", false}, + {"b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81", "abababababababababababababababababababababababababababababababab", "9674e2228034527f4c083206032b020310face156d4a4685e2fcaec2f6f3665aa635d90347b6ce124eb879266b1e801d185de36a0a289b85e9039662634f2eea1e02e670bc7ab849d006a70b2f93b84597558a05b879c8d445f387a5ffffffff", false}, + {"a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a", "abababababababababababababababababababababababababababababababab", "91347bccf740d859038fcdcaf233eeceb2a436bcaaee9b2aa3bfb70efe29dfb2677562ccbea1c8e061fb9971b0753c240622fab78489ce96768259fc01360346da5b9f579e5da0d941e4c6ba18a0e64906082375394f337fa1af2b71ffffffff", false}, + {"b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f", "0000000000000000000000000000000000000000000000000000000000000000", "948a7cb99f76d616c2c564ce9bf4a519f1bea6b0a624a02276443c245854219fabb8d4ce061d255af5330b078d5380681751aa7053da2c98bae898edc218c75f07e24d8802a17cd1f6833b71e58f5eb5b94208b4d0bb3848cecb075effffffff", false}, + {"a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a", "0000000000000000000000000000000000000000000000000000000000000000", "b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380bffffffff", false}, + {"b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f", "abababababababababababababababababababababababababababababababab", "ae82747ddeefe4fd64cf9cedb9b04ae3e8a43420cd255e3c7cd06a8d88b7c7f8638543719981c5d16fa3527c468c25f0026704a6951bde891360c7e8d12ddee0559004ccdbe6046b55bae1b257ee97f7cdb955773d7cf29adf3ccbb9975e4eb9", true}, + {"a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a", "5656565656565656565656565656565656565656565656565656565656565656", "882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb", true}, + {"b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81", "0000000000000000000000000000000000000000000000000000000000000000", "b23c46be3a001c63ca711f87a005c200cc550b9429d5f4eb38d74322144f1b63926da3388979e5321012fb1a0526bcd100b5ef5fe72628ce4cd5e904aeaa3279527843fae5ca9ca675f4f51ed8f83bbf7155da9ecc9663100a885d5dc6df96d9", true}, + {"b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81", "5656565656565656565656565656565656565656565656565656565656565656", "af1390c3c47acdb37131a51216da683c509fce0e954328a59f93aebda7e4ff974ba208d9a4a2a2389f892a9d418d618418dd7f7a6bc7aa0da999a9d3a5b815bc085e14fd001f6a1948768a3f4afefc8b8240dda329f984cb345c6363272ba4fe", true}, + {"b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f", "5656565656565656565656565656565656565656565656565656565656565656", "a4efa926610b8bd1c8330c918b7a5e9bf374e53435ef8b7ec186abf62e1b1f65aeaaeb365677ac1d1172a1f5b44b4e6d022c252c58486c0a759fbdc7de15a756acc4d343064035667a594b4c2a6f0b0b421975977f297dba63ee2f63ffe47bb6", true}, + {"b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81", "abababababababababababababababababababababababababababababababab", "9674e2228034527f4c083206032b020310face156d4a4685e2fcaec2f6f3665aa635d90347b6ce124eb879266b1e801d185de36a0a289b85e9039662634f2eea1e02e670bc7ab849d006a70b2f93b84597558a05b879c8d445f387a5d5b653df", true}, + {"a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a", "abababababababababababababababababababababababababababababababab", "91347bccf740d859038fcdcaf233eeceb2a436bcaaee9b2aa3bfb70efe29dfb2677562ccbea1c8e061fb9971b0753c240622fab78489ce96768259fc01360346da5b9f579e5da0d941e4c6ba18a0e64906082375394f337fa1af2b7127b0d121", true}, + {"b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f", "0000000000000000000000000000000000000000000000000000000000000000", "948a7cb99f76d616c2c564ce9bf4a519f1bea6b0a624a02276443c245854219fabb8d4ce061d255af5330b078d5380681751aa7053da2c98bae898edc218c75f07e24d8802a17cd1f6833b71e58f5eb5b94208b4d0bb3848cecb075ea21be115", true}, + {"a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a", "0000000000000000000000000000000000000000000000000000000000000000", "b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55", true}, + {"b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f", "abababababababababababababababababababababababababababababababab", "9674e2228034527f4c083206032b020310face156d4a4685e2fcaec2f6f3665aa635d90347b6ce124eb879266b1e801d185de36a0a289b85e9039662634f2eea1e02e670bc7ab849d006a70b2f93b84597558a05b879c8d445f387a5d5b653df", false}, + {"a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a", "5656565656565656565656565656565656565656565656565656565656565656", "a4efa926610b8bd1c8330c918b7a5e9bf374e53435ef8b7ec186abf62e1b1f65aeaaeb365677ac1d1172a1f5b44b4e6d022c252c58486c0a759fbdc7de15a756acc4d343064035667a594b4c2a6f0b0b421975977f297dba63ee2f63ffe47bb6", false}, + {"b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81", "0000000000000000000000000000000000000000000000000000000000000000", "b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55", false}, + {"b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81", "5656565656565656565656565656565656565656565656565656565656565656", "882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb", false}, + {"b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f", "5656565656565656565656565656565656565656565656565656565656565656", "af1390c3c47acdb37131a51216da683c509fce0e954328a59f93aebda7e4ff974ba208d9a4a2a2389f892a9d418d618418dd7f7a6bc7aa0da999a9d3a5b815bc085e14fd001f6a1948768a3f4afefc8b8240dda329f984cb345c6363272ba4fe", false}, + {"b301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81", "abababababababababababababababababababababababababababababababab", "91347bccf740d859038fcdcaf233eeceb2a436bcaaee9b2aa3bfb70efe29dfb2677562ccbea1c8e061fb9971b0753c240622fab78489ce96768259fc01360346da5b9f579e5da0d941e4c6ba18a0e64906082375394f337fa1af2b7127b0d121", false}, + {"a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a", "abababababababababababababababababababababababababababababababab", "ae82747ddeefe4fd64cf9cedb9b04ae3e8a43420cd255e3c7cd06a8d88b7c7f8638543719981c5d16fa3527c468c25f0026704a6951bde891360c7e8d12ddee0559004ccdbe6046b55bae1b257ee97f7cdb955773d7cf29adf3ccbb9975e4eb9", false}, + {"b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f", "0000000000000000000000000000000000000000000000000000000000000000", "b23c46be3a001c63ca711f87a005c200cc550b9429d5f4eb38d74322144f1b63926da3388979e5321012fb1a0526bcd100b5ef5fe72628ce4cd5e904aeaa3279527843fae5ca9ca675f4f51ed8f83bbf7155da9ecc9663100a885d5dc6df96d9", false}, + {"a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a", "0000000000000000000000000000000000000000000000000000000000000000", "948a7cb99f76d616c2c564ce9bf4a519f1bea6b0a624a02276443c245854219fabb8d4ce061d255af5330b078d5380681751aa7053da2c98bae898edc218c75f07e24d8802a17cd1f6833b71e58f5eb5b94208b4d0bb3848cecb075ea21be115", false}, + {"97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", "1212121212121212121212121212121212121212121212121212121212121212", "a42ae16f1c2a5fa69c04cb5998d2add790764ce8dd45bf25b29b4700829232052b52352dcff1cf255b3a7810ad7269601810f03b2bc8b68cf289cf295b206770605a190b6842583e47c3d1c0f73c54907bfb2a602157d46a4353a20283018763", true}, } func TestMinimalPublicKeyTestSolve(t *testing.T) { assert := test.NewAssert(t) for _, tc := range testCasesG2Sig { + if !tc.output { + // we skip negative test cases as the current implementation only supports positive cases + continue + } pubB, err := hex.DecodeString(tc.pubkey) assert.NoError(err, "failed to decode pubkey hex") msgB, err := hex.DecodeString(tc.msg) @@ -62,5 +93,9 @@ func TestMinimalPublicKeyTestSolve(t *testing.T) { Sig: SignatureG2(sw_bls12381.NewG2Affine(sig)), }, ecc.BN254.ScalarField()) assert.NoError(err, "test solve failed") + if testing.Short() { + t.Log("skipping rest of tests in short mode") + return + } } }