diff --git a/go.mod b/go.mod index b7b70697ee..d013932516 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.27 + github.com/consensys/bavard v0.1.29 github.com/consensys/compress v0.2.5 - github.com/consensys/gnark-crypto v0.16.1-0.20250205153847-10a243d332ca + github.com/consensys/gnark-crypto v0.16.1-0.20250218155240-b2e7aa1a22d7 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 @@ -19,9 +19,9 @@ require ( github.com/ronanh/intcomp v1.1.0 github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.10.0 - golang.org/x/crypto v0.32.0 + golang.org/x/crypto v0.33.0 golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 - golang.org/x/sync v0.10.0 + golang.org/x/sync v0.11.0 ) require ( @@ -31,7 +31,7 @@ require ( github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/sys v0.29.0 // indirect + golang.org/x/sys v0.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index 200c8c71ea..860625a7c0 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.27 h1:j6hKUrGAy/H+gpNrpLU3I26n1yc+VMGmd6ID5+gAhOs= -github.com/consensys/bavard v0.1.27/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= +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/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.20250205153847-10a243d332ca h1:u6iXwMBfbXODF+hDSwKSTBg6yfD3+eMX6o3PILAK474= -github.com/consensys/gnark-crypto v0.16.1-0.20250205153847-10a243d332ca/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU= +github.com/consensys/gnark-crypto v0.16.1-0.20250218155240-b2e7aa1a22d7 h1:8Xrc1ESCFpdgsE9+drHisc8TQz8uFQGHl0rG13+tXlk= +github.com/consensys/gnark-crypto v0.16.1-0.20250218155240-b2e7aa1a22d7/go.mod h1:A2URlMHUT81ifJ0UlLzSlm7TmnE3t7VxEThApdMukJw= 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= @@ -304,8 +304,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -410,8 +410,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -462,8 +462,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/std/algebra/native/sw_grumpkin/doc.go b/std/algebra/native/sw_grumpkin/doc.go new file mode 100644 index 0000000000..d31ec1ed40 --- /dev/null +++ b/std/algebra/native/sw_grumpkin/doc.go @@ -0,0 +1,7 @@ +// Package sw_grumpkin implements the arithmetics on ithe Grumpkin curve as a +// SNARK circuit over BN254. These two curves form a 2-cycle so the operations +// use native field arithmetic. +// +// References: +// https://aztecprotocol.github.io/aztec-connect/primitives.html/ +package sw_grumpkin diff --git a/std/algebra/native/sw_grumpkin/g1.go b/std/algebra/native/sw_grumpkin/g1.go new file mode 100644 index 0000000000..740f63a1df --- /dev/null +++ b/std/algebra/native/sw_grumpkin/g1.go @@ -0,0 +1,706 @@ +// Copyright 2020-2025 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +package sw_grumpkin + +import ( + "fmt" + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + fr_bn "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/consensys/gnark-crypto/ecc/grumpkin" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/algopts" +) + +// G1Affine point in affine coords +type G1Affine struct { + X, Y frontend.Variable +} + +// Neg outputs -p +func (p *G1Affine) Neg(api frontend.API, p1 G1Affine) *G1Affine { + p.X = p1.X + p.Y = api.Sub(0, p1.Y) + return p +} + +// AddAssign adds p1 to p using the affine formulas with division, and return p +func (p *G1Affine) AddAssign(api frontend.API, p1 G1Affine) *G1Affine { + + // compute lambda = (p1.y-p.y)/(p1.x-p.x) + lambda := api.DivUnchecked(api.Sub(p1.Y, p.Y), api.Sub(p1.X, p.X)) + + // xr = lambda**2-p.x-p1.x + xr := api.Sub(api.Mul(lambda, lambda), api.Add(p.X, p1.X)) + + // p.y = lambda(p.x-xr) - p.y + p.Y = api.Sub(api.Mul(lambda, api.Sub(p.X, xr)), p.Y) + + //p.x = xr + p.X = xr + return p +} + +func (p *G1Affine) AddUnified(api frontend.API, q G1Affine) *G1Affine { + // selector1 = 1 when p is (0,0) and 0 otherwise + selector1 := api.And(api.IsZero(p.X), api.IsZero(p.Y)) + // selector2 = 1 when q is (0,0) and 0 otherwise + selector2 := api.And(api.IsZero(q.X), api.IsZero(q.Y)) + + // λ = ((p.x+q.x)² - p.x*q.x + a)/(p.y + q.y) + pxqx := api.Mul(p.X, q.X) + pxplusqx := api.Add(p.X, q.X) + num := api.Mul(pxplusqx, pxplusqx) + num = api.Sub(num, pxqx) + denum := api.Add(p.Y, q.Y) + // if p.y + q.y = 0, assign dummy 1 to denum and continue + selector3 := api.IsZero(denum) + denum = api.Select(selector3, 1, denum) + λ := api.Div(num, denum) + + // x = λ^2 - p.x - q.x + xr := api.Mul(λ, λ) + xr = api.Sub(xr, pxplusqx) + + // y = λ(p.x - xr) - p.y + yr := api.Sub(p.X, xr) + yr = api.Mul(yr, λ) + yr = api.Sub(yr, p.Y) + result := G1Affine{ + X: xr, + Y: yr, + } + + // if p=(0,0) return q + result.Select(api, selector1, q, result) + // if q=(0,0) return p + result.Select(api, selector2, *p, result) + // if p.y + q.y = 0, return (0, 0) + result.Select(api, selector3, G1Affine{0, 0}, result) + + p.X = result.X + p.Y = result.Y + + return p +} + +// Select sets p1 if b=1, p2 if b=0, and returns it. b must be boolean constrained +func (p *G1Affine) Select(api frontend.API, b frontend.Variable, p1, p2 G1Affine) *G1Affine { + + p.X = api.Select(b, p1.X, p2.X) + p.Y = api.Select(b, p1.Y, p2.Y) + + return p + +} + +// Lookup2 performs a 2-bit lookup between p1, p2, p3, p4 based on bits b0 and b1. +// Returns: +// - p1 if b0=0 and b1=0, +// - p2 if b0=1 and b1=0, +// - p3 if b0=0 and b1=1, +// - p4 if b0=1 and b1=1. +func (p *G1Affine) Lookup2(api frontend.API, b1, b2 frontend.Variable, p1, p2, p3, p4 G1Affine) *G1Affine { + + p.X = api.Lookup2(b1, b2, p1.X, p2.X, p3.X, p4.X) + p.Y = api.Lookup2(b1, b2, p1.Y, p2.Y, p3.Y, p4.Y) + + return p + +} + +// Double double a point in affine coords +func (p *G1Affine) Double(api frontend.API, p1 G1Affine) *G1Affine { + + var three, two big.Int + three.SetInt64(3) + two.SetInt64(2) + + // compute lambda = (3*p1.x**2+a)/2*p1.y, here we assume a=0 (j invariant 0 curve) + lambda := api.DivUnchecked(api.Mul(p1.X, p1.X, three), api.Mul(p1.Y, two)) + + // xr = lambda**2-2*p1.x + xr := api.Sub(api.Mul(lambda, lambda), api.Mul(p1.X, two)) + + // p.y = lambda(p.x-xr) - p.y + p.Y = api.Sub(api.Mul(lambda, api.Sub(p1.X, xr)), p1.Y) + + //p.x = xr + p.X = xr + + return p +} + +// ScalarMul sets P = [s] Q and returns P. +// +// The method chooses an implementation based on scalar s. If it is constant, +// then the compiled circuit depends on s. If it is variable type, then +// the circuit is independent of the inputs. +func (P *G1Affine) ScalarMul(api frontend.API, Q G1Affine, s interface{}, opts ...algopts.AlgebraOption) *G1Affine { + if n, ok := api.Compiler().ConstantValue(s); ok { + return P.constScalarMul(api, Q, n, opts...) + } else { + return P.varScalarMul(api, Q, s, opts...) + } +} + +// constScalarMul sets P = [s] Q and returns P. +func (P *G1Affine) constScalarMul(api frontend.API, Q G1Affine, s *big.Int, opts ...algopts.AlgebraOption) *G1Affine { + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(err) + } + if s.BitLen() == 0 { + P.X = 0 + P.Y = 0 + return P + } + // see the comments in varScalarMul. However, two-bit lookup is cheaper if + // bits are constant and here it makes sense to use the table in the main + // loop. + var Acc, negQ, negPhiQ, phiQ G1Affine + cc := getInnerCurveConfig(api.Compiler().Field()) + s.Mod(s, cc.fr) + cc.phi1Neg(api, &phiQ, &Q) + phiQ.Neg(api, phiQ) + + k := ecc.SplitScalar(s, cc.glvBasis) + if k[0].Sign() == -1 { + k[0].Neg(&k[0]) + Q.Neg(api, Q) + } + if k[1].Sign() == -1 { + k[1].Neg(&k[1]) + phiQ.Neg(api, phiQ) + } + nbits := k[0].BitLen() + if k[1].BitLen() > nbits { + nbits = k[1].BitLen() + } + negQ.Neg(api, Q) + negPhiQ.Neg(api, phiQ) + var table [4]G1Affine + table[0] = negQ + table[1] = Q + table[2] = negQ + table[3] = Q + + if cfg.CompleteArithmetic { + table[0].AddUnified(api, negPhiQ) + table[1].AddUnified(api, negPhiQ) + table[2].AddUnified(api, phiQ) + table[3].AddUnified(api, phiQ) + } else { + table[0].AddAssign(api, negPhiQ) + table[1].AddAssign(api, negPhiQ) + table[2].AddAssign(api, phiQ) + table[3].AddAssign(api, phiQ) + } + + Acc = table[3] + // if both high bits are set, then we would get to the incomplete part, + // handle it separately. + if k[0].Bit(nbits-1) == 1 && k[1].Bit(nbits-1) == 1 { + if cfg.CompleteArithmetic { + Acc.AddUnified(api, Acc) + Acc.AddUnified(api, table[3]) + } else { + Acc.Double(api, Acc) + Acc.AddAssign(api, table[3]) + } + nbits = nbits - 1 + } + for i := nbits - 1; i > 0; i-- { + if cfg.CompleteArithmetic { + Acc.AddUnified(api, Acc) + Acc.AddUnified(api, table[k[0].Bit(i)+2*k[1].Bit(i)]) + } else { + Acc.DoubleAndAdd(api, &Acc, &table[k[0].Bit(i)+2*k[1].Bit(i)]) + } + } + + // i = 0 + if cfg.CompleteArithmetic { + negQ.AddUnified(api, Acc) + Acc.Select(api, k[0].Bit(0), Acc, negQ) + negPhiQ.AddUnified(api, Acc) + } else { + negQ.AddAssign(api, Acc) + Acc.Select(api, k[0].Bit(0), Acc, negQ) + negPhiQ.AddAssign(api, Acc) + } + Acc.Select(api, k[1].Bit(0), Acc, negPhiQ) + P.X, P.Y = Acc.X, Acc.Y + + return P +} + +// endoarScalarMul sets P = [s]Q and returns P. 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 (P *G1Affine) varScalarMul(api frontend.API, Q G1Affine, s frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(err) + } + var selector frontend.Variable + if cfg.CompleteArithmetic { + // if Q=(0,0) we assign a dummy (1,1) to Q and continue + selector = api.And(api.IsZero(Q.X), api.IsZero(Q.Y)) + Q.Select(api, selector, G1Affine{X: 1, Y: 1}, Q) + } + + // We use the endomorphism à la GLV to compute [s]Q as + // [s1]Q + [s2]Φ(Q) + // + // The context we are working is based on the `outer` curve. However, the + // points and the operations on the points are performed on the `inner` + // curve of the outer curve. We require some parameters from the inner + // curve. + cc := getInnerCurveConfig(api.Compiler().Field()) + + s1, s2 := callDecomposeScalar(api, s, true) + + nbits := 127 + s1bits := api.ToBinary(s1, nbits) + s2bits := api.ToBinary(s2, nbits) + + // precompute -Q, -Φ(Q), Φ(Q) + var tableQ, tablePhiQ [2]G1Affine + tableQ[1] = Q + tableQ[0].Neg(api, Q) + cc.phi1Neg(api, &tablePhiQ[1], &Q) + tablePhiQ[0].Neg(api, tablePhiQ[1]) + + // we suppose that the first bits of the sub-scalars are 1 and set: + // Acc = Q + Φ(Q) + var Acc, B G1Affine + Acc = Q + Acc.AddAssign(api, tablePhiQ[1]) + + // At each iteration we need to compute: + // [2]Acc ± Q ± Φ(Q). + // We can compute [2]Acc and look up the (precomputed) point B from: + // B1 = +Q + Φ(Q) + B1 := Acc + // B2 = -Q - Φ(Q) + B2 := G1Affine{} + B2.Neg(api, B1) + // B3 = +Q - Φ(Q) + B3 := tableQ[1] + B3.AddAssign(api, tablePhiQ[0]) + // B4 = -Q + Φ(Q) + B4 := G1Affine{} + B4.Neg(api, B3) + // + // Note that half the points are negatives of the other half, + // hence have the same X coordinates. + + // We add G (the base point) to Acc to avoid incomplete additions in the + // loop, because when doing doubleAndAdd(Acc, Bi) as (Acc+Bi)+Acc it might + // happen that Acc==Bi or Acc==-Bi. But now we force Acc to be different + // than the stored Bi. However, at the end, we need to subtract [2^nbits]G. + mPoints := getCurvePoints() + Acc.AddAssign(api, G1Affine{X: mPoints.G1x, Y: mPoints.G1y}) + + for i := nbits - 1; i > 0; i-- { + B.X = api.Select(api.Xor(s1bits[i], s2bits[i]), B3.X, B2.X) + B.Y = api.Lookup2(s1bits[i], s2bits[i], B2.Y, B3.Y, B4.Y, B1.Y) + // Acc = [2]Acc + B + Acc.DoubleAndAdd(api, &Acc, &B) + } + + // i = 0 + // subtract the Q, R, Φ(Q), Φ(R) 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). + if cfg.CompleteArithmetic { + tableQ[0].AddUnified(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableQ[0]) + tablePhiQ[0].AddUnified(api, Acc) + Acc.Select(api, s2bits[0], Acc, tablePhiQ[0]) + Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) + } else { + tableQ[0].AddAssign(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableQ[0]) + tablePhiQ[0].AddAssign(api, Acc) + Acc.Select(api, s2bits[0], Acc, tablePhiQ[0]) + } + + // subtract H=[2^N]G since we added G at the beginning + negH := G1Affine{X: mPoints.G1m[nbits-1][0], Y: api.Neg(mPoints.G1m[nbits-1][1])} + Acc.AddUnified(api, negH) + if cfg.CompleteArithmetic { + Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) + } + *P = Acc + + return P +} + +// genericScalarMul sets P = [s] Q and returns P. +// It computes the standard little-endian double-and-add algorithm +// (Algorithm 3.26, Guide to Elliptic Curve Cryptography) +func (P *G1Affine) genericScalarMul(api frontend.API, Q G1Affine, s frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(fmt.Sprintf("parse opts: %v", err)) + } + *P = Q + var selector frontend.Variable + if cfg.CompleteArithmetic { + // if Q=(0,0) we assign a dummy (1,1) to P and continue + selector = api.And(api.IsZero(P.X), api.IsZero(P.Y)) + P.Select(api, selector, G1Affine{X: 1, Y: 1}, *P) + } + + nBits := 254 + sBits := api.ToBinary(s, nBits) + + var temp, doubles G1Affine + doubles.Double(api, *P) + + for i := 1; i < nBits-1; i++ { + temp = *P + temp.AddAssign(api, doubles) + P.Select(api, sBits[i], temp, *P) + doubles.Double(api, doubles) + } + + // i = nBits - 1 + temp = *P + temp.AddAssign(api, doubles) + P.Select(api, sBits[nBits-1], temp, *P) + + // 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. + temp = *P + temp.AddUnified(api, *doubles.Neg(api, Q)) + P.Select(api, sBits[0], *P, temp) + + if cfg.CompleteArithmetic { + // if Q=(0,0), return (0,0) + P.Select(api, selector, G1Affine{X: 0, Y: 0}, *P) + } + + return P +} + +// Assign a value to self (witness assignment) +func (p *G1Affine) Assign(p1 *grumpkin.G1Affine) { + p.X = (fr_bn.Element)(p1.X) + p.Y = (fr_bn.Element)(p1.Y) +} + +// AssertIsEqual constraint self to be equal to other into the given constraint system +func (p *G1Affine) AssertIsEqual(api frontend.API, other G1Affine) { + api.AssertIsEqual(p.X, other.X) + api.AssertIsEqual(p.Y, other.Y) +} + +// DoubleAndAdd computes 2*p1+p in affine coords +func (p *G1Affine) DoubleAndAdd(api frontend.API, p1, p2 *G1Affine) *G1Affine { + + // compute lambda1 = (y2-y1)/(x2-x1) + l1 := api.DivUnchecked(api.Sub(p1.Y, p2.Y), api.Sub(p1.X, p2.X)) + + // compute x3 = lambda1**2-x1-x2 + x3 := api.Mul(l1, l1) + x3 = api.Sub(x3, api.Add(p1.X, p2.X)) + + // omit y3 computation + // compute lambda2 = lambda1+2*y1/(x3-x1) + l2 := api.DivUnchecked(api.Mul(p1.Y, big.NewInt(2)), api.Sub(x3, p1.X)) + l2 = api.Add(l2, l1) + + // compute x4 =lambda2**2-x1-x3 + x4 := api.Mul(l2, l2) + x4 = api.Sub(x4, api.Add(p1.X, x3)) + + // compute y4 = lambda2*(x4 - x1)-y1 + y4 := api.Sub(x4, p1.X) + y4 = api.Mul(l2, y4) + y4 = api.Sub(y4, p1.Y) + + p.X = x4 + p.Y = y4 + + return p +} + +// DoubleAndAddSelect computes 2*p1+p2 or condtionally 2*p2+p1 in affine coords +func (p *G1Affine) DoubleAndAddSelect(api frontend.API, b frontend.Variable, p1, p2 *G1Affine) *G1Affine { + + // compute lambda1 = (y2-y1)/(x2-x1) + l1 := api.DivUnchecked(api.Sub(p1.Y, p2.Y), api.Sub(p1.X, p2.X)) + + // compute x3 = lambda1**2-x1-x2 + x3 := api.Mul(l1, l1) + x3 = api.Sub(x3, api.Add(p1.X, p2.X)) + + // omit y3 computation + + // conditional second addition + var t G1Affine + t.Select(api, b, *p1, *p2) + + // compute lambda2 = lambda1+2*y1/(x3-x1) + l2 := api.DivUnchecked(api.Mul(t.Y, big.NewInt(2)), api.Sub(x3, t.X)) + l2 = api.Add(l2, l1) + + // compute x4 =lambda2**2-x1-x3 + x4 := api.Mul(l2, l2) + x4 = api.Sub(x4, api.Add(t.X, x3)) + + // compute y4 = lambda2*(x4 - x1)-y1 + y4 := api.Sub(x4, t.X) + y4 = api.Mul(l2, y4) + y4 = api.Sub(y4, t.Y) + + p.X = x4 + p.Y = y4 + + return p +} + +// ScalarMulBase computes s * g1 and returns it, where g1 is the fixed generator. It doesn't modify s. +func (P *G1Affine) ScalarMulBase(api frontend.API, s frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { + g1aff, _ := grumpkin.Generators() + generator := G1Affine{ + X: g1aff.X.BigInt(new(big.Int)), + Y: g1aff.Y.BigInt(new(big.Int)), + } + return P.ScalarMul(api, generator, s, opts...) +} + +func (P *G1Affine) jointScalarMul(api frontend.API, Q, R G1Affine, s, t frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(err) + } + if cfg.CompleteArithmetic { + var tmp G1Affine + P.ScalarMul(api, Q, s, opts...) + tmp.ScalarMul(api, R, t, opts...) + P.AddUnified(api, tmp) + } else { + P.jointScalarMulGLVUnsafe(api, Q, R, s, t) + } + return P +} + +// P = [s]Q + [t]R using Shamir's trick +func (P *G1Affine) jointScalarMulUnsafe(api frontend.API, Q, R G1Affine, s, t frontend.Variable) *G1Affine { + var Acc, B1, QNeg, RNeg G1Affine + QNeg.Neg(api, Q) + RNeg.Neg(api, R) + + // Acc = P1 + P2 + Acc = Q + Acc.AddAssign(api, R) + + nbits := 254 + sbits := api.ToBinary(s, nbits) + tbits := api.ToBinary(t, nbits) + + for i := nbits - 1; i > 0; i-- { + B1 = G1Affine{ + X: QNeg.X, + Y: api.Select(sbits[i], Q.Y, QNeg.Y), + } + Acc.DoubleAndAdd(api, &Acc, &B1) + B1 = G1Affine{ + X: RNeg.X, + Y: api.Select(tbits[i], R.Y, RNeg.Y), + } + Acc.AddAssign(api, B1) + + } + + // i = 0 + QNeg.AddAssign(api, Acc) + Acc.Select(api, sbits[0], Acc, QNeg) + RNeg.AddAssign(api, Acc) + P.Select(api, tbits[0], Acc, RNeg) + + return P +} + +// P = [s]Q + [t]R using Shamir's trick and endomorphism +func (P *G1Affine) jointScalarMulGLVUnsafe(api frontend.API, Q, R G1Affine, s, t frontend.Variable) *G1Affine { + cc := getInnerCurveConfig(api.Compiler().Field()) + s1, s2 := callDecomposeScalar(api, s, false) + t1, t2 := callDecomposeScalar(api, t, false) + nbits := cc.fr.BitLen()>>1 + 1 + + s1bits := api.ToBinary(s1, nbits) + s2bits := api.ToBinary(s2, nbits) + t1bits := api.ToBinary(t1, nbits) + t2bits := api.ToBinary(t2, nbits) + + // precompute -Q, -Φ(Q), Φ(Q) + var tableQ, tablePhiQ [2]G1Affine + tableQ[1] = Q + tableQ[0].Neg(api, Q) + cc.phi1Neg(api, &tablePhiQ[1], &Q) + tablePhiQ[0].Neg(api, tablePhiQ[1]) + // precompute -R, -Φ(R), Φ(R) + var tableR, tablePhiR [2]G1Affine + tableR[1] = R + tableR[0].Neg(api, R) + cc.phi1Neg(api, &tablePhiR[1], &R) + tablePhiR[0].Neg(api, tablePhiR[1]) + // precompute Q+R, -Q-R, Q-R, -Q+R, Φ(Q)+Φ(R), -Φ(Q)-Φ(R), Φ(Q)-Φ(R), -Φ(Q)+Φ(R) + var tableS, tablePhiS [4]G1Affine + tableS[0] = tableQ[0] + tableS[0].AddAssign(api, tableR[0]) + tableS[1].Neg(api, tableS[0]) + tableS[2] = Q + tableS[2].AddAssign(api, tableR[0]) + tableS[3].Neg(api, tableS[2]) + cc.phi1Neg(api, &tablePhiS[0], &tableS[0]) + cc.phi1Neg(api, &tablePhiS[1], &tableS[1]) + cc.phi1Neg(api, &tablePhiS[2], &tableS[2]) + cc.phi1Neg(api, &tablePhiS[3], &tableS[3]) + + // suppose first bit is 1 and set: + // Acc = Q + R + Φ(Q) + Φ(R) + Acc := tableS[1] + Acc.AddAssign(api, tablePhiS[1]) + + // Acc = [2]Acc ± Q ± R ± Φ(Q) ± Φ(R) + var B G1Affine + for i := nbits - 1; i > 0; i-- { + B.X = api.Select(api.Xor(s1bits[i], t1bits[i]), tableS[2].X, tableS[0].X) + B.Y = api.Lookup2(s1bits[i], t1bits[i], tableS[0].Y, tableS[2].Y, tableS[3].Y, tableS[1].Y) + Acc.DoubleAndAdd(api, &Acc, &B) + B.X = api.Select(api.Xor(s2bits[i], t2bits[i]), tablePhiS[2].X, tablePhiS[0].X) + B.Y = api.Lookup2(s2bits[i], t2bits[i], tablePhiS[0].Y, tablePhiS[2].Y, tablePhiS[3].Y, tablePhiS[1].Y) + Acc.AddAssign(api, B) + } + + // i = 0 + // subtract the initial point from the accumulator when first bit was 0 + tableQ[0].AddAssign(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableQ[0]) + tablePhiQ[0].AddAssign(api, Acc) + Acc.Select(api, s2bits[0], Acc, tablePhiQ[0]) + tableR[0].AddAssign(api, Acc) + Acc.Select(api, t1bits[0], Acc, tableR[0]) + tablePhiR[0].AddAssign(api, Acc) + Acc.Select(api, t2bits[0], Acc, tablePhiR[0]) + + P.X = Acc.X + P.Y = Acc.Y + + return P +} + +// scalarBitsMul computes [s]Q and returns it where sBits is the bit decomposition of s. It doesn't modify Q nor sBits. +// The method is similar to varScalarMul. +func (P *G1Affine) scalarBitsMul(api frontend.API, Q G1Affine, s1bits, s2bits []frontend.Variable, opts ...algopts.AlgebraOption) *G1Affine { + cfg, err := algopts.NewConfig(opts...) + if err != nil { + panic(err) + } + var selector frontend.Variable + if cfg.CompleteArithmetic { + // if Q=(0,0) we assign a dummy (1,1) to Q and continue + selector = api.And(api.IsZero(Q.X), api.IsZero(Q.Y)) + Q.Select(api, selector, G1Affine{X: 1, Y: 1}, Q) + } + + // We use the endomorphism à la GLV to compute [s]Q as + // [s1]Q + [s2]Φ(Q) + // + // The context we are working is based on the `outer` curve. However, the + // points and the operations on the points are performed on the `inner` + // curve of the outer curve. We require some parameters from the inner + // curve. + cc := getInnerCurveConfig(api.Compiler().Field()) + nbits := 127 + + // precompute -Q, -Φ(Q), Φ(Q) + var tableQ, tablePhiQ [2]G1Affine + tableQ[1] = Q + tableQ[0].Neg(api, Q) + cc.phi1Neg(api, &tablePhiQ[1], &Q) + tablePhiQ[0].Neg(api, tablePhiQ[1]) + + // we suppose that the first bits of the sub-scalars are 1 and set: + // Acc = Q + Φ(Q) + var Acc, B G1Affine + Acc = Q + Acc.AddAssign(api, tablePhiQ[1]) + + // At each iteration we need to compute: + // [2]Acc ± Q ± Φ(Q). + // We can compute [2]Acc and look up the (precomputed) point B from: + // B1 = +Q + Φ(Q) + B1 := Acc + // B2 = -Q - Φ(Q) + B2 := G1Affine{} + B2.Neg(api, B1) + // B3 = +Q - Φ(Q) + B3 := tableQ[1] + B3.AddAssign(api, tablePhiQ[0]) + // B4 = -Q + Φ(Q) + B4 := G1Affine{} + B4.Neg(api, B3) + // + // Note that half the points are negatives of the other half, + // hence have the same X coordinates. + + // We add G (the base point) to Acc to avoid incomplete additions in the + // loop, because when doing doubleAndAdd(Acc, Bi) as (Acc+Bi)+Acc it might + // happen that Acc==Bi or Acc==-Bi. But now we force Acc to be different + // than the stored Bi. However, at the end, we need to subtract [2^nbits]G. + mPoints := getCurvePoints() + Acc.AddAssign(api, G1Affine{X: mPoints.G1x, Y: mPoints.G1y}) + + for i := nbits - 1; i > 0; i-- { + B.X = api.Select(api.Xor(s1bits[i], s2bits[i]), B3.X, B2.X) + B.Y = api.Lookup2(s1bits[i], s2bits[i], B2.Y, B3.Y, B4.Y, B1.Y) + // Acc = [2]Acc + B + Acc.DoubleAndAdd(api, &Acc, &B) + } + + // i = 0 + // subtract the Q, R, Φ(Q), Φ(R) 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). + if cfg.CompleteArithmetic { + tableQ[0].AddUnified(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableQ[0]) + tablePhiQ[0].AddUnified(api, Acc) + Acc.Select(api, s2bits[0], Acc, tablePhiQ[0]) + Acc.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) + } else { + tableQ[0].AddAssign(api, Acc) + Acc.Select(api, s1bits[0], Acc, tableQ[0]) + tablePhiQ[0].AddAssign(api, Acc) + Acc.Select(api, s2bits[0], Acc, tablePhiQ[0]) + } + + // subtract H=[2^N]G since we added G at the beginning + negH := G1Affine{X: mPoints.G1m[nbits-1][0], Y: api.Neg(mPoints.G1m[nbits-1][1])} + Acc.AddUnified(api, negH) + + if cfg.CompleteArithmetic { + P.Select(api, selector, G1Affine{X: 0, Y: 0}, Acc) + } else { + *P = Acc + } + + return P +} diff --git a/std/algebra/native/sw_grumpkin/g1_test.go b/std/algebra/native/sw_grumpkin/g1_test.go new file mode 100644 index 0000000000..af1dce5515 --- /dev/null +++ b/std/algebra/native/sw_grumpkin/g1_test.go @@ -0,0 +1,841 @@ +// Copyright 2020-2025 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +package sw_grumpkin + +import ( + "math/big" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/grumpkin" + "github.com/consensys/gnark-crypto/ecc/grumpkin/fr" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/algopts" + "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/math/emulated/emparams" + "github.com/consensys/gnark/test" +) + +// ------------------------------------------------------------------------------------------------- +// Add affine + +type g1AddAssignAffine struct { + A, B G1Affine + C G1Affine `gnark:",public"` +} + +func (circuit *g1AddAssignAffine) Define(api frontend.API) error { + expected := circuit.A + expected.AddAssign(api, circuit.B) + expected.AssertIsEqual(api, circuit.C) + return nil +} + +func TestAddAssignAffineG1(t *testing.T) { + + // sample 2 random points + _a := randomPointG1() + _b := randomPointG1() + var a, b, c grumpkin.G1Affine + a.FromJacobian(&_a) + b.FromJacobian(&_b) + + // create the cs + var circuit, witness g1AddAssignAffine + + // assign the inputs + witness.A.Assign(&a) + witness.B.Assign(&b) + + // compute the result + _a.AddAssign(&_b) + c.FromJacobian(&_a) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BN254)) + +} + +// ------------------------------------------------------------------------------------------------- +// Double affine + +type g1DoubleAffine struct { + A G1Affine + C G1Affine `gnark:",public"` +} + +func (circuit *g1DoubleAffine) Define(api frontend.API) error { + expected := circuit.A + expected.Double(api, circuit.A) + expected.AssertIsEqual(api, circuit.C) + return nil +} + +func TestDoubleAffineG1(t *testing.T) { + + // sample 2 random points + _a, a := grumpkin.Generators() + var c grumpkin.G1Affine + + // create the cs + var circuit, witness g1DoubleAffine + + // assign the inputs and compute the result + witness.A.Assign(&a) + _a.DoubleAssign() + c.FromJacobian(&_a) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BN254)) + +} + +// ------------------------------------------------------------------------------------------------- +// DoubleAndAdd affine + +type g1DoubleAndAddAffine struct { + A, B G1Affine + C G1Affine `gnark:",public"` +} + +func (circuit *g1DoubleAndAddAffine) Define(api frontend.API) error { + expected := circuit.A + expected.DoubleAndAdd(api, &circuit.A, &circuit.B) + expected.AssertIsEqual(api, circuit.C) + return nil +} + +func TestDoubleAndAddAffineG1(t *testing.T) { + + // sample 2 random points + _a := randomPointG1() + _b := randomPointG1() + var a, b, c grumpkin.G1Affine + a.FromJacobian(&_a) + b.FromJacobian(&_b) + + // create the cs + var circuit, witness g1DoubleAndAddAffine + + // assign the inputs + witness.A.Assign(&a) + witness.B.Assign(&b) + + // compute the result + _a.Double(&_a).AddAssign(&_b) + c.FromJacobian(&_a) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BN254)) + +} + +// ------------------------------------------------------------------------------------------------- +// Scalar multiplication + +type g1constantScalarMul struct { + A G1Affine + C G1Affine `gnark:",public"` + R *big.Int +} + +func (circuit *g1constantScalarMul) Define(api frontend.API) error { + expected := G1Affine{} + expected.constScalarMul(api, circuit.A, circuit.R) + expected.AssertIsEqual(api, circuit.C) + return nil +} + +func TestConstantScalarMulG1(t *testing.T) { + // sample random point + _a := randomPointG1() + var a, c grumpkin.G1Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness g1constantScalarMul + var r fr.Element + _, _ = r.SetRandom() + // assign the inputs + witness.A.Assign(&a) + // compute the result + br := new(big.Int) + r.BigInt(br) + // br is a circuit parameter + circuit.R = br + _a.ScalarMultiplication(&_a, br) + c.FromJacobian(&_a) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BN254)) + +} + +type g1constantScalarMulEdgeCases struct { + A, Inf G1Affine + R *big.Int +} + +func (circuit *g1constantScalarMulEdgeCases) Define(api frontend.API) error { + expected1 := G1Affine{} + expected2 := G1Affine{} + expected1.constScalarMul(api, circuit.A, big.NewInt(0)) + expected2.constScalarMul(api, circuit.Inf, circuit.R, algopts.WithCompleteArithmetic()) + expected1.AssertIsEqual(api, circuit.Inf) + expected2.AssertIsEqual(api, circuit.Inf) + return nil +} + +func TestConstantScalarMulG1EdgeCases(t *testing.T) { + // sample random point + _a := randomPointG1() + var a grumpkin.G1Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness g1constantScalarMulEdgeCases + var r fr.Element + _, _ = r.SetRandom() + // assign the inputs + witness.A.Assign(&a) + // compute the result + br := new(big.Int) + r.BigInt(br) + // br is a circuit parameter + circuit.R = br + + witness.Inf.X = 0 + witness.Inf.Y = 0 + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BN254)) + +} + +type g1ScalarMulEdgeCases struct { + A, Inf G1Affine + R, Zero frontend.Variable +} + +func (circuit *g1ScalarMulEdgeCases) Define(api frontend.API) error { + expected1 := G1Affine{} + expected2 := G1Affine{} + expected2.ScalarMul(api, circuit.Inf, circuit.R, algopts.WithCompleteArithmetic()) + expected1.ScalarMul(api, circuit.A, circuit.Zero, algopts.WithCompleteArithmetic()) + expected1.AssertIsEqual(api, circuit.Inf) + expected2.AssertIsEqual(api, circuit.Inf) + return nil +} + +func TestEndoScalarMulG1EdgeCases(t *testing.T) { + // sample random point + _a := randomPointG1() + var a grumpkin.G1Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness g1ScalarMulEdgeCases + var r fr.Element + _, _ = r.SetRandom() + witness.R = r.String() + // assign the inputs + witness.A.Assign(&a) + witness.Inf.X = 0 + witness.Inf.Y = 0 + witness.Zero = 0 + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BN254)) +} + +type g1ScalarMul struct { + A G1Affine + C G1Affine `gnark:",public"` + Rvar frontend.Variable + Rcon fr.Element +} + +func (circuit *g1ScalarMul) Define(api frontend.API) error { + var expected, expected2 G1Affine + expected.ScalarMul(api, circuit.A, circuit.Rvar) + expected.AssertIsEqual(api, circuit.C) + expected2.ScalarMul(api, circuit.A, circuit.Rcon) + expected2.AssertIsEqual(api, circuit.C) + return nil +} + +func TestScalarMulG1(t *testing.T) { + // sample random point + _a := randomPointG1() + var a, c grumpkin.G1Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness g1ScalarMul + var r fr.Element + _, _ = r.SetRandom() + witness.Rvar = r.String() + circuit.Rcon = r + // assign the inputs + witness.A.Assign(&a) + // compute the result + var br big.Int + _a.ScalarMultiplication(&_a, r.BigInt(&br)) + c.FromJacobian(&_a) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BN254)) +} + +type g1ScalarMulBase struct { + C G1Affine `gnark:",public"` + R frontend.Variable +} + +func (circuit *g1ScalarMulBase) Define(api frontend.API) error { + expected := G1Affine{} + expected.ScalarMulBase(api, circuit.R) + expected.AssertIsEqual(api, circuit.C) + return nil +} + +func TestEndoScalarMulBaseG1(t *testing.T) { + var c grumpkin.G1Affine + gJac, _ := grumpkin.Generators() + + // create the cs + var circuit, witness g1ScalarMulBase + var r fr.Element + _, _ = r.SetRandom() + witness.R = r.String() + // compute the result + var br big.Int + gJac.ScalarMultiplication(&gJac, r.BigInt(&br)) + c.FromJacobian(&gJac) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BN254)) +} + +type MultiScalarMulEdgeCasesTest struct { + Points []G1Affine + Scalars []emulated.Element[ScalarField] + Res G1Affine +} + +func (c *MultiScalarMulEdgeCasesTest) Define(api frontend.API) error { + cr, err := NewCurve(api) + if err != nil { + return err + } + ps := make([]*G1Affine, len(c.Points)) + for i := range c.Points { + ps[i] = &c.Points[i] + } + ss := make([]*emulated.Element[ScalarField], len(c.Scalars)) + for i := range c.Scalars { + ss[i] = &c.Scalars[i] + } + res, err := cr.MultiScalarMul(ps, ss, algopts.WithCompleteArithmetic()) + if err != nil { + return err + } + cr.AssertIsEqual(res, &c.Res) + return nil +} + +func TestMultiScalarMulEdgeCases(t *testing.T) { + assert := test.NewAssert(t) + nbLen := 5 + P := make([]grumpkin.G1Affine, nbLen) + S := make([]fr.Element, nbLen) + for i := 0; i < nbLen; i++ { + S[i].SetRandom() + P[i].ScalarMultiplicationBase(S[i].BigInt(new(big.Int))) + } + var res, infinity grumpkin.G1Affine + _, err := res.MultiExp(P, S, ecc.MultiExpConfig{}) + + assert.NoError(err) + cP := make([]G1Affine, len(P)) + cS := make([]emulated.Element[ScalarField], len(S)) + + // s1 * (0,0) + s2 * (0,0) + s3 * (0,0) + s4 * (0,0) + s5 * (0,0) == (0,0) + for i := range cP { + cP[i] = NewG1Affine(infinity) + } + for i := range cS { + cS[i] = NewScalar(S[i]) + } + assignment1 := MultiScalarMulEdgeCasesTest{ + Points: cP, + Scalars: cS, + Res: NewG1Affine(infinity), + } + err = test.IsSolved(&MultiScalarMulEdgeCasesTest{ + Points: make([]G1Affine, nbLen), + Scalars: make([]emulated.Element[ScalarField], nbLen), + }, &assignment1, ecc.BN254.ScalarField()) + assert.NoError(err) + + // 0 * P1 + 0 * P2 + 0 * P3 + 0 * P4 + 0 * P5 == (0,0) + for i := range cP { + cP[i] = NewG1Affine(P[i]) + } + for i := range cS { + cS[i] = emulated.ValueOf[emparams.GRUMPKINFr](0) + } + assignment2 := MultiScalarMulEdgeCasesTest{ + Points: cP, + Scalars: cS, + Res: NewG1Affine(infinity), + } + err = test.IsSolved(&MultiScalarMulEdgeCasesTest{ + Points: make([]G1Affine, nbLen), + Scalars: make([]emulated.Element[ScalarField], nbLen), + }, &assignment2, ecc.BN254.ScalarField()) + assert.NoError(err) + + // s1 * (0,0) + s2 * P2 + s3 * (0,0) + s4 * P4 + 0 * P5 == s2 * P + s4 * P4 + var res3 grumpkin.G1Affine + res3.ScalarMultiplication(&P[1], S[1].BigInt(new(big.Int))) + res.ScalarMultiplication(&P[3], S[3].BigInt(new(big.Int))) + res3.Add(&res3, &res) + for i := range cP { + cP[i] = NewG1Affine(P[i]) + } + cP[0].X = infinity.X + cP[0].Y = infinity.Y + cP[2].X = infinity.X + cP[2].Y = infinity.Y + for i := range cS { + cS[i] = NewScalar(S[i]) + } + cS[4] = emulated.ValueOf[emparams.GRUMPKINFr](0) + + assignment3 := MultiScalarMulEdgeCasesTest{ + Points: cP, + Scalars: cS, + Res: NewG1Affine(res3), + } + err = test.IsSolved(&MultiScalarMulEdgeCasesTest{ + Points: make([]G1Affine, nbLen), + Scalars: make([]emulated.Element[ScalarField], nbLen), + }, &assignment3, ecc.BN254.ScalarField()) + assert.NoError(err) +} + +type MultiScalarMulTest struct { + Points []G1Affine + Scalars []emulated.Element[ScalarField] + Res G1Affine +} + +func (c *MultiScalarMulTest) Define(api frontend.API) error { + cr, err := NewCurve(api) + if err != nil { + return err + } + ps := make([]*G1Affine, len(c.Points)) + for i := range c.Points { + ps[i] = &c.Points[i] + } + ss := make([]*emulated.Element[ScalarField], len(c.Scalars)) + for i := range c.Scalars { + ss[i] = &c.Scalars[i] + } + res, err := cr.MultiScalarMul(ps, ss) + if err != nil { + return err + } + cr.AssertIsEqual(res, &c.Res) + return nil +} + +func TestMultiScalarMul(t *testing.T) { + assert := test.NewAssert(t) + nbLen := 4 + P := make([]grumpkin.G1Affine, nbLen) + S := make([]fr.Element, nbLen) + for i := 0; i < nbLen; i++ { + S[i].SetRandom() + P[i].ScalarMultiplicationBase(S[i].BigInt(new(big.Int))) + } + var res grumpkin.G1Affine + _, err := res.MultiExp(P, S, ecc.MultiExpConfig{}) + + assert.NoError(err) + cP := make([]G1Affine, len(P)) + for i := range cP { + cP[i] = NewG1Affine(P[i]) + } + cS := make([]emulated.Element[ScalarField], len(S)) + for i := range cS { + cS[i] = NewScalar(S[i]) + } + assignment := MultiScalarMulTest{ + Points: cP, + Scalars: cS, + Res: NewG1Affine(res), + } + err = test.IsSolved(&MultiScalarMulTest{ + Points: make([]G1Affine, nbLen), + Scalars: make([]emulated.Element[ScalarField], nbLen), + }, &assignment, ecc.BN254.ScalarField()) + assert.NoError(err) +} + +type g1JointScalarMulEdgeCases struct { + A, B, Inf G1Affine + C G1Affine `gnark:",public"` + R, S, Zero frontend.Variable +} + +func (circuit *g1JointScalarMulEdgeCases) Define(api frontend.API) error { + expected1 := G1Affine{} + expected2 := G1Affine{} + expected3 := G1Affine{} + expected4 := G1Affine{} + expected1.jointScalarMul(api, circuit.Inf, circuit.Inf, circuit.R, circuit.S, algopts.WithCompleteArithmetic()) + expected2.jointScalarMul(api, circuit.A, circuit.B, circuit.Zero, circuit.Zero, algopts.WithCompleteArithmetic()) + expected3.jointScalarMul(api, circuit.A, circuit.Inf, circuit.R, circuit.S, algopts.WithCompleteArithmetic()) + expected4.jointScalarMul(api, circuit.A, circuit.B, circuit.R, circuit.Zero, algopts.WithCompleteArithmetic()) + _expected := G1Affine{} + _expected.ScalarMul(api, circuit.A, circuit.R, algopts.WithCompleteArithmetic()) + expected1.AssertIsEqual(api, circuit.Inf) + expected2.AssertIsEqual(api, circuit.Inf) + expected3.AssertIsEqual(api, _expected) + expected4.AssertIsEqual(api, _expected) + return nil +} + +func TestJointScalarMulG1EdgeCases(t *testing.T) { + // sample random point + _a := randomPointG1() + _b := randomPointG1() + var a, b, c grumpkin.G1Affine + a.FromJacobian(&_a) + b.FromJacobian(&_b) + + // create the cs + var circuit, witness g1JointScalarMulEdgeCases + var r, s fr.Element + _, _ = r.SetRandom() + _, _ = s.SetRandom() + witness.R = r.String() + witness.S = s.String() + // assign the inputs + witness.A.Assign(&a) + witness.B.Assign(&b) + // compute the result + var br, bs big.Int + _a.ScalarMultiplication(&_a, r.BigInt(&br)) + _b.ScalarMultiplication(&_b, s.BigInt(&bs)) + _a.AddAssign(&_b) + c.FromJacobian(&_a) + witness.C.Assign(&c) + + witness.Inf.X = 0 + witness.Inf.Y = 0 + witness.Zero = 0 + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BN254)) +} + +type g1JointScalarMul struct { + A, B G1Affine + C G1Affine `gnark:",public"` + R, S frontend.Variable +} + +func (circuit *g1JointScalarMul) Define(api frontend.API) error { + expected := G1Affine{} + expected.jointScalarMul(api, circuit.A, circuit.B, circuit.R, circuit.S) + expected.AssertIsEqual(api, circuit.C) + return nil +} + +func TestJointScalarMulG1(t *testing.T) { + // sample random point + _a := randomPointG1() + _b := randomPointG1() + var a, b, c grumpkin.G1Affine + a.FromJacobian(&_a) + b.FromJacobian(&_b) + + // create the cs + var circuit, witness g1JointScalarMul + var r, s fr.Element + _, _ = r.SetRandom() + _, _ = s.SetRandom() + witness.R = r.String() + witness.S = s.String() + // assign the inputs + witness.A.Assign(&a) + witness.B.Assign(&b) + // compute the result + var br, bs big.Int + _a.ScalarMultiplication(&_a, r.BigInt(&br)) + _b.ScalarMultiplication(&_b, s.BigInt(&bs)) + _a.AddAssign(&_b) + c.FromJacobian(&_a) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BN254)) +} + +type g1JointScalarMulNaive struct { + A, B G1Affine + C G1Affine `gnark:",public"` + R, S frontend.Variable +} + +func (circuit *g1JointScalarMulNaive) Define(api frontend.API) error { + expected := G1Affine{} + tmp := G1Affine{} + tmp.ScalarMul(api, circuit.A, circuit.R) + expected.ScalarMul(api, circuit.B, circuit.S) + expected.AddAssign(api, tmp) + expected.AssertIsEqual(api, circuit.C) + return nil +} + +func TestJointScalarMulG1Naive(t *testing.T) { + // sample random point + _a := randomPointG1() + _b := randomPointG1() + var a, b, c grumpkin.G1Affine + a.FromJacobian(&_a) + b.FromJacobian(&_b) + + // create the cs + var circuit, witness g1JointScalarMulNaive + var r, s fr.Element + _, _ = r.SetRandom() + _, _ = s.SetRandom() + witness.R = r.String() + witness.S = s.String() + // assign the inputs + witness.A.Assign(&a) + witness.B.Assign(&b) + // compute the result + var br, bs big.Int + _a.ScalarMultiplication(&_a, r.BigInt(&br)) + _b.ScalarMultiplication(&_b, s.BigInt(&bs)) + _a.AddAssign(&_b) + c.FromJacobian(&_a) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BN254)) +} + +type MultiScalarMulFoldedEdgeCasesTest struct { + Points []G1Affine + Scalars []emulated.Element[ScalarField] + Res G1Affine +} + +func (c *MultiScalarMulFoldedEdgeCasesTest) Define(api frontend.API) error { + cr, err := NewCurve(api) + if err != nil { + return err + } + ps := make([]*G1Affine, len(c.Points)) + for i := range c.Points { + ps[i] = &c.Points[i] + } + ss := make([]*emulated.Element[ScalarField], len(c.Scalars)) + for i := range c.Scalars { + ss[i] = &c.Scalars[i] + } + res, err := cr.MultiScalarMul(ps, ss, algopts.WithFoldingScalarMul(), algopts.WithCompleteArithmetic()) + if err != nil { + return err + } + cr.AssertIsEqual(res, &c.Res) + return nil +} + +func TestMultiScalarMulFoldedEdgeCases(t *testing.T) { + assert := test.NewAssert(t) + nbLen := 5 + P := make([]grumpkin.G1Affine, nbLen) + S := make([]fr.Element, nbLen) + S[0].SetOne() + S[1].SetRandom() + S[2].Square(&S[1]) + S[3].Mul(&S[1], &S[2]) + S[4].Mul(&S[1], &S[3]) + for i := 0; i < nbLen; i++ { + P[i].ScalarMultiplicationBase(S[i].BigInt(new(big.Int))) + } + var res, infinity grumpkin.G1Affine + _, err := res.MultiExp(P, S, ecc.MultiExpConfig{}) + + assert.NoError(err) + cP := make([]G1Affine, len(P)) + cS := make([]emulated.Element[ScalarField], len(S)) + + // s^0 * (0,0) + s^1 * (0,0) + s^2 * (0,0) + s^3 * (0,0) + s^4 * (0,0) == (0,0) + for i := range cP { + cP[i] = NewG1Affine(infinity) + } + // s0 = s + S[0].Set(&S[1]) + for i := range cS { + cS[i] = NewScalar(S[i]) + } + assignment1 := MultiScalarMulFoldedEdgeCasesTest{ + Points: cP, + Scalars: cS, + Res: NewG1Affine(infinity), + } + err = test.IsSolved(&MultiScalarMulFoldedEdgeCasesTest{ + Points: make([]G1Affine, nbLen), + Scalars: make([]emulated.Element[ScalarField], nbLen), + }, &assignment1, ecc.BN254.ScalarField()) + assert.NoError(err) + + // 0^0 * P1 + 0 * P2 + 0 * P3 + 0 * P4 + 0 * P5 == P1 + for i := range cP { + cP[i] = NewG1Affine(P[i]) + } + for i := range cS { + cS[i] = emulated.ValueOf[emparams.GRUMPKINFr](0) + } + + assignment3 := MultiScalarMulFoldedEdgeCasesTest{ + Points: cP, + Scalars: cS, + Res: NewG1Affine(P[0]), + } + err = test.IsSolved(&MultiScalarMulFoldedEdgeCasesTest{ + Points: make([]G1Affine, nbLen), + Scalars: make([]emulated.Element[ScalarField], nbLen), + }, &assignment3, ecc.BN254.ScalarField()) + assert.NoError(err) +} + +type MultiScalarMulFoldedTest struct { + Points []G1Affine + Scalars []emulated.Element[ScalarField] + Res G1Affine +} + +func (c *MultiScalarMulFoldedTest) Define(api frontend.API) error { + cr, err := NewCurve(api) + if err != nil { + return err + } + ps := make([]*G1Affine, len(c.Points)) + for i := range c.Points { + ps[i] = &c.Points[i] + } + ss := make([]*emulated.Element[ScalarField], len(c.Scalars)) + for i := range c.Scalars { + ss[i] = &c.Scalars[i] + } + res, err := cr.MultiScalarMul(ps, ss, algopts.WithFoldingScalarMul()) + if err != nil { + return err + } + cr.AssertIsEqual(res, &c.Res) + return nil +} + +func TestMultiScalarMulFolded(t *testing.T) { + assert := test.NewAssert(t) + nbLen := 4 + P := make([]grumpkin.G1Affine, nbLen) + S := make([]fr.Element, nbLen) + // [s^0]P0 + [s^1]P1 + [s^2]P2 + [s^3]P3 = P0 + [s]P1 + [s^2]P2 + [s^3]P3 + S[0].SetOne() + S[1].SetRandom() + S[2].Square(&S[1]) + S[3].Mul(&S[1], &S[2]) + for i := 0; i < nbLen; i++ { + P[i].ScalarMultiplicationBase(S[i].BigInt(new(big.Int))) + } + var res grumpkin.G1Affine + _, err := res.MultiExp(P, S, ecc.MultiExpConfig{}) + + assert.NoError(err) + cP := make([]G1Affine, len(P)) + for i := range cP { + cP[i] = NewG1Affine(P[i]) + } + cS := make([]emulated.Element[ScalarField], len(S)) + // s0 = s + S[0].Set(&S[1]) + for i := range cS { + cS[i] = NewScalar(S[i]) + } + assignment := MultiScalarMulFoldedTest{ + Points: cP, + Scalars: cS, + Res: NewG1Affine(res), + } + err = test.IsSolved(&MultiScalarMulFoldedTest{ + Points: make([]G1Affine, nbLen), + Scalars: make([]emulated.Element[ScalarField], nbLen), + }, &assignment, ecc.BN254.ScalarField()) + assert.NoError(err) +} + +type g1varScalarMul struct { + A G1Affine + C G1Affine `gnark:",public"` + R frontend.Variable +} + +func (circuit *g1varScalarMul) Define(api frontend.API) error { + expected := G1Affine{} + expected.varScalarMul(api, circuit.A, circuit.R) + expected.AssertIsEqual(api, circuit.C) + return nil +} + +func TestEndoScalarMulG1(t *testing.T) { + // sample random point + _a := randomPointG1() + var a, c grumpkin.G1Affine + a.FromJacobian(&_a) + + // create the cs + var circuit, witness g1varScalarMul + var r fr.Element + _, _ = r.SetRandom() + witness.R = r.String() + // assign the inputs + witness.A.Assign(&a) + // compute the result + var br big.Int + _a.ScalarMultiplication(&_a, r.BigInt(&br)) + c.FromJacobian(&_a) + witness.C.Assign(&c) + + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BN254)) +} + +func randomPointG1() grumpkin.G1Jac { + + p1, _ := grumpkin.Generators() + + var r1 fr.Element + var b big.Int + _, _ = r1.SetRandom() + p1.ScalarMultiplication(&p1, r1.BigInt(&b)) + + return p1 +} diff --git a/std/algebra/native/sw_grumpkin/hints.go b/std/algebra/native/sw_grumpkin/hints.go new file mode 100644 index 0000000000..04db014c88 --- /dev/null +++ b/std/algebra/native/sw_grumpkin/hints.go @@ -0,0 +1,95 @@ +package sw_grumpkin + +import ( + "errors" + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/constraint/solver" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/math/emulated/emparams" +) + +func GetHints() []solver.Hint { + return []solver.Hint{ + decomposeScalar, + decompose, + } +} + +func init() { + solver.RegisterHint(GetHints()...) +} + +func decomposeScalar(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { + return emulated.UnwrapHintWithNativeInput(nativeInputs, nativeOutputs, func(nnMod *big.Int, nninputs, nnOutputs []*big.Int) error { + if len(nninputs) != 1 { + return errors.New("expecting one input") + } + if len(nnOutputs) != 2 { + return errors.New("expecting two outputs") + } + cc := getInnerCurveConfig(nativeMod) + sp := ecc.SplitScalar(nninputs[0], cc.glvBasis) + nnOutputs[0].Set(&(sp[0])) + nnOutputs[1].Neg(&(sp[1])) + + return nil + }) +} + +func callDecomposeScalar(api frontend.API, s frontend.Variable, simple bool) (s1, s2 frontend.Variable) { + var fr emparams.GRUMPKINFr + cc := getInnerCurveConfig(api.Compiler().Field()) + sapi, err := emulated.NewField[emparams.GRUMPKINFr](api) + if err != nil { + panic(err) + } + // compute the decomposition using a hint. We have to use the emulated + // version which takes native input and outputs non-native outputs. + // + // the hints allow to decompose the scalar s into s1 and s2 such that + // s1 + λ * s2 == s mod r, + // where λ is third root of one in 𝔽_r. + sd, err := sapi.NewHintWithNativeInput(decomposeScalar, 2, s) + if err != nil { + panic(err) + } + // lambda as nonnative element + lambdaEmu := sapi.NewElement(cc.lambda) + // the scalar as nonnative element. We need to split at 64 bits. + limbs, err := api.NewHint(decompose, int(fr.NbLimbs()), s) + if err != nil { + panic(err) + } + semu := sapi.NewElement(limbs) + // s1 + λ * s2 == s mod r + lhs := sapi.MulNoReduce(sd[1], lambdaEmu) + lhs = sapi.Sub(sd[0], lhs) + + sapi.AssertIsEqual(lhs, semu) + + s1 = 0 + s2 = 0 + b := big.NewInt(1) + for i := range sd[0].Limbs { + s1 = api.Add(s1, api.Mul(sd[0].Limbs[i], b)) + s2 = api.Add(s2, api.Mul(sd[1].Limbs[i], b)) + b.Lsh(b, 64) + } + return s1, s2 +} + +func decompose(mod *big.Int, inputs, outputs []*big.Int) error { + if len(inputs) != 1 && len(outputs) != 4 { + return errors.New("input/output length mismatch") + } + tmp := new(big.Int).Set(inputs[0]) + mask := new(big.Int).SetUint64(^uint64(0)) + for i := 0; i < 4; i++ { + outputs[i].And(tmp, mask) + tmp.Rsh(tmp, 64) + } + return nil +} diff --git a/std/algebra/native/sw_grumpkin/inner.go b/std/algebra/native/sw_grumpkin/inner.go new file mode 100644 index 0000000000..dc6e2d839e --- /dev/null +++ b/std/algebra/native/sw_grumpkin/inner.go @@ -0,0 +1,79 @@ +package sw_grumpkin + +import ( + "fmt" + "math/big" + "sync" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/grumpkin" + "github.com/consensys/gnark/frontend" +) + +var mappingOnce sync.Once + +type innerConfig struct { + thirdRootOne1 *big.Int + thirdRootOne2 *big.Int + glvBasis *ecc.Lattice + lambda *big.Int + fr *big.Int + fp *big.Int +} + +var innerConfigBN254 innerConfig + +func (cc *innerConfig) phi1Neg(api frontend.API, res, P *G1Affine) *G1Affine { + res.X = api.Mul(P.X, cc.thirdRootOne1) + res.Y = api.Neg(P.Y) + return res +} + +// getInnerCurveConfig returns the configuration of the inner elliptic curve +// which can be defined on the scalars of outer curve. +func getInnerCurveConfig(outerCurveScalarField *big.Int) *innerConfig { + if outerCurveScalarField.Cmp(ecc.BN254.ScalarField()) != 0 { + panic(fmt.Sprintf("outer curve %s does not have a inner curve", outerCurveScalarField.String())) + } + + mappingOnce.Do(func() { + grumpkinlambda := new(big.Int).SetBytes([]byte{0x59, 0xe2, 0x6b, 0xce, 0xa0, 0xd4, 0x8b, 0xac, 0xd4, 0xf2, 0x63, 0xf1, 0xac, 0xdb, 0x5c, 0x4f, 0x57, 0x63, 0x47, 0x31, 0x77, 0xff, 0xff, 0xfe}) + grumpkinthirdRootOne1 := new(big.Int).SetBytes([]byte{0xb3, 0xc4, 0xd7, 0x9d, 0x41, 0xa9, 0x17, 0x58, 0x5b, 0xfc, 0x41, 0x8, 0x8d, 0x8d, 0xaa, 0xa7, 0x8b, 0x17, 0xea, 0x66, 0xb9, 0x9c, 0x90, 0xdd}) + grumpkinthirdRootOne2 := new(big.Int).Mul(grumpkinthirdRootOne1, grumpkinthirdRootOne1) + grumpkinglvBasis := new(ecc.Lattice) + ecc.PrecomputeLattice(ecc.GRUMPKIN.ScalarField(), grumpkinlambda, grumpkinglvBasis) + innerConfigBN254 = innerConfig{ + thirdRootOne1: grumpkinthirdRootOne1, + thirdRootOne2: grumpkinthirdRootOne2, + glvBasis: grumpkinglvBasis, + lambda: grumpkinlambda, + fp: ecc.GRUMPKIN.BaseField(), + fr: ecc.GRUMPKIN.ScalarField(), + } + }) + + return &innerConfigBN254 +} + +var ( + computedCurveTable [][2]*big.Int +) + +func init() { + computedCurveTable = computeCurveTable() +} + +type curvePoints struct { + G1x *big.Int // base point x + G1y *big.Int // base point y + G1m [][2]*big.Int // m*base points (x,y) +} + +func getCurvePoints() curvePoints { + g1aff, _ := grumpkin.Generators() + return curvePoints{ + G1x: g1aff.X.BigInt(new(big.Int)), + G1y: g1aff.Y.BigInt(new(big.Int)), + G1m: computedCurveTable, + } +} diff --git a/std/algebra/native/sw_grumpkin/inner_compute.go b/std/algebra/native/sw_grumpkin/inner_compute.go new file mode 100644 index 0000000000..07e54e158b --- /dev/null +++ b/std/algebra/native/sw_grumpkin/inner_compute.go @@ -0,0 +1,33 @@ +package sw_grumpkin + +import ( + "math/big" + + "github.com/consensys/gnark-crypto/ecc/grumpkin" +) + +func computeCurveTable() [][2]*big.Int { + G1jac, _ := grumpkin.Generators() + table := make([][2]*big.Int, 254) + tmp := new(grumpkin.G1Jac).Set(&G1jac) + aff := new(grumpkin.G1Affine) + jac := new(grumpkin.G1Jac) + for i := 1; i < 254; i++ { + tmp = tmp.Double(tmp) + switch i { + case 1, 2: + jac.Set(tmp).AddAssign(&G1jac) + aff.FromJacobian(jac) + table[i-1] = [2]*big.Int{aff.X.BigInt(new(big.Int)), aff.Y.BigInt(new(big.Int))} + case 3: + jac.Set(tmp).SubAssign(&G1jac) + aff.FromJacobian(jac) + table[i-1] = [2]*big.Int{aff.X.BigInt(new(big.Int)), aff.Y.BigInt(new(big.Int))} + fallthrough + default: + aff.FromJacobian(tmp) + table[i] = [2]*big.Int{aff.X.BigInt(new(big.Int)), aff.Y.BigInt(new(big.Int))} + } + } + return table[:] +} diff --git a/std/algebra/native/sw_grumpkin/wrapper.go b/std/algebra/native/sw_grumpkin/wrapper.go new file mode 100644 index 0000000000..778fb8d986 --- /dev/null +++ b/std/algebra/native/sw_grumpkin/wrapper.go @@ -0,0 +1,234 @@ +package sw_grumpkin + +import ( + "errors" + "fmt" + "math/big" + + fr_bn254 "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/consensys/gnark-crypto/ecc/grumpkin" + fr_grumpkin "github.com/consensys/gnark-crypto/ecc/grumpkin/fr" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra/algopts" + "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/math/emulated/emparams" + "github.com/consensys/gnark/std/selector" +) + +// Curve allows G1 operations in Grumpkin. +type Curve struct { + api frontend.API + fr *emulated.Field[ScalarField] +} + +// NewCurve initializes a new [Curve] instance. +func NewCurve(api frontend.API) (*Curve, error) { + f, err := emulated.NewField[ScalarField](api) + if err != nil { + return nil, errors.New("scalar field") + } + return &Curve{ + api: api, + fr: f, + }, nil +} + +// Add points P and Q and return the result. Does not modify the inputs. +func (c *Curve) Add(P, Q *G1Affine) *G1Affine { + res := &G1Affine{ + X: P.X, + Y: P.Y, + } + res.AddAssign(c.api, *Q) + return res +} + +// AddUnified adds any two points and returns the sum. It does not modify the input +// points. +func (c *Curve) AddUnified(P, Q *G1Affine) *G1Affine { + res := &G1Affine{ + X: P.X, + Y: P.Y, + } + res.AddUnified(c.api, *Q) + return res +} + +// AssertIsEqual asserts the equality of P and Q. +func (c *Curve) AssertIsEqual(P, Q *G1Affine) { + P.AssertIsEqual(c.api, *Q) +} + +// Neg negates P and returns the result. Does not modify P. +func (c *Curve) Neg(P *G1Affine) *G1Affine { + res := &G1Affine{ + X: P.X, + Y: P.Y, + } + res.Neg(c.api, *P) + return res +} + +// jointScalarMul computes s1*P+s2*P2 and returns the result. It doesn't modify the +// inputs. +func (c *Curve) jointScalarMul(P1, P2 *G1Affine, s1, s2 *Scalar, opts ...algopts.AlgebraOption) *G1Affine { + res := &G1Affine{} + varScalar1 := c.packScalarToVar(s1) + varScalar2 := c.packScalarToVar(s2) + res.jointScalarMul(c.api, *P1, *P2, varScalar1, varScalar2, opts...) + return res +} + +// ScalarMul computes scalar*P and returns the result. It doesn't modify the +// inputs. +func (c *Curve) ScalarMul(P *G1Affine, s *Scalar, opts ...algopts.AlgebraOption) *G1Affine { + res := &G1Affine{ + X: P.X, + Y: P.Y, + } + varScalar := c.packScalarToVar(s) + res.ScalarMul(c.api, *P, varScalar, opts...) + return res +} + +// ScalarMulBase computes scalar*G where G is the standard base point of the +// curve. It doesn't modify the scalar. +func (c *Curve) ScalarMulBase(s *Scalar, opts ...algopts.AlgebraOption) *G1Affine { + res := new(G1Affine) + varScalar := c.packScalarToVar(s) + res.ScalarMulBase(c.api, varScalar, opts...) + return res +} + +// MultiScalarMul computes ∑scalars_i * P_i and returns it. It doesn't modify +// the inputs. It returns an error if there is a mismatch in the lengths of the +// inputs. +func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar, opts ...algopts.AlgebraOption) (*G1Affine, error) { + if len(P) == 0 { + return &G1Affine{ + X: 0, + Y: 0, + }, nil + } + cfg, err := algopts.NewConfig(opts...) + if err != nil { + return nil, fmt.Errorf("new config: %w", err) + } + addFn := c.Add + if cfg.CompleteArithmetic { + addFn = c.AddUnified + } + if !cfg.FoldMulti { + if len(P) != len(scalars) { + return nil, errors.New("mismatching points and scalars slice lengths") + } + // points and scalars must be non-zero + n := len(P) + var res *G1Affine + if n%2 == 1 { + res = c.ScalarMul(P[n-1], scalars[n-1], opts...) + } else { + res = c.jointScalarMul(P[n-2], P[n-1], scalars[n-2], scalars[n-1], opts...) + } + for i := 1; i < n-1; i += 2 { + q := c.jointScalarMul(P[i-1], P[i], scalars[i-1], scalars[i], opts...) + res = addFn(res, q) + } + return res, nil + } else { + // scalars are powers + if len(scalars) == 0 { + return nil, errors.New("need scalar for folding") + } + gamma := c.packScalarToVar(scalars[0]) + // decompose gamma in the endomorphism eigenvalue basis and bit-decompose the sub-scalars + gamma1, gamma2 := callDecomposeScalar(c.api, gamma, true) + nbits := 127 + gamma1Bits := c.api.ToBinary(gamma1, nbits) + gamma2Bits := c.api.ToBinary(gamma2, nbits) + + // points and scalars must be non-zero + var res G1Affine + res.scalarBitsMul(c.api, *P[len(P)-1], gamma1Bits, gamma2Bits, opts...) + for i := len(P) - 2; i > 0; i-- { + res = *addFn(P[i], &res) + res.scalarBitsMul(c.api, res, gamma1Bits, gamma2Bits, opts...) + } + res = *addFn(P[0], &res) + return &res, nil + } +} + +// Select sets p1 if b=1, p2 if b=0, and returns it. b must be boolean constrained +func (c *Curve) Select(b frontend.Variable, p1, p2 *G1Affine) *G1Affine { + return &G1Affine{ + X: c.api.Select(b, p1.X, p2.X), + Y: c.api.Select(b, p1.Y, p2.Y), + } +} + +// Lookup2 performs a 2-bit lookup between p1, p2, p3, p4 based on bits b0 and b1. +// Returns: +// - p1 if b0=0 and b1=0, +// - p2 if b0=1 and b1=0, +// - p3 if b0=0 and b1=1, +// - p4 if b0=1 and b1=1. +func (c *Curve) Lookup2(b1, b2 frontend.Variable, p1, p2, p3, p4 *G1Affine) *G1Affine { + return &G1Affine{ + X: c.api.Lookup2(b1, b2, p1.X, p2.X, p3.X, p4.X), + Y: c.api.Lookup2(b1, b2, p1.Y, p2.Y, p3.Y, p4.Y), + } +} + +// Mux performs a lookup from the inputs and returns inputs[sel]. It is most +// efficient for power of two lengths of the inputs, but works for any number of +// inputs. +func (c *Curve) Mux(sel frontend.Variable, inputs ...*G1Affine) *G1Affine { + xs := make([]frontend.Variable, len(inputs)) + ys := make([]frontend.Variable, len(inputs)) + for i := range inputs { + xs[i] = inputs[i].X + ys[i] = inputs[i].Y + } + return &G1Affine{ + X: selector.Mux(c.api, sel, xs...), + Y: selector.Mux(c.api, sel, ys...), + } +} + +// NewG1Affine allocates a witness from the native G1 element and returns it. +func NewG1Affine(v grumpkin.G1Affine) G1Affine { + return G1Affine{ + X: (fr_bn254.Element)(v.X), + Y: (fr_bn254.Element)(v.Y), + } +} + +// Scalar is a scalar in the groups. As the implementation is defined on a +// 2-chain, then this type is an alias to [frontend.Variable]. +type Scalar = emulated.Element[ScalarField] + +// NewScalar allocates a witness from the native scalar and returns it. +func NewScalar(v fr_grumpkin.Element) Scalar { + return emulated.ValueOf[ScalarField](v) +} + +// packScalarToVar packs the limbs of emulated scalar to a frontend.Variable. +// +// The method is for compatibility for existing scalar multiplication +// implementation which assumes as an input frontend.Variable. +func (c *Curve) packScalarToVar(s *Scalar) frontend.Variable { + var fr ScalarField + reduced := c.fr.Reduce(s) + var res frontend.Variable = 0 + nbBits := fr.BitsPerLimb() + coef := new(big.Int) + one := big.NewInt(1) + for i := range reduced.Limbs { + res = c.api.Add(res, c.api.Mul(reduced.Limbs[i], coef.Lsh(one, nbBits*uint(i)))) + } + return res +} + +// ScalarField defines the [emulated.FieldParams] implementation on a one limb of the scalar field. +type ScalarField = emparams.GRUMPKINFr diff --git a/std/math/emulated/emparams/emparams.go b/std/math/emulated/emparams/emparams.go index 51e3ed8c2e..172238fecb 100644 --- a/std/math/emulated/emparams/emparams.go +++ b/std/math/emulated/emparams/emparams.go @@ -117,6 +117,34 @@ type BN254Fr struct{ fourLimbPrimeField } func (fp BN254Fr) Modulus() *big.Int { return ecc.BN254.ScalarField() } +// GRUMPKINFp provides type parametrization for field emulation: +// - limbs: 4 +// - limb width: 64 bits +// +// The prime modulus for type parametrisation is: +// +// 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 (base 16) +// 21888242871839275222246405745257275088548364400416034343698204186575808495617 (base 10) +// +// This is the base field of the GRUMPKIN curve. +type GRUMPKINFp struct{ fourLimbPrimeField } + +func (fp GRUMPKINFp) Modulus() *big.Int { return ecc.GRUMPKIN.BaseField() } + +// GRUMPKINFr provides type parametrization for field emulation: +// - limbs: 4 +// - limb width: 64 bits +// +// The prime modulus for type parametrisation is: +// +// 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 (base 16) +// 21888242871839275222246405745257275088696311157297823662689037894645226208583 (base 10) +// +// This is the scalar field of the GRUMPKIN curve. +type GRUMPKINFr struct{ fourLimbPrimeField } + +func (fp GRUMPKINFr) Modulus() *big.Int { return ecc.GRUMPKIN.ScalarField() } + // BLS12377Fp provides type parametrization for field emulation: // - limbs: 6 // - limb width: 64 bits diff --git a/std/permutation/poseidon2/poseidon2.go b/std/permutation/poseidon2/poseidon2.go index 02a90aeb87..4786027a6e 100644 --- a/std/permutation/poseidon2/poseidon2.go +++ b/std/permutation/poseidon2/poseidon2.go @@ -49,7 +49,7 @@ type parameters struct { func NewPoseidon2(api frontend.API) (*Permutation, error) { switch utils.FieldToCurve(api.Compiler().Field()) { // TODO: assumes pairing based builder, reconsider when supporting other backends case ecc.BLS12_377: - params := poseidonbls12377.NewDefaultParameters() + params := poseidonbls12377.GetDefaultParameters() return NewPoseidon2FromParameters(api, 2, params.NbFullRounds, params.NbPartialRounds) // TODO: we don't have default parameters for other curves yet. Update this when we do. default: