From 1814e605313f0c3fc9b1ebe950092b0d9c4a0fdf Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 11 Dec 2024 19:47:46 +0100 Subject: [PATCH 01/12] fix: NNA quotient length computation edge cases (#1340) --- std/math/emulated/field_mul.go | 72 +++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/std/math/emulated/field_mul.go b/std/math/emulated/field_mul.go index a1947cb7c..1d44483d5 100644 --- a/std/math/emulated/field_mul.go +++ b/std/math/emulated/field_mul.go @@ -347,10 +347,18 @@ func (f *Field[T]) callMulHint(a, b *Element[T], isMulMod bool, customMod *Eleme // the quotient can be the total length of the multiplication result. modbits = 0 } - nbQuoLimbs := (uint(nbMultiplicationResLimbs(len(a.Limbs), len(b.Limbs)))*nbBits + nextOverflow + 1 - // - modbits + // - nbBits - 1) / - nbBits + var nbQuoLimbs uint + if uint(nbMultiplicationResLimbs(len(a.Limbs), len(b.Limbs)))*nbBits+nextOverflow+nbBits > modbits { + // when the product of a*b is wider than the modulus, then we need + // non-zero limbs for the quotient. Otherwise the quotient is zero, + // represented on zero limbs. But we already handle cases when the + // quotient is zero in the calling functions, this is only for + // additional safety. + nbQuoLimbs = (uint(nbMultiplicationResLimbs(len(a.Limbs), len(b.Limbs)))*nbBits + nextOverflow + 1 - // + modbits + // + nbBits - 1) / + nbBits + } // the remainder is always less than modulus so can represent on the same // number of limbs as the modulus. nbRemLimbs := nbLimbs @@ -435,15 +443,26 @@ func mulHint(field *big.Int, inputs, outputs []*big.Int) error { if err := limbs.Decompose(rem, uint(nbBits), remLimbs); err != nil { return fmt.Errorf("decompose rem: %w", err) } - xp := limbMul(alimbs, blimbs) - yp := limbMul(quoLimbs, plimbs) + // to compute the carries, we need to perform multiplication on limbs + lhs := limbMul(alimbs, blimbs) + rhs := limbMul(quoLimbs, plimbs) + // add the remainder to the rhs, it now only has k*p. This is only for very + // edge cases where by adding the remainder we get additional bits in the + // carry. + for i := range remLimbs { + if i < len(rhs) { + rhs[i].Add(rhs[i], remLimbs[i]) + } else { + rhs = append(rhs, new(big.Int).Set(remLimbs[i])) + } + } carry := new(big.Int) for i := range carryLimbs { - if i < len(xp) { - carry.Add(carry, xp[i]) + if i < len(lhs) { + carry.Add(carry, lhs[i]) } - if i < len(yp) { - carry.Sub(carry, yp[i]) + if i < len(rhs) { + carry.Sub(carry, rhs[i]) } carry.Rsh(carry, uint(nbBits)) carryLimbs[i] = new(big.Int).Set(carry) @@ -714,7 +733,10 @@ func (f *Field[T]) callPolyMvHint(mv *multivariate[T], at []*Element[T]) (quo, r nbLimbs, nbBits := f.fParams.NbLimbs(), f.fParams.BitsPerLimb() modBits := uint(f.fParams.Modulus().BitLen()) quoSize := f.polyMvEvalQuoSize(mv, at) - nbQuoLimbs := (uint(quoSize) - modBits + nbBits) / nbBits + var nbQuoLimbs uint + if quoSize+nbBits > modBits { + nbQuoLimbs = (quoSize - modBits + nbBits) / nbBits + } nbRemLimbs := nbLimbs nbCarryLimbs := nbMultiplicationResLimbs(int(nbQuoLimbs), int(nbLimbs)) - 1 @@ -723,7 +745,7 @@ func (f *Field[T]) callPolyMvHint(mv *multivariate[T], at []*Element[T]) (quo, r nbHintInputs += len(at[i].Limbs) + 1 } hintInputs := make([]frontend.Variable, 0, nbHintInputs) - hintInputs = append(hintInputs, nbBits, nbLimbs, len(mv.Terms), len(at), nbQuoLimbs, nbRemLimbs, nbCarryLimbs) + hintInputs = append(hintInputs, nbBits, nbLimbs, len(mv.Terms), len(at), nbQuoLimbs, nbCarryLimbs) // store the terms in the hint input. First the exponents for i := range mv.Terms { for j := range mv.Terms[i] { @@ -837,24 +859,28 @@ func (mc *mvCheck[T]) cleanEvaluations() { // // As it only depends on the bit-length of the inputs, then we can precompute it // regardless of the actual values. -func (f *Field[T]) polyMvEvalQuoSize(mv *multivariate[T], at []*Element[T]) (quoSize int) { +func (f *Field[T]) polyMvEvalQuoSize(mv *multivariate[T], at []*Element[T]) (quoSize uint) { var fp T - quoSizes := make([]int, len(mv.Terms)) + quoSizes := make([]uint, len(mv.Terms)) for i, term := range mv.Terms { // for every term, the result length is the sum of the lengths of the // variables and the coefficient. - var lengths []int + var lengths []uint for j, pow := range term { for k := 0; k < pow; k++ { - lengths = append(lengths, len(at[j].Limbs)*int(fp.BitsPerLimb())+int(at[j].overflow)) + lengths = append(lengths, uint(len(at[j].Limbs))*fp.BitsPerLimb()+at[j].overflow) } } - lengths = append(lengths, bits.Len(uint(mv.Coefficients[i]))) - quoSizes[i] = sum(lengths...) - 1 + lengths = append(lengths, uint(bits.Len(uint(mv.Coefficients[i])))) + if lengthSum := sum(lengths...); lengthSum > 0 { + // in edge case when inputs are zeros and coefficient is zero, we + // would have a underflow otherwise. + quoSizes[i] = lengthSum - 1 + } } // and for the full result, it is maximum of the inputs. We also add a bit // for every term for overflow. - quoSize = max(quoSizes...) + len(quoSizes) + quoSize = max(quoSizes...) + uint(len(quoSizes)) return quoSize } @@ -871,8 +897,8 @@ func polyMvHint(mod *big.Int, inputs, outputs []*big.Int) error { nbTerms = int(inputs[2].Int64()) nbVars = int(inputs[3].Int64()) nbQuoLimbs = int(inputs[4].Int64()) - nbRemLimbs = int(inputs[5].Int64()) - nbCarryLimbs = int(inputs[6].Int64()) + nbRemLimbs = nbLimbs + nbCarryLimbs = int(inputs[5].Int64()) ) if len(outputs) != nbQuoLimbs+nbRemLimbs+nbCarryLimbs { return fmt.Errorf("output length mismatch") @@ -884,7 +910,7 @@ func polyMvHint(mod *big.Int, inputs, outputs []*big.Int) error { outPtr += nbRemLimbs carryLimbs := outputs[outPtr : outPtr+nbCarryLimbs] terms := make([][]int, nbTerms) - ptr := 7 + ptr := 6 // read the terms for i := range terms { terms[i] = make([]int, nbVars) @@ -986,7 +1012,7 @@ func polyMvHint(mod *big.Int, inputs, outputs []*big.Int) error { } // compute the result as r + k*p on limbs - rhs := make([]*big.Int, nbMultiplicationResLimbs(nbQuoLimbs, nbLimbs)) + rhs := make([]*big.Int, max(nbLimbs, nbMultiplicationResLimbs(nbQuoLimbs, nbLimbs))) for i := range rhs { rhs[i] = new(big.Int) } From 754b26e0c06785c1d5d1364228aa75fec01dac7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:01:25 -0600 Subject: [PATCH 02/12] build(deps): bump golang.org/x/crypto from 0.26.0 to 0.31.0 (#1346) --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index fbf26d48f..6e0371490 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/ronanh/intcomp v1.1.0 github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.9.0 - golang.org/x/crypto v0.26.0 + golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 golang.org/x/sync v0.8.0 ) @@ -33,7 +33,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/sys v0.24.0 // indirect + golang.org/x/sys v0.28.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 e4a044940..5a48cfc66 100644 --- a/go.sum +++ b/go.sum @@ -306,8 +306,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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 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= @@ -464,8 +464,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.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.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= From e5315fbaf710eeb623ffe5eb675f479da1fbbcb1 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Sat, 14 Dec 2024 11:03:52 -0500 Subject: [PATCH 03/12] Perf: Pairing on BN254 using direct Fp12 extension and non-native `Eval()` (#1339) --- internal/stats/latest_stats.csv | 4 +- std/algebra/emulated/fields_bn254/doc.go | 6 +- std/algebra/emulated/fields_bn254/e12.go | 843 ++++++++++++++---- .../emulated/fields_bn254/e12_pairing.go | 714 ++++++--------- std/algebra/emulated/fields_bn254/e12_test.go | 440 ++++----- std/algebra/emulated/fields_bn254/e2.go | 35 +- std/algebra/emulated/fields_bn254/e6.go | 495 ---------- std/algebra/emulated/fields_bn254/e6_test.go | 444 --------- std/algebra/emulated/fields_bn254/hints.go | 441 +++------ std/algebra/emulated/sw_bn254/g2.go | 57 +- std/algebra/emulated/sw_bn254/hints.go | 124 +++ std/algebra/emulated/sw_bn254/pairing.go | 665 +++++++------- std/algebra/emulated/sw_bn254/pairing_test.go | 162 +--- std/algebra/emulated/sw_emulated/point.go | 15 +- std/evmprecompiles/08-bnpairing.go | 2 +- 15 files changed, 1769 insertions(+), 2678 deletions(-) delete mode 100644 std/algebra/emulated/fields_bn254/e6.go delete mode 100644 std/algebra/emulated/fields_bn254/e6_test.go diff --git a/internal/stats/latest_stats.csv b/internal/stats/latest_stats.csv index 18197f897..5b272ec86 100644 --- a/internal/stats/latest_stats.csv +++ b/internal/stats/latest_stats.csv @@ -181,14 +181,14 @@ pairing_bls24315,bls24_315,plonk,0,0 pairing_bls24315,bls24_317,plonk,0,0 pairing_bls24315,bw6_761,plonk,0,0 pairing_bls24315,bw6_633,plonk,141249,141249 -pairing_bn254,bn254,groth16,963003,1603091 +pairing_bn254,bn254,groth16,604783,990919 pairing_bn254,bls12_377,groth16,0,0 pairing_bn254,bls12_381,groth16,0,0 pairing_bn254,bls24_315,groth16,0,0 pairing_bn254,bls24_317,groth16,0,0 pairing_bn254,bw6_761,groth16,0,0 pairing_bn254,bw6_633,groth16,0,0 -pairing_bn254,bn254,plonk,3771397,3534755 +pairing_bn254,bn254,plonk,2319665,2030447 pairing_bn254,bls12_377,plonk,0,0 pairing_bn254,bls12_381,plonk,0,0 pairing_bn254,bls24_315,plonk,0,0 diff --git a/std/algebra/emulated/fields_bn254/doc.go b/std/algebra/emulated/fields_bn254/doc.go index ae94b3c24..f4c7ac289 100644 --- a/std/algebra/emulated/fields_bn254/doc.go +++ b/std/algebra/emulated/fields_bn254/doc.go @@ -1,6 +1,10 @@ -// Package fields_bn254 implements the fields arithmetic of the Fp12 tower +// Package fields_bn254 implements the fields arithmetic of the direct 𝔽p¹² extension // used to compute the pairing over the BN254 curve. // +// 𝔽p¹²[i] = 𝔽p/i¹²-18i⁶+82 +// +// This direct tower is isomorphic to the 2-3-2 tower: +// // 𝔽p²[u] = 𝔽p/u²+1 // 𝔽p⁶[v] = 𝔽p²/v³-9-u // 𝔽p¹²[w] = 𝔽p⁶/w²-v diff --git a/std/algebra/emulated/fields_bn254/e12.go b/std/algebra/emulated/fields_bn254/e12.go index 1cc99fe36..6d97690b0 100644 --- a/std/algebra/emulated/fields_bn254/e12.go +++ b/std/algebra/emulated/fields_bn254/e12.go @@ -1,217 +1,421 @@ package fields_bn254 import ( + "math/big" + "github.com/consensys/gnark-crypto/ecc/bn254" + fp_bn "github.com/consensys/gnark-crypto/ecc/bn254/fp" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/math/emulated" ) type E12 struct { - C0, C1 E6 + A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11 baseEl } type Ext12 struct { - *Ext6 + *Ext2 + api frontend.API + fp *curveF } func NewExt12(api frontend.API) *Ext12 { - return &Ext12{Ext6: NewExt6(api)} + fp, err := emulated.NewField[emulated.BN254Fp](api) + if err != nil { + panic(err) + } + return &Ext12{ + Ext2: NewExt2(api), + api: api, + fp: fp, + } } -func (e Ext12) Add(x, y *E12) *E12 { - z0 := e.Ext6.Add(&x.C0, &y.C0) - z1 := e.Ext6.Add(&x.C1, &y.C1) +func (e Ext12) Zero() *E12 { + zero := e.fp.Zero() return &E12{ - C0: *z0, - C1: *z1, + A0: *zero, + A1: *zero, + A2: *zero, + A3: *zero, + A4: *zero, + A5: *zero, + A6: *zero, + A7: *zero, + A8: *zero, + A9: *zero, + A10: *zero, + A11: *zero, } } -func (e Ext12) Sub(x, y *E12) *E12 { - z0 := e.Ext6.Sub(&x.C0, &y.C0) - z1 := e.Ext6.Sub(&x.C1, &y.C1) +func (e Ext12) One() *E12 { + one := e.fp.One() + zero := e.fp.Zero() return &E12{ - C0: *z0, - C1: *z1, + A0: *one, + A1: *zero, + A2: *zero, + A3: *zero, + A4: *zero, + A5: *zero, + A6: *zero, + A7: *zero, + A8: *zero, + A9: *zero, + A10: *zero, + A11: *zero, } } -func (e Ext12) Conjugate(x *E12) *E12 { - z1 := e.Ext6.Neg(&x.C1) +func (e Ext12) Neg(x *E12) *E12 { + a0 := e.fp.Neg(&x.A0) + a1 := e.fp.Neg(&x.A1) + a2 := e.fp.Neg(&x.A2) + a3 := e.fp.Neg(&x.A3) + a4 := e.fp.Neg(&x.A4) + a5 := e.fp.Neg(&x.A5) + a6 := e.fp.Neg(&x.A6) + a7 := e.fp.Neg(&x.A7) + a8 := e.fp.Neg(&x.A8) + a9 := e.fp.Neg(&x.A9) + a10 := e.fp.Neg(&x.A10) + a11 := e.fp.Neg(&x.A11) + return &E12{ - C0: x.C0, - C1: *z1, + A0: *a0, + A1: *a1, + A2: *a2, + A3: *a3, + A4: *a4, + A5: *a5, + A6: *a6, + A7: *a7, + A8: *a8, + A9: *a9, + A10: *a10, + A11: *a11, } } -func (e Ext12) Mul(x, y *E12) *E12 { - a := e.Ext6.Add(&x.C0, &x.C1) - b := e.Ext6.Add(&y.C0, &y.C1) - a = e.Ext6.Mul(a, b) - b = e.Ext6.Mul(&x.C0, &y.C0) - c := e.Ext6.Mul(&x.C1, &y.C1) - d := e.Ext6.Add(c, b) - z1 := e.Ext6.Sub(a, d) - z0 := e.Ext6.MulByNonResidue(c) - z0 = e.Ext6.Add(z0, b) +func (e Ext12) Add(x, y *E12) *E12 { + a0 := e.fp.Add(&x.A0, &y.A0) + a1 := e.fp.Add(&x.A1, &y.A1) + a2 := e.fp.Add(&x.A2, &y.A2) + a3 := e.fp.Add(&x.A3, &y.A3) + a4 := e.fp.Add(&x.A4, &y.A4) + a5 := e.fp.Add(&x.A5, &y.A5) + a6 := e.fp.Add(&x.A6, &y.A6) + a7 := e.fp.Add(&x.A7, &y.A7) + a8 := e.fp.Add(&x.A8, &y.A8) + a9 := e.fp.Add(&x.A9, &y.A9) + a10 := e.fp.Add(&x.A10, &y.A10) + a11 := e.fp.Add(&x.A11, &y.A11) + return &E12{ - C0: *z0, - C1: *z1, + A0: *a0, + A1: *a1, + A2: *a2, + A3: *a3, + A4: *a4, + A5: *a5, + A6: *a6, + A7: *a7, + A8: *a8, + A9: *a9, + A10: *a10, + A11: *a11, } } -func (e Ext12) Zero() *E12 { - zero := e.fp.Zero() +func (e Ext12) Sub(x, y *E12) *E12 { + a0 := e.fp.Sub(&x.A0, &y.A0) + a1 := e.fp.Sub(&x.A1, &y.A1) + a2 := e.fp.Sub(&x.A2, &y.A2) + a3 := e.fp.Sub(&x.A3, &y.A3) + a4 := e.fp.Sub(&x.A4, &y.A4) + a5 := e.fp.Sub(&x.A5, &y.A5) + a6 := e.fp.Sub(&x.A6, &y.A6) + a7 := e.fp.Sub(&x.A7, &y.A7) + a8 := e.fp.Sub(&x.A8, &y.A8) + a9 := e.fp.Sub(&x.A9, &y.A9) + a10 := e.fp.Sub(&x.A10, &y.A10) + a11 := e.fp.Sub(&x.A11, &y.A11) + return &E12{ - C0: E6{ - B0: E2{A0: *zero, A1: *zero}, - B1: E2{A0: *zero, A1: *zero}, - B2: E2{A0: *zero, A1: *zero}, - }, - C1: E6{ - B0: E2{A0: *zero, A1: *zero}, - B1: E2{A0: *zero, A1: *zero}, - B2: E2{A0: *zero, A1: *zero}, - }, + A0: *a0, + A1: *a1, + A2: *a2, + A3: *a3, + A4: *a4, + A5: *a5, + A6: *a6, + A7: *a7, + A8: *a8, + A9: *a9, + A10: *a10, + A11: *a11, } } -func (e Ext12) One() *E12 { - z000 := e.fp.One() - zero := e.fp.Zero() +func (e Ext12) Double(x *E12) *E12 { + two := big.NewInt(2) + a0 := e.fp.MulConst(&x.A0, two) + a1 := e.fp.MulConst(&x.A1, two) + a2 := e.fp.MulConst(&x.A2, two) + a3 := e.fp.MulConst(&x.A3, two) + a4 := e.fp.MulConst(&x.A4, two) + a5 := e.fp.MulConst(&x.A5, two) + a6 := e.fp.MulConst(&x.A6, two) + a7 := e.fp.MulConst(&x.A7, two) + a8 := e.fp.MulConst(&x.A8, two) + a9 := e.fp.MulConst(&x.A9, two) + a10 := e.fp.MulConst(&x.A10, two) + a11 := e.fp.MulConst(&x.A11, two) + return &E12{ - C0: E6{ - B0: E2{A0: *z000, A1: *zero}, - B1: E2{A0: *zero, A1: *zero}, - B2: E2{A0: *zero, A1: *zero}, - }, - C1: E6{ - B0: E2{A0: *zero, A1: *zero}, - B1: E2{A0: *zero, A1: *zero}, - B2: E2{A0: *zero, A1: *zero}, - }, + A0: *a0, + A1: *a1, + A2: *a2, + A3: *a3, + A4: *a4, + A5: *a5, + A6: *a6, + A7: *a7, + A8: *a8, + A9: *a9, + A10: *a10, + A11: *a11, } } -func (e Ext12) IsZero(z *E12) frontend.Variable { - c0 := e.Ext6.IsZero(&z.C0) - c1 := e.Ext6.IsZero(&z.C1) - return e.api.And(c0, c1) -} - -func (e Ext12) Square(x *E12) *E12 { - c0 := e.Ext6.Sub(&x.C0, &x.C1) - c3 := e.Ext6.MulByNonResidue(&x.C1) - c3 = e.Ext6.Sub(&x.C0, c3) - c2 := e.Ext6.Mul(&x.C0, &x.C1) - c0 = e.Ext6.Mul(c0, c3) - c0 = e.Ext6.Add(c0, c2) - z1 := e.Ext6.Double(c2) - c2 = e.Ext6.MulByNonResidue(c2) - z0 := e.Ext6.Add(c0, c2) +func (e Ext12) Conjugate(x *E12) *E12 { return &E12{ - C0: *z0, - C1: *z1, + A0: x.A0, + A1: *e.fp.Neg(&x.A1), + A2: x.A2, + A3: *e.fp.Neg(&x.A3), + A4: x.A4, + A5: *e.fp.Neg(&x.A5), + A6: x.A6, + A7: *e.fp.Neg(&x.A7), + A8: x.A8, + A9: *e.fp.Neg(&x.A9), + A10: x.A10, + A11: *e.fp.Neg(&x.A11), } } -// Granger--Scott cyclotomic square -func (e Ext12) CyclotomicSquare(x *E12) *E12 { - t0 := e.Ext2.Square(&x.C1.B1) - t1 := e.Ext2.Square(&x.C0.B0) - t6 := e.Ext2.Add(&x.C1.B1, &x.C0.B0) - t6 = e.Ext2.Square(t6) - t6 = e.Ext2.Sub(t6, t0) - t6 = e.Ext2.Sub(t6, t1) - t2 := e.Ext2.Square(&x.C0.B2) - t3 := e.Ext2.Square(&x.C1.B0) - t7 := e.Ext2.Add(&x.C0.B2, &x.C1.B0) - t7 = e.Ext2.Square(t7) - t7 = e.Ext2.Sub(t7, t2) - t7 = e.Ext2.Sub(t7, t3) - t4 := e.Ext2.Square(&x.C1.B2) - t5 := e.Ext2.Square(&x.C0.B1) - t8 := e.Ext2.Add(&x.C1.B2, &x.C0.B1) - t8 = e.Ext2.Square(t8) - t8 = e.Ext2.Sub(t8, t4) - t8 = e.Ext2.Sub(t8, t5) - t8 = e.Ext2.MulByNonResidue(t8) - t0 = e.Ext2.MulByNonResidue(t0) - t0 = e.Ext2.Add(t0, t1) - t2 = e.Ext2.MulByNonResidue(t2) - t2 = e.Ext2.Add(t2, t3) - t4 = e.Ext2.MulByNonResidue(t4) - t4 = e.Ext2.Add(t4, t5) - z00 := e.Ext2.Sub(t0, &x.C0.B0) - z00 = e.Ext2.Double(z00) - z00 = e.Ext2.Add(z00, t0) - z01 := e.Ext2.Sub(t2, &x.C0.B1) - z01 = e.Ext2.Double(z01) - z01 = e.Ext2.Add(z01, t2) - z02 := e.Ext2.Sub(t4, &x.C0.B2) - z02 = e.Ext2.Double(z02) - z02 = e.Ext2.Add(z02, t4) - z10 := e.Ext2.Add(t8, &x.C1.B0) - z10 = e.Ext2.Double(z10) - z10 = e.Ext2.Add(z10, t8) - z11 := e.Ext2.Add(t6, &x.C1.B1) - z11 = e.Ext2.Double(z11) - z11 = e.Ext2.Add(z11, t6) - z12 := e.Ext2.Add(t7, &x.C1.B2) - z12 = e.Ext2.Double(z12) - z12 = e.Ext2.Add(z12, t7) +func (e Ext12) Mul(x, y *E12) *E12 { + return e.mulDirect(x, y) +} + +func (e Ext12) mulDirect(a, b *E12) *E12 { + + // a = a11 w^11 + a10 w^10 + a9 w^9 + a8 w^8 + a7 w^7 + a6 w^6 + a5 w^5 + a4 w^4 + a3 w^3 + a2 w^2 + a1 w + a0 + // b = b11 w^11 + b10 w^10 + b9 w^9 + b8 w^8 + b7 w^7 + b6 w^6 + b5 w^5 + b4 w^4 + b3 w^3 + b2 w^2 + b1 w + b0 + // + // Given that w^12 = 18 w^6 - 82, we can compute the product a * b as follows: + // + // a * b = d11 w^11 + d10 w^10 + d9 w^9 + d8 w^8 + d7 w^7 + d6 w^6 + d5 w^5 + d4 w^4 + d3 w^3 + d2 w^2 + d1 w + d0 + // + // where: + // + // d0 = c0 - 82 * c12 - 1476 * c18 + // d1 = c1 - 82 * c13 - 1476 * c19 + // d2 = c2 - 82 * c14 - 1476 * c20 + // d3 = c3 - 82 * c15 - 1476 * c21 + // d4 = c4 - 82 * c16 - 1476 * c22 + // d5 = c5 - 82 * c17 + // d6 = c6 + 18 * c12 + 242 * c18 + // d7 = c7 + 18 * c13 + 242 * c19 + // d8 = c8 + 18 * c14 + 242 * c20 + // d9 = c9 + 18 * c15 + 242 * c21 + // d10 = c10 + 18 * c16 + 242 * c22 + // d11 = c11 + 18 * c17 + // + // and: + // + // c0 = a0 b0 + // c1 = a0 b1 + a1 b0 + // c2 = a0 b2 + a1 b1 + a2 b0 + // c3 = a0 b3 + a1 b2 + a2 b1 + a3 b0 + // c4 = a0 b4 + a1 b3 + a2 b2 + a3 b1 + a4 b0 + // c5 = a0 b5 + a1 b4 + a2 b3 + a3 b2 + a4 b1 + a5 b0 + // c6 = a0 b6 + a1 b5 + a2 b4 + a3 b3 + a4 b2 + a5 b1 + a6 b0 + // c7 = a0 b7 + a1 b6 + a2 b5 + a3 b4 + a4 b3 + a5 b2 + a6 b1 + a7 b0 + // c8 = a0 b8 + a1 b7 + a2 b6 + a3 b5 + a4 b4 + a5 b3 + a6 b2 + a7 b1 + a8 b0 + // c9 = a0 b9 + a1 b8 + a2 b7 + a3 b6 + a4 b5 + a5 b4 + a6 b3 + a7 b2 + a8 b1 + a9 b0 + // c10 = a0 b10 + a1 b9 + a2 b8 + a3 b7 + a4 b6 + a5 b5 + a6 b4 + a7 b3 + a8 b2 + a9 b1 + a10 b0 + // c11 = a0 b11 + a1 b10 + a2 b9 + a3 b8 + a4 b7 + a5 b6 + a6 b5 + a7 b4 + a8 b3 + a9 b2 + a10 b1 + a11 b0 + // c12 = a1 b11 + a2 b10 + a3 b9 + a4 b8 + a5 b7 + a6 b6 + a7 b5 + a8 b4 + a9 b3 + a10 b2 + a11 b1 + // c13 = a2 b11 + a3 b10 + a4 b9 + a5 b8 + a6 b7 + a7 b6 + a8 b5 + a9 b4 + a10 b3 + a11 b2 + // c14 = a3 b11 + a4 b10 + a5 b9 + a6 b8 + a7 b7 + a8 b6 + a9 b5 + a10 b4 + a11 b3 + // c15 = a4 b11 + a5 b10 + a6 b9 + a7 b8 + a8 b7 + a9 b6 + a10 b5 + a11 b4 + // c16 = a5 b11 + a6 b10 + a7 b9 + a8 b8 + a9 b7 + a10 b6 + a11 b5 + // c17 = a6 b11 + a7 b10 + a8 b9 + a9 b8 + a10 b7 + a11 b6 + // c18 = a7 b11 + a8 b10 + a9 b9 + a10 b8 + a11 b7 + // c19 = a8 b11 + a9 b10 + a10 b9 + a11 b8 + // c20 = a9 b11 + a10 b10 + a11 b9 + // c21 = a10 b11 + a11 b10 + // c22 = a11 b11 + + // d0 = c0 - 82 * c12 - 1476 * c18 + // = a0 b0 - 82 * (a1 b11 + a2 b10 + a3 b9 + a4 b8 + a5 b7 + a6 b6 + a7 b5 + a8 b4 + a9 b3 + a10 b2 + a11 b1) - 1476 * (a7 b11 + a8 b10 + a9 b9 + a10 b8 + a11 b7) + mone := e.fp.NewElement(-1) + d0 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A0}, {mone, &a.A1, &b.A11}, {mone, &a.A2, &b.A10}, {mone, &a.A3, &b.A9}, {mone, &a.A4, &b.A8}, {mone, &a.A5, &b.A7}, {mone, &a.A6, &b.A6}, {mone, &a.A7, &b.A5}, {mone, &a.A8, &b.A4}, {mone, &a.A9, &b.A3}, {mone, &a.A10, &b.A2}, {mone, &a.A11, &b.A1}, {mone, &a.A7, &b.A11}, {mone, &a.A8, &b.A10}, {mone, &a.A9, &b.A9}, {mone, &a.A10, &b.A8}, {mone, &a.A11, &b.A7}}, []int{1, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 1476, 1476, 1476, 1476, 1476}) + + // d1 = c1 - 82 * c13 - 1476 * c19 + // = a0 b1 + a1 b0 - 82 * (a2 b11 + a3 b10 + a4 b9 + a5 b8 + a6 b7 + a7 b6 + a8 b5 + a9 b4 + a10 b3 + a11 b2) - 1476 * (a8 b11 + a9 b10 + a10 b9 + a11 b8) + d1 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A1}, {&a.A1, &b.A0}, {mone, &a.A2, &b.A11}, {mone, &a.A3, &b.A10}, {mone, &a.A4, &b.A9}, {mone, &a.A5, &b.A8}, {mone, &a.A6, &b.A7}, {mone, &a.A7, &b.A6}, {mone, &a.A8, &b.A5}, {mone, &a.A9, &b.A4}, {mone, &a.A10, &b.A3}, {mone, &a.A11, &b.A2}, {mone, &a.A8, &b.A11}, {mone, &a.A9, &b.A10}, {mone, &a.A10, &b.A9}, {mone, &a.A11, &b.A8}}, []int{1, 1, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 1476, 1476, 1476, 1476}) + + // d2 = c2 - 82 * c14 - 1476 * c20 + // = a0 b2 + a1 b1 + a2 b0 - 82 * (a3 b11 + a4 b10 + a5 b9 + a6 b8 + a7 b7 + a8 b6 + a9 b5 + a10 b4 + a11 b3) - 1476 * (a9 b11 + a10 b10 + a11 b9) + d2 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A2}, {&a.A1, &b.A1}, {&a.A2, &b.A0}, {mone, &a.A3, &b.A11}, {mone, &a.A4, &b.A10}, {mone, &a.A5, &b.A9}, {mone, &a.A6, &b.A8}, {mone, &a.A7, &b.A7}, {mone, &a.A8, &b.A6}, {mone, &a.A9, &b.A5}, {mone, &a.A10, &b.A4}, {mone, &a.A11, &b.A3}, {mone, &a.A9, &b.A11}, {mone, &a.A10, &b.A10}, {mone, &a.A11, &b.A9}}, []int{1, 1, 1, 82, 82, 82, 82, 82, 82, 82, 82, 82, 1476, 1476, 1476}) + + // d3 = c3 - 82 * c15 - 1476 * c21 + // = a0 b3 + a1 b2 + a2 b1 + a3 b0 - 82 * (a4 b11 + a5 b10 + a6 b9 + a7 b8 + a8 b7 + a9 b6 + a10 b5 + a11 b4) - 1476 * (a10 b11 + a11 b10) + d3 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A3}, {&a.A1, &b.A2}, {&a.A2, &b.A1}, {&a.A3, &b.A0}, {mone, &a.A4, &b.A11}, {mone, &a.A5, &b.A10}, {mone, &a.A6, &b.A9}, {mone, &a.A7, &b.A8}, {mone, &a.A8, &b.A7}, {mone, &a.A9, &b.A6}, {mone, &a.A10, &b.A5}, {mone, &a.A11, &b.A4}, {mone, &a.A10, &b.A11}, {mone, &a.A11, &b.A10}}, []int{1, 1, 1, 1, 82, 82, 82, 82, 82, 82, 82, 82, 1476, 1476}) + + // d4 = c4 - 82 * c16 - 1476 * c22 + // = a0 b4 + a1 b3 + a2 b2 + a3 b1 + a4 b0 - 82 * (a5 b11 + a6 b10 + a7 b9 + a8 b8 + a9 b7 + a10 b6 + a11 b5) - 1476 * a11 b11 + d4 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A4}, {&a.A1, &b.A3}, {&a.A2, &b.A2}, {&a.A3, &b.A1}, {&a.A4, &b.A0}, {mone, &a.A5, &b.A11}, {mone, &a.A6, &b.A10}, {mone, &a.A7, &b.A9}, {mone, &a.A8, &b.A8}, {mone, &a.A9, &b.A7}, {mone, &a.A10, &b.A6}, {mone, &a.A11, &b.A5}, {mone, &a.A11, &b.A11}}, []int{1, 1, 1, 1, 1, 82, 82, 82, 82, 82, 82, 82, 1476}) + + // d5 = c5 - 82 * c17 + // = a0 b5 + a1 b4 + a2 b3 + a3 b2 + a4 b1 + a5 b0 - 82 * (a6 b11 + a7 b10 + a8 b9 + a9 b8 + a10 b7 + a11 b6) + d5 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A5}, {&a.A1, &b.A4}, {&a.A2, &b.A3}, {&a.A3, &b.A2}, {&a.A4, &b.A1}, {&a.A5, &b.A0}, {mone, &a.A6, &b.A11}, {mone, &a.A7, &b.A10}, {mone, &a.A8, &b.A9}, {mone, &a.A9, &b.A8}, {mone, &a.A10, &b.A7}, {mone, &a.A11, &b.A6}}, []int{1, 1, 1, 1, 1, 1, 82, 82, 82, 82, 82, 82}) + + // d6 = c6 + 18 * c12 + 242 * c18 + // = a0 b6 + a1 b5 + a2 b4 + a3 b3 + a4 b2 + a5 b1 + a6 b0 + 18 * (a1 b11 + a2 b10 + a3 b9 + a4 b8 + a5 b7 + a6 b6 + a7 b5 + a8 b4 + a9 b3 + a10 b2 + a11 b1) + 242 * (a7 b11 + a8 b10 + a9 b9 + a10 b8 + a11 b7) + d6 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A6}, {&a.A1, &b.A5}, {&a.A2, &b.A4}, {&a.A3, &b.A3}, {&a.A4, &b.A2}, {&a.A5, &b.A1}, {&a.A6, &b.A0}, {&a.A1, &b.A11}, {&a.A2, &b.A10}, {&a.A3, &b.A9}, {&a.A4, &b.A8}, {&a.A5, &b.A7}, {&a.A6, &b.A6}, {&a.A7, &b.A5}, {&a.A8, &b.A4}, {&a.A9, &b.A3}, {&a.A10, &b.A2}, {&a.A11, &b.A1}, {&a.A7, &b.A11}, {&a.A8, &b.A10}, {&a.A9, &b.A9}, {&a.A10, &b.A8}, {&a.A11, &b.A7}}, []int{1, 1, 1, 1, 1, 1, 1, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 242, 242, 242, 242, 242}) + + // d7 = c7 + 18 * c13 + 242 * c19 + // = a0 b7 + a1 b6 + a2 b5 + a3 b4 + a4 b3 + a5 b2 + a6 b1 + a7 b0 + 18 * (a2 b11 + a3 b10 + a4 b9 + a5 b8 + a6 b7 + a7 b6 + a8 b5 + a9 b4 + a10 b3 + a11 b2) + 242 * (a8 b11 + a9 b10 + a10 b9 + a11 b8) + d7 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A7}, {&a.A1, &b.A6}, {&a.A2, &b.A5}, {&a.A3, &b.A4}, {&a.A4, &b.A3}, {&a.A5, &b.A2}, {&a.A6, &b.A1}, {&a.A7, &b.A0}, {&a.A2, &b.A11}, {&a.A3, &b.A10}, {&a.A4, &b.A9}, {&a.A5, &b.A8}, {&a.A6, &b.A7}, {&a.A7, &b.A6}, {&a.A8, &b.A5}, {&a.A9, &b.A4}, {&a.A10, &b.A3}, {&a.A11, &b.A2}, {&a.A8, &b.A11}, {&a.A9, &b.A10}, {&a.A10, &b.A9}, {&a.A11, &b.A8}}, []int{1, 1, 1, 1, 1, 1, 1, 1, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 242, 242, 242, 242}) + + // d8 = c8 + 18 * c14 + 242 * c20 + // = a0 b8 + a1 b7 + a2 b6 + a3 b5 + a4 b4 + a5 b3 + a6 b2 + a7 b1 + a8 b0 + 18 * (a3 b11 + a4 b10 + a5 b9 + a6 b8 + a7 b7 + a8 b6 + a9 b5 + a10 b4 + a11 b3) + 242 * (a9 b11 + a10 b10 + a11 b9) + d8 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A8}, {&a.A1, &b.A7}, {&a.A2, &b.A6}, {&a.A3, &b.A5}, {&a.A4, &b.A4}, {&a.A5, &b.A3}, {&a.A6, &b.A2}, {&a.A7, &b.A1}, {&a.A8, &b.A0}, {&a.A3, &b.A11}, {&a.A4, &b.A10}, {&a.A5, &b.A9}, {&a.A6, &b.A8}, {&a.A7, &b.A7}, {&a.A8, &b.A6}, {&a.A9, &b.A5}, {&a.A10, &b.A4}, {&a.A11, &b.A3}, {&a.A9, &b.A11}, {&a.A10, &b.A10}, {&a.A11, &b.A9}}, []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 18, 18, 18, 18, 18, 18, 18, 18, 242, 242, 242}) + + // d9 = c9 + 18 * c15 + 242 * c21 + // = a0 b9 + a1 b8 + a2 b7 + a3 b6 + a4 b5 + a5 b4 + a6 b3 + a7 b2 + a8 b1 + a9 b0 + 18 * (a4 b11 + a5 b10 + a6 b9 + a7 b8 + a8 b7 + a9 b6 + a10 b5 + a11 b4) + 242 * (a10 b11 + a11 b10) + d9 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A9}, {&a.A1, &b.A8}, {&a.A2, &b.A7}, {&a.A3, &b.A6}, {&a.A4, &b.A5}, {&a.A5, &b.A4}, {&a.A6, &b.A3}, {&a.A7, &b.A2}, {&a.A8, &b.A1}, {&a.A9, &b.A0}, {&a.A4, &b.A11}, {&a.A5, &b.A10}, {&a.A6, &b.A9}, {&a.A7, &b.A8}, {&a.A8, &b.A7}, {&a.A9, &b.A6}, {&a.A10, &b.A5}, {&a.A11, &b.A4}, {&a.A10, &b.A11}, {&a.A11, &b.A10}}, []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 18, 18, 18, 18, 18, 18, 18, 242, 242}) + + // d10 = c10 + 18 * c16 + 242 * c22 + // = a0 b10 + a1 b9 + a2 b8 + a3 b7 + a4 b6 + a5 b5 + a6 b4 + a7 b3 + a8 b2 + a9 b1 + a10 b0 + 18 * (a5 b11 + a6 b10 + a7 b9 + a8 b8 + a9 b7 + a10 b6 + a11 b5) + 242 * (a11 b11) + d10 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A10}, {&a.A1, &b.A9}, {&a.A2, &b.A8}, {&a.A3, &b.A7}, {&a.A4, &b.A6}, {&a.A5, &b.A5}, {&a.A6, &b.A4}, {&a.A7, &b.A3}, {&a.A8, &b.A2}, {&a.A9, &b.A1}, {&a.A10, &b.A0}, {&a.A5, &b.A11}, {&a.A6, &b.A10}, {&a.A7, &b.A9}, {&a.A8, &b.A8}, {&a.A9, &b.A7}, {&a.A10, &b.A6}, {&a.A11, &b.A5}, {&a.A11, &b.A11}}, []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 18, 18, 18, 18, 18, 18, 242}) + + // d11 = c11 + 18 * c17 + // = a0 b11 + a1 b10 + a2 b9 + a3 b8 + a4 b7 + a5 b6 + a6 b5 + a7 b4 + a8 b3 + a9 b2 + a10 b1 + a11 b0 + 18 * (a6 b11 + a7 b10 + a8 b9 + a9 b8 + a10 b7 + a11 b6) + d11 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A11}, {&a.A1, &b.A10}, {&a.A2, &b.A9}, {&a.A3, &b.A8}, {&a.A4, &b.A7}, {&a.A5, &b.A6}, {&a.A6, &b.A5}, {&a.A7, &b.A4}, {&a.A8, &b.A3}, {&a.A9, &b.A2}, {&a.A10, &b.A1}, {&a.A11, &b.A0}, {&a.A6, &b.A11}, {&a.A7, &b.A10}, {&a.A8, &b.A9}, {&a.A9, &b.A8}, {&a.A10, &b.A7}, {&a.A11, &b.A6}}, []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 18, 18, 18, 18, 18}) + return &E12{ - C0: E6{ - B0: *z00, - B1: *z01, - B2: *z02, - }, - C1: E6{ - B0: *z10, - B1: *z11, - B2: *z12, - }, + A0: *d0, + A1: *d1, + A2: *d2, + A3: *d3, + A4: *d4, + A5: *d5, + A6: *d6, + A7: *d7, + A8: *d8, + A9: *d9, + A10: *d10, + A11: *d11, } } -func (e Ext12) IsEqual(x, y *E12) frontend.Variable { - isC0Equal := e.Ext6.IsEqual(&x.C0, &y.C0) - isC1Equal := e.Ext6.IsEqual(&x.C1, &y.C1) - return e.api.And(isC0Equal, isC1Equal) +func (e Ext12) Square(x *E12) *E12 { + return e.squareDirect(x) } -func (e Ext12) AssertIsEqual(x, y *E12) { - e.Ext6.AssertIsEqual(&x.C0, &y.C0) - e.Ext6.AssertIsEqual(&x.C1, &y.C1) -} +func (e Ext12) squareDirect(a *E12) *E12 { -func FromE12(y *bn254.E12) E12 { - return E12{ - C0: FromE6(&y.C0), - C1: FromE6(&y.C1), + mone := e.fp.NewElement(-1) + // d0 = a0 a0 - 82 * (2 a1 a11 + 2 a2 a10 + 2 a3 a9 + 2 a4 a8 + 2 a5 a7 + a6 a6) - 1476 * (2 a7 a11 + 2 a8 a10 + a9 a9) + d0 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A0}, {mone, &a.A1, &a.A11}, {mone, &a.A2, &a.A10}, {mone, &a.A3, &a.A9}, {mone, &a.A4, &a.A8}, {mone, &a.A5, &a.A7}, {mone, &a.A6, &a.A6}, {mone, &a.A7, &a.A11}, {mone, &a.A8, &a.A10}, {mone, &a.A9, &a.A9}}, []int{1, 164, 164, 164, 164, 164, 82, 2952, 2952, 1476}) + + // d1 = 2 a0 a1 - 164 * (2 a2 a11 + a3 a10 + a4 a9 + a5 a8 + a6 a7) - 2952 * (a8 a11 + a9 a10) + d1 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A1}, {mone, &a.A2, &a.A11}, {mone, &a.A3, &a.A10}, {mone, &a.A4, &a.A9}, {mone, &a.A5, &a.A8}, {mone, &a.A6, &a.A7}, {mone, &a.A8, &a.A11}, {mone, &a.A9, &a.A10}}, []int{2, 164, 164, 164, 164, 164, 2952, 2952}) + + // d2 = 2 a0 a2 + a1 a1 - 82 * (2 a3 a11 + 2 a4 a10 + 2 a5 a9 + 2 a6 a8 + a7 a7) - 1476 * (2 a9 a11 + a10 a10) + d2 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A2}, {&a.A1, &a.A1}, {mone, &a.A3, &a.A11}, {mone, &a.A4, &a.A10}, {mone, &a.A5, &a.A9}, {mone, &a.A6, &a.A8}, {mone, &a.A7, &a.A7}, {mone, &a.A9, &a.A11}, {mone, &a.A10, &a.A10}}, []int{2, 1, 164, 164, 164, 164, 82, 2952, 1476}) + + // d3 = 2 a0 a3 + 2 a1 a2 - 164 * (a4 a11 + a5 a10 + a6 a9 + a7 a8) - 2952 * a10 a11 + d3 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A3}, {&a.A1, &a.A2}, {mone, &a.A4, &a.A11}, {mone, &a.A5, &a.A10}, {mone, &a.A6, &a.A9}, {mone, &a.A7, &a.A8}, {mone, &a.A10, &a.A11}}, []int{2, 2, 164, 164, 164, 164, 2952}) + + // d4 = 2 a0 a4 + 2 a1 a3 + a2 a2 - 82 * (2 a5 a11 + 2 a6 a10 + 2 a7 a9 + a8 a8) - 1476 * a11 a11 + d4 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A4}, {&a.A1, &a.A3}, {&a.A2, &a.A2}, {mone, &a.A5, &a.A11}, {mone, &a.A6, &a.A10}, {mone, &a.A7, &a.A9}, {mone, &a.A8, &a.A8}, {mone, &a.A11, &a.A11}}, []int{2, 2, 1, 164, 164, 164, 82, 1476}) + + // d5 = 2 (a0 a5 + a1 a4 + a2 a3) - 164 * (a6 a11 + a7 a10 + a8 a9) + d5 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A5}, {&a.A1, &a.A4}, {&a.A2, &a.A3}, {mone, &a.A6, &a.A11}, {mone, &a.A7, &a.A10}, {mone, &a.A8, &a.A9}}, []int{2, 2, 2, 164, 164, 164}) + + // d6 = 2 a0 a6 + 2 a1 a5 + 2 a2 a4 + a3 a3 + 18 * (2 a1 a11 + 2 a2 a10 + 2 a3 a9 + 2 a4 a8 + 2 a5 a7 + a6 a6) + 242 * (2 a7 a11 + 2 a8 a10 + a9 a9) + d6 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A6}, {&a.A1, &a.A5}, {&a.A2, &a.A4}, {&a.A3, &a.A3}, {&a.A1, &a.A11}, {&a.A2, &a.A10}, {&a.A3, &a.A9}, {&a.A4, &a.A8}, {&a.A5, &a.A7}, {&a.A6, &a.A6}, {&a.A7, &a.A11}, {&a.A8, &a.A10}, {&a.A9, &a.A9}}, []int{2, 2, 2, 1, 36, 36, 36, 36, 36, 18, 484, 484, 242}) + + // d7 = 2(a0 a7 + a1 a6 + a2 a5 + a3 a4) + 36 * (a2 a11 + a3 a10 + a4 a9 + a5 a8 + a6 a7) + 484 * (a8 a11 + a9 a10) + d7 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A7}, {&a.A1, &a.A6}, {&a.A2, &a.A5}, {&a.A3, &a.A4}, {&a.A2, &a.A11}, {&a.A3, &a.A10}, {&a.A4, &a.A9}, {&a.A5, &a.A8}, {&a.A6, &a.A7}, {&a.A8, &a.A11}, {&a.A9, &a.A10}}, []int{2, 2, 2, 2, 36, 36, 36, 36, 36, 484, 484}) + + // d8 = 2(a0 a8 + a1 a7 + a2 a6 + a3 a5) + a4 a4 + 18 * (2 a3 a11 + 2 a4 a10 + 2 a5 a9 + 2 a6 a8 + a7 a7) + 242 * (2 a9 a11 + a10 a10) + d8 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A8}, {&a.A1, &a.A7}, {&a.A2, &a.A6}, {&a.A3, &a.A5}, {&a.A4, &a.A4}, {&a.A3, &a.A11}, {&a.A4, &a.A10}, {&a.A5, &a.A9}, {&a.A6, &a.A8}, {&a.A7, &a.A7}, {&a.A9, &a.A11}, {&a.A10, &a.A10}}, []int{2, 2, 2, 2, 1, 36, 36, 36, 36, 18, 484, 242}) + + // d9 = 2(a0 a9 + a1 a8 + a2 a7 + a3 a6 + a4 a5) + 36 * (a4 a11 + a5 a10 + a6 a9 + a7 a8) + 484 * a10 a11 + d9 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A9}, {&a.A1, &a.A8}, {&a.A2, &a.A7}, {&a.A3, &a.A6}, {&a.A4, &a.A5}, {&a.A4, &a.A11}, {&a.A5, &a.A10}, {&a.A6, &a.A9}, {&a.A7, &a.A8}, {&a.A10, &a.A11}}, []int{2, 2, 2, 2, 2, 36, 36, 36, 36, 484}) + + // d10 = 2(a0 a10 + a1 a9 + a2 a8 + a3 a7 + a4 a6) + a5 a5 + 18 * (2 a5 a11 + 2 a6 a10 + 2 a7 a9 + a8 a8) + 242 * a11 a11 + d10 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A10}, {&a.A1, &a.A9}, {&a.A2, &a.A8}, {&a.A3, &a.A7}, {&a.A4, &a.A6}, {&a.A5, &a.A5}, {&a.A5, &a.A11}, {&a.A6, &a.A10}, {&a.A7, &a.A9}, {&a.A8, &a.A8}, {&a.A11, &a.A11}}, []int{2, 2, 2, 2, 2, 1, 36, 36, 36, 18, 242}) + + // d11 = 2(a0 a11 + a1 a10 + a2 a9 + a3 a8 + a4 a7 + a5 a6) + 36 * (a6 a11 + a7 a10 + a8 a9) + d11 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A11}, {&a.A1, &a.A10}, {&a.A2, &a.A9}, {&a.A3, &a.A8}, {&a.A4, &a.A7}, {&a.A5, &a.A6}, {&a.A6, &a.A11}, {&a.A7, &a.A10}, {&a.A8, &a.A9}}, []int{2, 2, 2, 2, 2, 2, 36, 36, 36}) + + return &E12{ + A0: *d0, + A1: *d1, + A2: *d2, + A3: *d3, + A4: *d4, + A5: *d5, + A6: *d6, + A7: *d7, + A8: *d8, + A9: *d9, + A10: *d10, + A11: *d11, } +} + +// Granger-Scott's cyclotomic square +// https://eprint.iacr.org/2009/565.pdf, 3.2 +func (e Ext12) CyclotomicSquareGS(x *E12) *E12 { + tower := e.ToTower(x) + + mone := e.fp.NewElement(-1) + z000 := e.fp.Eval([][]*baseEl{{tower[8], tower[8]}, {mone, tower[9], tower[9]}, {mone, tower[8], tower[9]}, {tower[0], tower[0]}, {mone, tower[1], tower[1]}, {mone, tower[0]}}, []int{27, 27, 6, 3, 3, 2}) + z001 := e.fp.Eval([][]*baseEl{{tower[8], tower[8]}, {mone, tower[9], tower[9]}, {tower[8], tower[9]}, {tower[0], tower[1]}, {mone, tower[1]}}, []int{3, 3, 54, 6, 2}) + z010 := e.fp.Eval([][]*baseEl{{tower[4], tower[4]}, {mone, tower[5], tower[5]}, {mone, tower[4], tower[5]}, {tower[6], tower[6]}, {mone, tower[7], tower[7]}, {mone, tower[2]}}, []int{27, 27, 6, 3, 3, 2}) + z011 := e.fp.Eval([][]*baseEl{{tower[4], tower[4]}, {mone, tower[5], tower[5]}, {tower[4], tower[5]}, {tower[6], tower[7]}, {mone, tower[3]}}, []int{3, 3, 54, 6, 2}) + z020 := e.fp.Eval([][]*baseEl{{tower[10], tower[10]}, {mone, tower[11], tower[11]}, {mone, tower[10], tower[11]}, {tower[2], tower[2]}, {mone, tower[3], tower[3]}, {mone, tower[4]}}, []int{27, 27, 6, 3, 3, 2}) + z021 := e.fp.Eval([][]*baseEl{{tower[10], tower[10]}, {mone, tower[11], tower[11]}, {tower[10], tower[11]}, {tower[2], tower[3]}, {mone, tower[5]}}, []int{3, 3, 54, 6, 2}) + z100 := e.fp.Eval([][]*baseEl{{tower[2], tower[10]}, {mone, tower[3], tower[11]}, {mone, tower[2], tower[11]}, {mone, tower[3], tower[10]}, {tower[6]}}, []int{54, 54, 6, 6, 2}) + z101 := e.fp.Eval([][]*baseEl{{tower[2], tower[10]}, {mone, tower[3], tower[11]}, {tower[2], tower[11]}, {tower[3], tower[10]}, {tower[7]}}, []int{6, 6, 54, 54, 2}) + z110 := e.fp.Eval([][]*baseEl{{tower[0], tower[8]}, {mone, tower[1], tower[9]}, {tower[8]}}, []int{6, 6, 2}) + z111 := e.fp.Eval([][]*baseEl{{tower[0], tower[9]}, {tower[1], tower[8]}, {tower[9]}}, []int{6, 6, 2}) + z120 := e.fp.Eval([][]*baseEl{{tower[4], tower[6]}, {mone, tower[5], tower[7]}, {tower[10]}}, []int{6, 6, 2}) + z121 := e.fp.Eval([][]*baseEl{{tower[4], tower[7]}, {tower[5], tower[6]}, {tower[11]}}, []int{6, 6, 2}) + direct := e.FromTower([12]*baseEl{z000, z001, z010, z011, z020, z021, z100, z101, z110, z111, z120, z121}) + + return direct } func (e Ext12) Inverse(x *E12) *E12 { - res, err := e.fp.NewHint(inverseE12Hint, 12, &x.C0.B0.A0, &x.C0.B0.A1, &x.C0.B1.A0, &x.C0.B1.A1, &x.C0.B2.A0, &x.C0.B2.A1, &x.C1.B0.A0, &x.C1.B0.A1, &x.C1.B1.A0, &x.C1.B1.A1, &x.C1.B2.A0, &x.C1.B2.A1) + res, err := e.fp.NewHint(inverseE12Hint, 12, &x.A0, &x.A1, &x.A2, &x.A3, &x.A4, &x.A5, &x.A6, &x.A7, &x.A8, &x.A9, &x.A10, &x.A11) if err != nil { // err is non-nil only for invalid number of inputs panic(err) } - inv := E12{ - C0: E6{ - B0: E2{A0: *res[0], A1: *res[1]}, - B1: E2{A0: *res[2], A1: *res[3]}, - B2: E2{A0: *res[4], A1: *res[5]}, - }, - C1: E6{ - B0: E2{A0: *res[6], A1: *res[7]}, - B1: E2{A0: *res[8], A1: *res[9]}, - B2: E2{A0: *res[10], A1: *res[11]}, - }, - } - + inv := E12{A0: *res[0], A1: *res[1], A2: *res[2], A3: *res[3], A4: *res[4], A5: *res[5], A6: *res[6], A7: *res[7], A8: *res[8], A9: *res[9], A10: *res[10], A11: *res[11]} one := e.One() // 1 == inv * x @@ -223,41 +427,328 @@ func (e Ext12) Inverse(x *E12) *E12 { } func (e Ext12) DivUnchecked(x, y *E12) *E12 { - res, err := e.fp.NewHint(divE12Hint, 12, &x.C0.B0.A0, &x.C0.B0.A1, &x.C0.B1.A0, &x.C0.B1.A1, &x.C0.B2.A0, &x.C0.B2.A1, &x.C1.B0.A0, &x.C1.B0.A1, &x.C1.B1.A0, &x.C1.B1.A1, &x.C1.B2.A0, &x.C1.B2.A1, &y.C0.B0.A0, &y.C0.B0.A1, &y.C0.B1.A0, &y.C0.B1.A1, &y.C0.B2.A0, &y.C0.B2.A1, &y.C1.B0.A0, &y.C1.B0.A1, &y.C1.B1.A0, &y.C1.B1.A1, &y.C1.B2.A0, &y.C1.B2.A1) - + res, err := e.fp.NewHint(divE12Hint, 12, &x.A0, &x.A1, &x.A2, &x.A3, &x.A4, &x.A5, &x.A6, &x.A7, &x.A8, &x.A9, &x.A10, &x.A11, &y.A0, &y.A1, &y.A2, &y.A3, &y.A4, &y.A5, &y.A6, &y.A7, &y.A8, &y.A9, &y.A10, &y.A11) if err != nil { // err is non-nil only for invalid number of inputs panic(err) } - div := E12{ - C0: E6{ - B0: E2{A0: *res[0], A1: *res[1]}, - B1: E2{A0: *res[2], A1: *res[3]}, - B2: E2{A0: *res[4], A1: *res[5]}, - }, - C1: E6{ - B0: E2{A0: *res[6], A1: *res[7]}, - B1: E2{A0: *res[8], A1: *res[9]}, - B2: E2{A0: *res[10], A1: *res[11]}, - }, - } + div := E12{A0: *res[0], A1: *res[1], A2: *res[2], A3: *res[3], A4: *res[4], A5: *res[5], A6: *res[6], A7: *res[7], A8: *res[8], A9: *res[9], A10: *res[10], A11: *res[11]} - // x == div * y + // x = div * y _x := e.Mul(&div, y) e.AssertIsEqual(x, _x) return &div + +} + +func (e Ext12) AssertIsEqual(a, b *E12) { + e.fp.AssertIsEqual(&a.A0, &b.A0) + e.fp.AssertIsEqual(&a.A1, &b.A1) + e.fp.AssertIsEqual(&a.A2, &b.A2) + e.fp.AssertIsEqual(&a.A3, &b.A3) + e.fp.AssertIsEqual(&a.A4, &b.A4) + e.fp.AssertIsEqual(&a.A5, &b.A5) + e.fp.AssertIsEqual(&a.A6, &b.A6) + e.fp.AssertIsEqual(&a.A7, &b.A7) + e.fp.AssertIsEqual(&a.A8, &b.A8) + e.fp.AssertIsEqual(&a.A9, &b.A9) + e.fp.AssertIsEqual(&a.A10, &b.A10) + e.fp.AssertIsEqual(&a.A11, &b.A11) +} + +func (e Ext12) IsEqual(x, y *E12) 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) + diff6 := e.fp.Sub(&x.A6, &y.A6) + diff7 := e.fp.Sub(&x.A7, &y.A7) + diff8 := e.fp.Sub(&x.A8, &y.A8) + diff9 := e.fp.Sub(&x.A9, &y.A9) + diff10 := e.fp.Sub(&x.A10, &y.A10) + diff11 := e.fp.Sub(&x.A11, &y.A11) + 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) + isZero6 := e.fp.IsZero(diff6) + isZero7 := e.fp.IsZero(diff7) + isZero8 := e.fp.IsZero(diff8) + isZero9 := e.fp.IsZero(diff9) + isZero10 := e.fp.IsZero(diff10) + isZero11 := e.fp.IsZero(diff11) + + return e.api.And( + e.api.And( + e.api.And(e.api.And(isZero0, isZero1), e.api.And(isZero2, isZero3)), + e.api.And(e.api.And(isZero4, isZero5), e.api.And(isZero6, isZero7)), + ), + e.api.And(e.api.And(isZero8, isZero9), e.api.And(isZero10, isZero11)), + ) +} + +func (e Ext12) Copy(x *E12) *E12 { + return &E12{ + A0: x.A0, + A1: x.A1, + A2: x.A2, + A3: x.A3, + A4: x.A4, + A5: x.A5, + A6: x.A6, + A7: x.A7, + A8: x.A8, + A9: x.A9, + A10: x.A10, + A11: x.A11, + } +} + +func (e Ext12) Frobenius(a *E12) *E12 { + tower := e.ToTower(a) + + tower[1] = e.fp.Neg(tower[1]) + tower[3] = e.fp.Neg(tower[3]) + tower[5] = e.fp.Neg(tower[5]) + tower[7] = e.fp.Neg(tower[7]) + tower[9] = e.fp.Neg(tower[9]) + tower[11] = e.fp.Neg(tower[11]) + + t1 := e.Ext2.MulByNonResidue1Power2(&E2{A0: *tower[2], A1: *tower[3]}) + t2 := e.Ext2.MulByNonResidue1Power4(&E2{A0: *tower[4], A1: *tower[5]}) + t3 := e.Ext2.MulByNonResidue1Power1(&E2{A0: *tower[6], A1: *tower[7]}) + t4 := e.Ext2.MulByNonResidue1Power3(&E2{A0: *tower[8], A1: *tower[9]}) + t5 := e.Ext2.MulByNonResidue1Power5(&E2{A0: *tower[10], A1: *tower[11]}) + + nine := big.NewInt(9) + A0 := e.fp.Sub(tower[0], e.fp.MulConst(tower[1], nine)) + A1 := e.fp.Sub(&t3.A0, e.fp.MulConst(&t3.A1, nine)) + A2 := e.fp.Sub(&t1.A0, e.fp.MulConst(&t1.A1, nine)) + A3 := e.fp.Sub(&t4.A0, e.fp.MulConst(&t4.A1, nine)) + A4 := e.fp.Sub(&t2.A0, e.fp.MulConst(&t2.A1, nine)) + A5 := e.fp.Sub(&t5.A0, e.fp.MulConst(&t5.A1, nine)) + + return &E12{ + A0: *A0, + A1: *A1, + A2: *A2, + A3: *A3, + A4: *A4, + A5: *A5, + A6: *tower[1], + A7: t3.A1, + A8: t1.A1, + A9: t4.A1, + A10: t2.A1, + A11: t5.A1, + } +} + +func (e Ext12) FrobeniusSquare(a *E12) *E12 { + tower := e.ToTower(a) + + t1 := e.Ext2.MulByNonResidue2Power2(&E2{A0: *tower[2], A1: *tower[3]}) + t2 := e.Ext2.MulByNonResidue2Power4(&E2{A0: *tower[4], A1: *tower[5]}) + t3 := e.Ext2.MulByNonResidue2Power1(&E2{A0: *tower[6], A1: *tower[7]}) + t4 := e.Ext2.MulByNonResidue2Power3(&E2{A0: *tower[8], A1: *tower[9]}) + t5 := e.Ext2.MulByNonResidue2Power5(&E2{A0: *tower[10], A1: *tower[11]}) + + nine := big.NewInt(9) + A0 := e.fp.Sub(tower[0], e.fp.MulConst(tower[1], nine)) + A1 := e.fp.Sub(&t3.A0, e.fp.MulConst(&t3.A1, nine)) + A2 := e.fp.Sub(&t1.A0, e.fp.MulConst(&t1.A1, nine)) + A3 := e.fp.Sub(&t4.A0, e.fp.MulConst(&t4.A1, nine)) + A4 := e.fp.Sub(&t2.A0, e.fp.MulConst(&t2.A1, nine)) + A5 := e.fp.Sub(&t5.A0, e.fp.MulConst(&t5.A1, nine)) + + return &E12{ + A0: *A0, + A1: *A1, + A2: *A2, + A3: *A3, + A4: *A4, + A5: *A5, + A6: *tower[1], + A7: t3.A1, + A8: t1.A1, + A9: t4.A1, + A10: t2.A1, + A11: t5.A1, + } +} + +func (e Ext12) FrobeniusCube(a *E12) *E12 { + tower := e.ToTower(a) + + tower[1] = e.fp.Neg(tower[1]) + tower[3] = e.fp.Neg(tower[3]) + tower[5] = e.fp.Neg(tower[5]) + tower[7] = e.fp.Neg(tower[7]) + tower[9] = e.fp.Neg(tower[9]) + tower[11] = e.fp.Neg(tower[11]) + + t1 := e.Ext2.MulByNonResidue3Power2(&E2{A0: *tower[2], A1: *tower[3]}) + t2 := e.Ext2.MulByNonResidue3Power4(&E2{A0: *tower[4], A1: *tower[5]}) + t3 := e.Ext2.MulByNonResidue3Power1(&E2{A0: *tower[6], A1: *tower[7]}) + t4 := e.Ext2.MulByNonResidue3Power3(&E2{A0: *tower[8], A1: *tower[9]}) + t5 := e.Ext2.MulByNonResidue3Power5(&E2{A0: *tower[10], A1: *tower[11]}) + + nine := big.NewInt(9) + A0 := e.fp.Sub(tower[0], e.fp.MulConst(tower[1], nine)) + A1 := e.fp.Sub(&t3.A0, e.fp.MulConst(&t3.A1, nine)) + A2 := e.fp.Sub(&t1.A0, e.fp.MulConst(&t1.A1, nine)) + A3 := e.fp.Sub(&t4.A0, e.fp.MulConst(&t4.A1, nine)) + A4 := e.fp.Sub(&t2.A0, e.fp.MulConst(&t2.A1, nine)) + A5 := e.fp.Sub(&t5.A0, e.fp.MulConst(&t5.A1, nine)) + + return &E12{ + A0: *A0, + A1: *A1, + A2: *A2, + A3: *A3, + A4: *A4, + A5: *A5, + A6: *tower[1], + A7: t3.A1, + A8: t1.A1, + A9: t4.A1, + A10: t2.A1, + A11: t5.A1, + } +} + +// tower to direct extension conversion +func FromE12(a *bn254.E12) E12 { + // gnark-crypto uses a quadratic over cubic over quadratic 12th extension of Fp. + // The two towers are isomorphic and the coefficients are permuted as follows: + // a000 a001 a010 a011 a020 a021 a100 a101 a110 a111 a120 a121 + // a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 + // + // A0 = a000 - 9 * a001 + // A1 = a100 - 9 * a101 + // A2 = a010 - 9 * a011 + // A3 = a110 - 9 * a111 + // A4 = a020 - 9 * a021 + // A5 = a120 - 9 * a121 + // A6 = a001 + // A7 = a101 + // A8 = a011 + // A9 = a111 + // A10 = a021 + // A11 = a121 + + var c0, c1, c2, c3, c4, c5, t fp_bn.Element + t.SetUint64(9).Mul(&t, &a.C0.B0.A1) + c0.Sub(&a.C0.B0.A0, &t) + t.SetUint64(9).Mul(&t, &a.C1.B0.A1) + c1.Sub(&a.C1.B0.A0, &t) + t.SetUint64(9).Mul(&t, &a.C0.B1.A1) + c2.Sub(&a.C0.B1.A0, &t) + t.SetUint64(9).Mul(&t, &a.C1.B1.A1) + c3.Sub(&a.C1.B1.A0, &t) + t.SetUint64(9).Mul(&t, &a.C0.B2.A1) + c4.Sub(&a.C0.B2.A0, &t) + t.SetUint64(9).Mul(&t, &a.C1.B2.A1) + c5.Sub(&a.C1.B2.A0, &t) + + return E12{ + A0: emulated.ValueOf[emulated.BN254Fp](c0), + A1: emulated.ValueOf[emulated.BN254Fp](c1), + A2: emulated.ValueOf[emulated.BN254Fp](c2), + A3: emulated.ValueOf[emulated.BN254Fp](c3), + A4: emulated.ValueOf[emulated.BN254Fp](c4), + A5: emulated.ValueOf[emulated.BN254Fp](c5), + A6: emulated.ValueOf[emulated.BN254Fp](a.C0.B0.A1), + A7: emulated.ValueOf[emulated.BN254Fp](a.C1.B0.A1), + A8: emulated.ValueOf[emulated.BN254Fp](a.C0.B1.A1), + A9: emulated.ValueOf[emulated.BN254Fp](a.C1.B1.A1), + A10: emulated.ValueOf[emulated.BN254Fp](a.C0.B2.A1), + A11: emulated.ValueOf[emulated.BN254Fp](a.C1.B2.A1), + } } -func (e Ext12) Select(selector frontend.Variable, z1, z0 *E12) *E12 { - c0 := e.Ext6.Select(selector, &z1.C0, &z0.C0) - c1 := e.Ext6.Select(selector, &z1.C1, &z0.C1) - return &E12{C0: *c0, C1: *c1} +func (e Ext12) ToTower(a *E12) [12]*baseEl { + // gnark-crypto uses a quadratic over cubic over quadratic 12th extension of Fp. + // The two towers are isomorphic and the coefficients are permuted as follows: + // + // tower = a000 a001 a010 a011 a020 a021 a100 a101 a110 a111 a120 a121 + // direct = a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 + // + // a000 = A0 + 9 * A6 + // a001 = A6 + // a010 = A2 + 9 * A8 + // a011 = A8 + // a020 = A4 + 9 * A10 + // a021 = A10 + // a100 = A1 + 9 * A7 + // a101 = A7 + // a110 = A3 + 9 * A9 + // a111 = A9 + // a120 = A5 + 9 * A11 + // a121 = A11 + nine := big.NewInt(9) + a000 := e.fp.Add(&a.A0, e.fp.MulConst(&a.A6, nine)) + a001 := &a.A6 + a010 := e.fp.Add(&a.A2, e.fp.MulConst(&a.A8, nine)) + a011 := &a.A8 + a020 := e.fp.Add(&a.A4, e.fp.MulConst(&a.A10, nine)) + a021 := &a.A10 + a100 := e.fp.Add(&a.A1, e.fp.MulConst(&a.A7, nine)) + a101 := &a.A7 + a110 := e.fp.Add(&a.A3, e.fp.MulConst(&a.A9, nine)) + a111 := &a.A9 + a120 := e.fp.Add(&a.A5, e.fp.MulConst(&a.A11, nine)) + a121 := &a.A11 + + tower := [12]*baseEl{a000, a001, a010, a011, a020, a021, a100, a101, a110, a111, a120, a121} + return tower } -func (e Ext12) Lookup2(s1, s2 frontend.Variable, a, b, c, d *E12) *E12 { - c0 := e.Ext6.Lookup2(s1, s2, &a.C0, &b.C0, &c.C0, &d.C0) - c1 := e.Ext6.Lookup2(s1, s2, &a.C1, &b.C1, &c.C1, &d.C1) - return &E12{C0: *c0, C1: *c1} +func (e Ext12) FromTower(tower [12]*baseEl) *E12 { + // gnark-crypto uses a quadratic over cubic over quadratic 12th extension of Fp. + // The two towers are isomorphic and the coefficients are permuted as follows: + // + // tower = a000 a001 a010 a011 a020 a021 a100 a101 a110 a111 a120 a121 + // direct = a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 + // + // A0 = a000 - 9 * a001 + // A1 = a100 - 9 * a101 + // A2 = a010 - 9 * a011 + // A3 = a110 - 9 * a111 + // A4 = a020 - 9 * a021 + // A5 = a120 - 9 * a121 + // A6 = a001 + // A7 = a101 + // A8 = a011 + // A9 = a111 + // A10 = a021 + // A11 = a121 + nine := big.NewInt(9) + A0 := e.fp.Sub(tower[0], e.fp.MulConst(tower[1], nine)) + A1 := e.fp.Sub(tower[6], e.fp.MulConst(tower[7], nine)) + A2 := e.fp.Sub(tower[2], e.fp.MulConst(tower[3], nine)) + A3 := e.fp.Sub(tower[8], e.fp.MulConst(tower[9], nine)) + A4 := e.fp.Sub(tower[4], e.fp.MulConst(tower[5], nine)) + A5 := e.fp.Sub(tower[10], e.fp.MulConst(tower[11], nine)) + + return &E12{ + A0: *A0, + A1: *A1, + A2: *A2, + A3: *A3, + A4: *A4, + A5: *A5, + A6: *tower[1], + A7: *tower[7], + A8: *tower[3], + A9: *tower[9], + A10: *tower[5], + A11: *tower[11], + } } diff --git a/std/algebra/emulated/fields_bn254/e12_pairing.go b/std/algebra/emulated/fields_bn254/e12_pairing.go index 2e0411afe..395df7182 100644 --- a/std/algebra/emulated/fields_bn254/e12_pairing.go +++ b/std/algebra/emulated/fields_bn254/e12_pairing.go @@ -1,16 +1,80 @@ package fields_bn254 import ( - "github.com/consensys/gnark/std/math/emulated" + "math/big" ) +func (e Ext12) nSquare(z *E12, n int) *E12 { + for i := 0; i < n; i++ { + z = e.Square(z) + } + return z +} + func (e Ext12) nSquareGS(z *E12, n int) *E12 { for i := 0; i < n; i++ { - z = e.CyclotomicSquare(z) + z = e.CyclotomicSquareGS(z) } return z } +// Exponentiation by the seed t=4965661367192848881 +func (e Ext12) Expt(x *E12) *E12 { + // ExptTorus computation is derived from the addition chain: + // + // _10 = 2*1 + // _100 = 2*_10 + // _1000 = 2*_100 + // _10000 = 2*_1000 + // _10001 = 1 + _10000 + // _10011 = _10 + _10001 + // _10100 = 1 + _10011 + // _11001 = _1000 + _10001 + // _100010 = 2*_10001 + // _100111 = _10011 + _10100 + // _101001 = _10 + _100111 + // i27 = (_100010 << 6 + _100 + _11001) << 7 + _11001 + // i44 = (i27 << 8 + _101001 + _10) << 6 + _10001 + // i70 = ((i44 << 8 + _101001) << 6 + _101001) << 10 + // return (_100111 + i70) << 6 + _101001 + _1000 + // + // Operations: 62 squares 17 multiplies + // + // Generated by github.com/mmcloughlin/addchain v0.4.0. + + t3 := e.CyclotomicSquareGS(x) + t5 := e.CyclotomicSquareGS(t3) + result := e.CyclotomicSquareGS(t5) + t0 := e.CyclotomicSquareGS(result) + t2 := e.Mul(x, t0) + t0 = e.Mul(t3, t2) + t1 := e.Mul(x, t0) + t4 := e.Mul(result, t2) + t6 := e.CyclotomicSquareGS(t2) + t1 = e.Mul(t0, t1) + t0 = e.Mul(t3, t1) + t6 = e.nSquareGS(t6, 6) + t5 = e.Mul(t5, t6) + t5 = e.Mul(t4, t5) + t5 = e.nSquareGS(t5, 7) + t4 = e.Mul(t4, t5) + t4 = e.nSquareGS(t4, 8) + t4 = e.Mul(t0, t4) + t3 = e.Mul(t3, t4) + t3 = e.nSquareGS(t3, 6) + t2 = e.Mul(t2, t3) + t2 = e.nSquareGS(t2, 8) + t2 = e.Mul(t0, t2) + t2 = e.nSquareGS(t2, 6) + t2 = e.Mul(t0, t2) + t2 = e.nSquareGS(t2, 10) + t1 = e.Mul(t1, t2) + t1 = e.nSquareGS(t1, 6) + t0 = e.Mul(t0, t1) + z := e.Mul(result, t0) + return z +} + // Exponentiation by U=6u+2 where t is the seed u=4965661367192848881 func (e Ext12) ExpByU(x *E12) *E12 { // ExpByU computation is derived from the addition chain: @@ -39,504 +103,238 @@ func (e Ext12) ExpByU(x *E12) *E12 { z = e.Mul(x, t1) t2 := e.Square(t1) t1 = e.Mul(t0, t2) - t2 = e.nSquareGS(t2, 3) + t2 = e.nSquare(t2, 3) t2 = e.Mul(z, t2) - t2 = e.nSquareGS(t2, 2) + t2 = e.nSquare(t2, 2) t2 = e.Mul(x, t2) - t2 = e.nSquareGS(t2, 5) + t2 = e.nSquare(t2, 5) t2 = e.Mul(t1, t2) - t2 = e.nSquareGS(t2, 3) + t2 = e.nSquare(t2, 3) t2 = e.Mul(x, t2) - t2 = e.nSquareGS(t2, 4) + t2 = e.nSquare(t2, 4) t2 = e.Mul(z, t2) - t2 = e.nSquareGS(t2, 9) + t2 = e.nSquare(t2, 9) t2 = e.Mul(z, t2) - t2 = e.nSquareGS(t2, 4) + t2 = e.nSquare(t2, 4) t2 = e.Mul(t0, t2) - t2 = e.nSquareGS(t2, 5) + t2 = e.nSquare(t2, 5) t1 = e.Mul(t1, t2) t1 = e.Square(t1) t1 = e.Mul(x, t1) - t1 = e.nSquareGS(t1, 5) + t1 = e.nSquare(t1, 5) t1 = e.Mul(z, t1) - t1 = e.nSquareGS(t1, 3) + t1 = e.nSquare(t1, 3) t0 = e.Mul(t0, t1) - t0 = e.nSquareGS(t0, 6) + t0 = e.nSquare(t0, 6) t0 = e.Mul(z, t0) - t0 = e.nSquareGS(t0, 4) + t0 = e.nSquare(t0, 4) z = e.Mul(z, t0) - z = e.nSquareGS(z, 2) + z = e.nSquare(z, 2) z = e.Mul(x, z) - z = e.nSquareGS(z, 2) + z = e.nSquare(z, 2) z = e.Mul(x, z) - z = e.nSquareGS(z, 3) - - return z -} - -func (e Ext12) nSquareTorus(z *E6, n int) *E6 { - for i := 0; i < n; i++ { - z = e.SquareTorus(z) - } - return z -} - -// Exponentiation by the seed t=4965661367192848881 -// The computations are performed on E6 compressed form using Torus-based arithmetic. -func (e Ext12) ExptTorus(x *E6) *E6 { - // ExptTorus computation is derived from the addition chain: - // - // _10 = 2*1 - // _100 = 2*_10 - // _1000 = 2*_100 - // _10000 = 2*_1000 - // _10001 = 1 + _10000 - // _10011 = _10 + _10001 - // _10100 = 1 + _10011 - // _11001 = _1000 + _10001 - // _100010 = 2*_10001 - // _100111 = _10011 + _10100 - // _101001 = _10 + _100111 - // i27 = (_100010 << 6 + _100 + _11001) << 7 + _11001 - // i44 = (i27 << 8 + _101001 + _10) << 6 + _10001 - // i70 = ((i44 << 8 + _101001) << 6 + _101001) << 10 - // return (_100111 + i70) << 6 + _101001 + _1000 - // - // Operations: 62 squares 17 multiplies - // - // Generated by github.com/mmcloughlin/addchain v0.4.0. + z = e.nSquare(z, 3) - t3 := e.SquareTorus(x) - t5 := e.SquareTorus(t3) - result := e.SquareTorus(t5) - t0 := e.SquareTorus(result) - t2 := e.MulTorus(x, t0) - t0 = e.MulTorus(t3, t2) - t1 := e.MulTorus(x, t0) - t4 := e.MulTorus(result, t2) - t6 := e.SquareTorus(t2) - t1 = e.MulTorus(t0, t1) - t0 = e.MulTorus(t3, t1) - t6 = e.nSquareTorus(t6, 6) - t5 = e.MulTorus(t5, t6) - t5 = e.MulTorus(t4, t5) - t5 = e.nSquareTorus(t5, 7) - t4 = e.MulTorus(t4, t5) - t4 = e.nSquareTorus(t4, 8) - t4 = e.MulTorus(t0, t4) - t3 = e.MulTorus(t3, t4) - t3 = e.nSquareTorus(t3, 6) - t2 = e.MulTorus(t2, t3) - t2 = e.nSquareTorus(t2, 8) - t2 = e.MulTorus(t0, t2) - t2 = e.nSquareTorus(t2, 6) - t2 = e.MulTorus(t0, t2) - t2 = e.nSquareTorus(t2, 10) - t1 = e.MulTorus(t1, t2) - t1 = e.nSquareTorus(t1, 6) - t0 = e.MulTorus(t0, t1) - z := e.MulTorus(result, t0) return z } -// Square034 squares an E12 sparse element of the form +// MulBy01379 multiplies a by an E12 sparse element b of the form // -// E12{ -// C0: E6{B0: 1, B1: 0, B2: 0}, -// C1: E6{B0: c3, B1: c4, B2: 0}, -// } -func (e *Ext12) Square034(x *E12) *E12 { - c0 := E6{ - B0: *e.Ext2.Sub(&x.C0.B0, &x.C1.B0), - B1: *e.Ext2.Neg(&x.C1.B1), - B2: *e.Ext2.Zero(), - } - - c3 := &E6{ - B0: x.C0.B0, - B1: *e.Ext2.Neg(&x.C1.B0), - B2: *e.Ext2.Neg(&x.C1.B1), - } - - c2 := E6{ - B0: x.C1.B0, - B1: x.C1.B1, - B2: *e.Ext2.Zero(), - } - c3 = e.MulBy01(c3, &c0.B0, &c0.B1) - c3 = e.Ext6.Add(c3, &c2) - - var z E12 - z.C1.B0 = *e.Ext2.Add(&c2.B0, &c2.B0) - z.C1.B1 = *e.Ext2.Add(&c2.B1, &c2.B1) - - z.C0.B0 = c3.B0 - z.C0.B1 = *e.Ext2.Add(&c3.B1, &c2.B0) - z.C0.B2 = *e.Ext2.Add(&c3.B2, &c2.B1) - - return &z -} - -// MulBy034 multiplies z by an E12 sparse element of the form -// -// E12{ -// C0: E6{B0: 1, B1: 0, B2: 0}, -// C1: E6{B0: c3, B1: c4, B2: 0}, -// } -func (e *Ext12) MulBy034(z *E12, c3, c4 *E2) *E12 { - - a := z.C0 - b := e.MulBy01(&z.C1, c3, c4) - c3 = e.Ext2.Add(e.Ext2.One(), c3) - d := e.Ext6.Add(&z.C0, &z.C1) - d = e.MulBy01(d, c3, c4) - - zC1 := e.Ext6.Add(&a, b) - zC1 = e.Ext6.Neg(zC1) - zC1 = e.Ext6.Add(zC1, d) - zC0 := e.Ext6.MulByNonResidue(b) - zC0 = e.Ext6.Add(zC0, &a) +// b.A0 = 1 +// b.A1 = c3.A0 - 9 * c3.A1 +// b.A2 = 0 +// b.A3 = c4.A0 - 9 * c4.A1 +// b.A4 = 0 +// b.A5 = 0 +// b.A6 = 0 +// b.A7 = c3.A1 +// b.A8 = 0 +// b.A9 = c4.A1 +// b.A10 = 0 +// b.A11 = 0 +func (e *Ext12) MulBy01379(a *E12, c3, c4 *E2) *E12 { + nine := big.NewInt(9) + b1 := e.fp.Sub(&c3.A0, e.fp.MulConst(&c3.A1, nine)) + b3 := e.fp.Sub(&c4.A0, e.fp.MulConst(&c4.A1, nine)) + b7 := &c3.A1 + b9 := &c4.A1 + // d0 = a0 - 82 * (a3 b9 + a5 b7 + a9 b3 + a11 b1) - 1476 * (a9 b9 + a11 b7) + mone := e.fp.NewElement(-1) + d0 := e.fp.Eval([][]*baseEl{{&a.A0}, {mone, &a.A3, b9}, {mone, &a.A5, b7}, {mone, &a.A9, b3}, {mone, &a.A11, b1}, {mone, &a.A9, b9}, {mone, &a.A11, b7}}, []int{1, 82, 82, 82, 82, 1476, 1476}) + + // d1 = a0 b1 + a1 - 82 * (a4 b9 + a10 b3 + a6 b7) - 1476 * a10 b9 + d1 := e.fp.Eval([][]*baseEl{{&a.A0, b1}, {&a.A1}, {mone, &a.A4, b9}, {mone, &a.A10, b3}, {mone, &a.A6, b7}, {mone, &a.A10, b9}}, []int{1, 1, 82, 82, 82, 1476}) + + // d2 = a1 b1 + a2 - 82 * (a5 b9 + a11 b3 + a7 b7) - 1476 * a11 b9 + d2 := e.fp.Eval([][]*baseEl{{&a.A1, b1}, {&a.A2}, {mone, &a.A5, b9}, {mone, &a.A11, b3}, {mone, &a.A7, b7}, {mone, &a.A11, b9}}, []int{1, 1, 82, 82, 82, 1476}) + + // d3 = a0 b3 + a2 b1 + a3 - 82 * (a6 b9 + a8 b7) + d3 := e.fp.Eval([][]*baseEl{{&a.A0, b3}, {&a.A2, b1}, {&a.A3}, {mone, &a.A6, b9}, {mone, &a.A8, b7}}, []int{1, 1, 1, 82, 82}) + + // d4 = a1 b3 + a3 b1 + a4 - 82 * (a7 b9 + a9 b7) + d4 := e.fp.Eval([][]*baseEl{{&a.A1, b3}, {&a.A3, b1}, {&a.A4}, {mone, &a.A7, b9}, {mone, &a.A9, b7}}, []int{1, 1, 1, 82, 82}) + + // d5 = a2 b3 + a4 b1 + a5 - 82 * (a8 b9 + a10 b7) + d5 := e.fp.Eval([][]*baseEl{{&a.A2, b3}, {&a.A4, b1}, {&a.A5}, {mone, &a.A8, b9}, {mone, &a.A10, b7}}, []int{1, 1, 1, 82, 82}) + + // d6 = a3 b3 + a5 b1 + a6 + 18 * (a3 b9 + a9 b3 + a11 b1 + a5 b7) + 242 * (a9 b9 + a11 b7) + d6 := e.fp.Eval([][]*baseEl{{&a.A3, b3}, {&a.A5, b1}, {&a.A6}, {&a.A3, b9}, {&a.A9, b3}, {&a.A11, b1}, {&a.A5, b7}, {&a.A11, b7}, {&a.A9, b9}}, []int{1, 1, 1, 18, 18, 18, 18, 242, 242}) + + // d7 = a0 b7 + a4 b3 + a6 b1 + a7 + 18 * (a4 b9 + a10 b3 + a6 b7) + 242 * a10 b9 + d7 := e.fp.Eval([][]*baseEl{{&a.A0, b7}, {&a.A4, b3}, {&a.A6, b1}, {&a.A7}, {&a.A4, b9}, {&a.A10, b3}, {&a.A6, b7}, {&a.A10, b9}}, []int{1, 1, 1, 1, 18, 18, 18, 242}) + + // d8 = a1 b7 + a5 b3 + a7 b1 + a8 + 18 * (a5 b9 + a11 b3 + a7 b7) + 242 * a11 b9 + d8 := e.fp.Eval([][]*baseEl{{&a.A1, b7}, {&a.A5, b3}, {&a.A7, b1}, {&a.A8}, {&a.A5, b9}, {&a.A11, b3}, {&a.A7, b7}, {&a.A11, b9}}, []int{1, 1, 1, 1, 18, 18, 18, 242}) + + // d9 = a2 b7 + a0 b9 + a6 b3 + a8 b1 + a9 + 18 * (a6 b9 + a8 b7) + d9 := e.fp.Eval([][]*baseEl{{&a.A2, b7}, {&a.A0, b9}, {&a.A6, b3}, {&a.A8, b1}, {&a.A9}, {&a.A6, b9}, {&a.A8, b7}}, []int{1, 1, 1, 1, 1, 18, 18}) + + // d10 = a3 b7 + a1 b9 + a7 b3 + a9 b1 + a10 + 18 * (a7 b9 + a9 b7) + d10 := e.fp.Eval([][]*baseEl{{&a.A3, b7}, {&a.A1, b9}, {&a.A7, b3}, {&a.A9, b1}, {&a.A10}, {&a.A7, b9}, {&a.A9, b7}}, []int{1, 1, 1, 1, 1, 18, 18}) + + // d11 = a4 b7 + a2 b9 + a8 b3 + a10 b1 + a11 + 18 * (a8 b9 + a10 b7) + d11 := e.fp.Eval([][]*baseEl{{&a.A4, b7}, {&a.A2, b9}, {&a.A8, b3}, {&a.A10, b1}, {&a.A11}, {&a.A8, b9}, {&a.A10, b7}}, []int{1, 1, 1, 1, 1, 18, 18}) return &E12{ - C0: *zC0, - C1: *zC1, + A0: *d0, + A1: *d1, + A2: *d2, + A3: *d3, + A4: *d4, + A5: *d5, + A6: *d6, + A7: *d7, + A8: *d8, + A9: *d9, + A10: *d10, + A11: *d11, } } -// multiplies two E12 sparse element of the form: -// -// E12{ -// C0: E6{B0: 1, B1: 0, B2: 0}, -// C1: E6{B0: c3, B1: c4, B2: 0}, -// } -// -// and -// -// E12{ -// C0: E6{B0: 1, B1: 0, B2: 0}, -// C1: E6{B0: d3, B1: d4, B2: 0}, -// } -func (e *Ext12) Mul034By034(d3, d4, c3, c4 *E2) [5]*E2 { - x3 := e.Ext2.Mul(c3, d3) - x4 := e.Ext2.Mul(c4, d4) - x04 := e.Ext2.Add(c4, d4) - x03 := e.Ext2.Add(c3, d3) - tmp := e.Ext2.Add(c3, c4) - x34 := e.Ext2.Add(d3, d4) - x34 = e.Ext2.Mul(x34, tmp) - tmp = e.Ext2.Add(x4, x3) - x34 = e.Ext2.Sub(x34, tmp) - - zC0B0 := e.Ext2.MulByNonResidue(x4) - zC0B0 = e.Ext2.Add(zC0B0, e.Ext2.One()) - zC0B1 := x3 - zC0B2 := x34 - zC1B0 := x03 - zC1B1 := x04 - - return [5]*E2{zC0B0, zC0B1, zC0B2, zC1B0, zC1B1} -} - -// MulBy01234 multiplies z by an E12 sparse element of the form +// Mul01379By01379 multiplies two E12 sparse element of the form: // -// E12{ -// C0: E6{B0: c0, B1: c1, B2: c2}, -// C1: E6{B0: c3, B1: c4, B2: 0}, -// } -func (e *Ext12) MulBy01234(z *E12, x [5]*E2) *E12 { - c0 := &E6{B0: *x[0], B1: *x[1], B2: *x[2]} - c1 := &E6{B0: *x[3], B1: *x[4], B2: *e.Ext2.Zero()} - a := e.Ext6.Add(&z.C0, &z.C1) - b := e.Ext6.Add(c0, c1) - a = e.Ext6.Mul(a, b) - b = e.Ext6.Mul(&z.C0, c0) - c := e.Ext6.MulBy01(&z.C1, x[3], x[4]) - d := e.Ext6.Add(c, b) - z1 := e.Ext6.Sub(a, d) - z0 := e.Ext6.MulByNonResidue(c) - z0 = e.Ext6.Add(z0, b) - return &E12{ - C0: *z0, - C1: *z1, - } -} - -// multiplies two E12 sparse element of the form: -// -// E12{ -// C0: E6{B0: x0, B1: x1, B2: x2}, -// C1: E6{B0: x3, B1: x4, B2: 0}, -// } -// -// and -// -// E12{ -// C0: E6{B0: 1, B1: 0, B2: 0}, -// C1: E6{B0: z3, B1: z4, B2: 0}, -// } -func (e *Ext12) Mul01234By034(x [5]*E2, z3, z4 *E2) *E12 { - c0 := &E6{B0: *x[0], B1: *x[1], B2: *x[2]} - c1 := &E6{B0: *x[3], B1: *x[4], B2: *e.Ext2.Zero()} - a := e.Ext6.Add(e.Ext6.One(), &E6{B0: *z3, B1: *z4, B2: *e.Ext2.Zero()}) - b := e.Ext6.Add(c0, c1) - a = e.Ext6.Mul(a, b) - c := e.Ext6.Mul01By01(z3, z4, x[3], x[4]) - b = e.Ext6.Add(c0, c) - z1 := e.Ext6.Sub(a, b) - z0 := e.Ext6.MulByNonResidue(c) - z0 = e.Ext6.Add(z0, c0) - return &E12{ - C0: *z0, - C1: *z1, - } -} - -// Torus-based arithmetic: -// -// After the easy part of the final exponentiation the elements are in a proper -// subgroup of Fpk (E12) that coincides with some algebraic tori. The elements -// are in the torus Tk(Fp) and thus in each torus Tk/d(Fp^d) for d|k, d≠k. We -// take d=6. So the elements are in T2(Fp6). -// Let G_{q,2} = {m ∈ Fq^2 | m^(q+1) = 1} where q = p^6. -// When m.C1 = 0, then m.C0 must be 1 or −1. -// -// We recall the tower construction: -// -// 𝔽p²[u] = 𝔽p/u²+1 -// 𝔽p⁶[v] = 𝔽p²/v³-9-u -// 𝔽p¹²[w] = 𝔽p⁶/w²-v - -// CompressTorus compresses x ∈ E12 to (x.C0 + 1)/x.C1 ∈ E6 -func (e Ext12) CompressTorus(x *E12) *E6 { - // x ∈ G_{q,2} \ {-1,1} - y := e.Ext6.Add(&x.C0, e.Ext6.One()) - y = e.Ext6.DivUnchecked(y, &x.C1) - return y -} - -// DecompressTorus decompresses y ∈ E6 to (y+w)/(y-w) ∈ E12 -func (e Ext12) DecompressTorus(y *E6) *E12 { - var n, d E12 - one := e.Ext6.One() - n.C0 = *y - n.C1 = *one - d.C0 = *y - d.C1 = *e.Ext6.Neg(one) - - x := e.DivUnchecked(&n, &d) - return x -} - -// MulTorus multiplies two compressed elements y1, y2 ∈ E6 -// and returns (y1 * y2 + v)/(y1 + y2) -// N.B.: we use MulTorus in the final exponentiation throughout y1 ≠ -y2 always. -func (e Ext12) MulTorus(y1, y2 *E6) *E6 { - n := e.Ext6.Mul(y1, y2) - n = &E6{ - B0: n.B0, - B1: *e.Ext2.Add(&n.B1, e.Ext2.One()), - B2: n.B2, - } - d := e.Ext6.Add(y1, y2) - y3 := e.Ext6.DivUnchecked(n, d) - return y3 +// A0 = 1 +// A1 = c3.A0 - 9 * c3.A1 +// A2 = 0 +// A3 = c4.A0 - 9 * c4.A1 +// A4 = 0 +// A5 = 0 +// A6 = 0 +// A7 = c3.A1 +// A8 = 0 +// A9 = c4.A1 +// A10 = 0 +// A11 = 0 +func (e *Ext12) Mul01379By01379(e3, e4, c3, c4 *E2) [10]*baseEl { + nine := big.NewInt(9) + a1 := e.fp.Sub(&e3.A0, e.fp.MulConst(&e3.A1, nine)) + a3 := e.fp.Sub(&e4.A0, e.fp.MulConst(&e4.A1, nine)) + a7 := &e3.A1 + a9 := &e4.A1 + b1 := e.fp.Sub(&c3.A0, e.fp.MulConst(&c3.A1, nine)) + b3 := e.fp.Sub(&c4.A0, e.fp.MulConst(&c4.A1, nine)) + b7 := &c3.A1 + b9 := &c4.A1 + + // d0 = 1 - 82 * (a3 b9 + a9 b3) - 1476 * a9 b9 + mone := e.fp.NewElement(-1) + d0 := e.fp.Eval([][]*baseEl{{a3, b9}, {a9, b3}, {a9, b9}, {mone}}, []int{82, 82, 1476, 1}) + d0 = e.fp.Neg(d0) + + // d1 = b1 + a1 + d1 := e.fp.Add(a1, b1) + + // d2 = a1 b1 - 82 * a7 b7 + d2 := e.fp.Eval([][]*baseEl{{a1, b1}, {mone, a7, b7}}, []int{1, 82}) + + // d3 = b3 + a3 + d3 := e.fp.Add(a3, b3) + + // d4 = a1 b3 + a3 b1 - 82 * (a7 b9 + a9 b7) + d4 := e.fp.Eval([][]*baseEl{{a1, b3}, {a3, b1}, {mone, a7, b9}, {mone, a9, b7}}, []int{1, 1, 82, 82}) + + // d6 = a3 b3 + 18 * (a3 b9 + a9 b3) + 242 * a9 b9 + d6 := e.fp.Eval([][]*baseEl{{a3, b3}, {a3, b9}, {a9, b3}, {a9, b9}}, []int{1, 18, 18, 242}) + + // d7 = b7 + a7 + d7 := e.fp.Add(a7, b7) + + // d8 = a1 b7 + a7 b1 + 18 * a7 b7 + d8 := e.fp.Eval([][]*baseEl{{a1, b7}, {a7, b1}, {a7, b7}}, []int{1, 1, 18}) + + // d9 = b9 + a9 + d9 := e.fp.Add(a9, b9) + + // d10 = a3 b7 + a1 b9 + a7 b3 + a9 b1 + 18 * (a7 b9 + a9 b7) + d10 := e.fp.Eval([][]*baseEl{{a3, b7}, {a1, b9}, {a7, b3}, {a9, b1}, {a7, b9}, {a9, b7}}, []int{1, 1, 1, 1, 18, 18}) + + return [10]*baseEl{d0, d1, d2, d3, d4, d6, d7, d8, d9, d10} } -// InverseTorus inverses a compressed elements y ∈ E6 -// and returns -y -func (e Ext12) InverseTorus(y *E6) *E6 { - return e.Ext6.Neg(y) -} - -// SquareTorus squares a compressed elements y ∈ E6 -// and returns (y + v/y)/2 +// MulBy012346789 multiplies a by an E12 sparse element b of the form // -// It uses a hint to verify that (2x-y)y = v saving one E6 AssertIsEqual. -func (e Ext12) SquareTorus(y *E6) *E6 { - res, err := e.fp.NewHint(squareTorusHint, 6, &y.B0.A0, &y.B0.A1, &y.B1.A0, &y.B1.A1, &y.B2.A0, &y.B2.A1) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } +// b.A0 = b[0] +// b.A1 = b[1] +// b.A2 = b[2] +// b.A3 = b[3] +// b.A4 = b[4] +// b.A5 = 0 +// b.A6 = b[5] +// b.A7 = b[6] +// b.A8 = b[7] +// b.A9 = b[8] +// b.A10 = b[9] +// b.A11 = 0 +func (e *Ext12) MulBy012346789(a *E12, b [10]*baseEl) *E12 { + // d0 = a0 b0 - 82 * (a2 b10 + a3 b9 + a4 b8 + a5 b7 + a6 b6 + a8 b4 + a9 b3 + a10 b2 + a11 b1) - 1476 * (a8 b10 + a9 b9 + a10 b8 + a11 b7) + mone := e.fp.NewElement(-1) + d0 := e.fp.Eval([][]*baseEl{{&a.A0, b[0]}, {mone, &a.A2, b[9]}, {mone, &a.A3, b[8]}, {mone, &a.A4, b[7]}, {mone, &a.A5, b[6]}, {mone, &a.A6, b[5]}, {mone, &a.A8, b[4]}, {mone, &a.A9, b[3]}, {mone, &a.A10, b[2]}, {mone, &a.A11, b[1]}, {mone, &a.A8, b[9]}, {mone, &a.A9, b[8]}, {mone, &a.A10, b[7]}, {mone, &a.A11, b[6]}}, []int{1, 82, 82, 82, 82, 82, 82, 82, 82, 82, 1476, 1476, 1476, 1476}) - sq := E6{ - B0: E2{A0: *res[0], A1: *res[1]}, - B1: E2{A0: *res[2], A1: *res[3]}, - B2: E2{A0: *res[4], A1: *res[5]}, - } + // d1 = a0 b1 + a1 b0 - 82 * (a3 b10 + a4 b9 + a5 b8 + a6 b7 + a7 b6 + a9 b4 + a10 b3 + a11 b2) - 1476 * (a9 b10 + a10 b9 + a11 b8) + d1 := e.fp.Eval([][]*baseEl{{&a.A0, b[1]}, {&a.A1, b[0]}, {mone, &a.A3, b[9]}, {mone, &a.A4, b[8]}, {mone, &a.A5, b[7]}, {mone, &a.A6, b[6]}, {mone, &a.A7, b[5]}, {mone, &a.A9, b[4]}, {mone, &a.A10, b[3]}, {mone, &a.A11, b[2]}, {mone, &a.A9, b[9]}, {mone, &a.A10, b[8]}, {mone, &a.A11, b[7]}}, []int{1, 1, 82, 82, 82, 82, 82, 82, 82, 82, 1476, 1476, 1476}) - // v = (2x-y)y - v := e.Ext6.Double(&sq) - v = e.Ext6.Sub(v, y) - v = e.Ext6.Mul(v, y) + // d2 = a0 b2 + a1 b1 + a2 b0 - 82 * (a4 b10 + a5 b9 + a6 b8 + a7 b7 + a8 b6 + a10 b4 + a11 b3) - 1476 * (a10 b10 + a11 b9) + d2 := e.fp.Eval([][]*baseEl{{&a.A0, b[2]}, {&a.A1, b[1]}, {&a.A2, b[0]}, {mone, &a.A4, b[9]}, {mone, &a.A5, b[8]}, {mone, &a.A6, b[7]}, {mone, &a.A7, b[6]}, {mone, &a.A8, b[5]}, {mone, &a.A10, b[4]}, {mone, &a.A11, b[3]}, {mone, &a.A10, b[9]}, {mone, &a.A11, b[8]}}, []int{1, 1, 1, 82, 82, 82, 82, 82, 82, 82, 1476, 1476}) - _v := E6{B0: *e.Ext2.Zero(), B1: *e.Ext2.One(), B2: *e.Ext2.Zero()} - e.Ext6.AssertIsEqual(v, &_v) + // d3 = a0 b3 + a1 b2 + a2 b1 + a3 b0 - 82 * (a5 b10 + a6 b9 + a7 b8 + a8 b7 + a9 b6 + a11 b4) - 1476 * a11 b10 + d3 := e.fp.Eval([][]*baseEl{{&a.A0, b[3]}, {&a.A1, b[2]}, {&a.A2, b[1]}, {&a.A3, b[0]}, {mone, &a.A5, b[9]}, {mone, &a.A6, b[8]}, {mone, &a.A7, b[7]}, {mone, &a.A8, b[6]}, {mone, &a.A9, b[5]}, {mone, &a.A11, b[4]}, {mone, &a.A11, b[9]}}, []int{1, 1, 1, 1, 82, 82, 82, 82, 82, 82, 1476}) - return &sq + // d4 = a0 b4 + a1 b3 + a2 b2 + a3 b1 + a4 b0 - 82 * (a6 b10 + a7 b9 + a8 b8 + a9 b7 + a10 b6) + d4 := e.fp.Eval([][]*baseEl{{&a.A0, b[4]}, {&a.A1, b[3]}, {&a.A2, b[2]}, {&a.A3, b[1]}, {&a.A4, b[0]}, {mone, &a.A6, b[9]}, {mone, &a.A7, b[8]}, {mone, &a.A8, b[7]}, {mone, &a.A9, b[6]}, {mone, &a.A10, b[5]}}, []int{1, 1, 1, 1, 1, 82, 82, 82, 82, 82}) -} + // d5 = a1 b4 + a2 b3 + a3 b2 + a4 b1 + a5 b0 - 82 * (a7 b10 + a8 b9 + a9 b8 + a10 b7 + a11 b6) + d5 := e.fp.Eval([][]*baseEl{{&a.A1, b[4]}, {&a.A2, b[3]}, {&a.A3, b[2]}, {&a.A4, b[1]}, {&a.A5, b[0]}, {mone, &a.A7, b[9]}, {mone, &a.A8, b[8]}, {mone, &a.A9, b[7]}, {mone, &a.A10, b[6]}, {mone, &a.A11, b[5]}}, []int{1, 1, 1, 1, 1, 82, 82, 82, 82, 82}) -// FrobeniusTorus raises a compressed elements y ∈ E6 to the modulus p -// and returns y^p / v^((p-1)/2) -func (e Ext12) FrobeniusTorus(y *E6) *E6 { - t0 := e.Ext2.Conjugate(&y.B0) - t1 := e.Ext2.Conjugate(&y.B1) - t2 := e.Ext2.Conjugate(&y.B2) - t1 = e.Ext2.MulByNonResidue1Power2(t1) - t2 = e.Ext2.MulByNonResidue1Power4(t2) + // d6 = a0 b6 + a2 b4 + a3 b3 + a4 b2 + a5 b1 + a6 b0 + 18 * (a2 b10 + a3 b9 + a4 b8 + a5 b7 + a6 b6 + a8 b4 + a9 b3 + a10 b2 + a11 b1) + 242 * (a8 b10 + a9 b9 + a10 b8 + a11 b7) + d6 := e.fp.Eval([][]*baseEl{{&a.A0, b[5]}, {&a.A2, b[4]}, {&a.A3, b[3]}, {&a.A4, b[2]}, {&a.A5, b[1]}, {&a.A6, b[0]}, {&a.A2, b[9]}, {&a.A3, b[8]}, {&a.A4, b[7]}, {&a.A5, b[6]}, {&a.A6, b[5]}, {&a.A8, b[4]}, {&a.A9, b[3]}, {&a.A10, b[2]}, {&a.A11, b[1]}, {&a.A8, b[9]}, {&a.A9, b[8]}, {&a.A10, b[7]}, {&a.A11, b[6]}}, []int{1, 1, 1, 1, 1, 1, 18, 18, 18, 18, 18, 18, 18, 18, 18, 242, 242, 242, 242}) - v0 := E2{emulated.ValueOf[emulated.BN254Fp]("18566938241244942414004596690298913868373833782006617400804628704885040364344"), emulated.ValueOf[emulated.BN254Fp]("5722266937896532885780051958958348231143373700109372999374820235121374419868")} - res := &E6{B0: *t0, B1: *t1, B2: *t2} - res = e.Ext6.MulBy0(res, &v0) + // d7 == a0 b7 + a1 b6 + a3 b4 + a4 b3 + a5 b2 + a6 b1 + a7 b0 + 18 * (a3 b10 + a4 b9 + a5 b8 + a6 b7 + a7 b6 + a9 b4 + a10 b3 + a11 b2) + 242 * (a9 b10 + a10 b9 + a11 b8) + d7 := e.fp.Eval([][]*baseEl{{&a.A0, b[6]}, {&a.A1, b[5]}, {&a.A3, b[4]}, {&a.A4, b[3]}, {&a.A5, b[2]}, {&a.A6, b[1]}, {&a.A7, b[0]}, {&a.A3, b[9]}, {&a.A4, b[8]}, {&a.A5, b[7]}, {&a.A6, b[6]}, {&a.A7, b[5]}, {&a.A9, b[4]}, {&a.A10, b[3]}, {&a.A11, b[2]}, {&a.A9, b[9]}, {&a.A10, b[8]}, {&a.A11, b[7]}}, []int{1, 1, 1, 1, 1, 1, 1, 18, 18, 18, 18, 18, 18, 18, 18, 242, 242, 242}) - return res -} + // d8 = a0 b8 + a1 b7 + a2 b6 + a4 b4 + a5 b3 + a6 b2 + a7 b1 + a8 b0 + 18 * (a4 b10 + a5 b9 + a6 b8 + a7 b7 + a8 b6 + a10 b4 + a11 b3) + 242 * (a10 b10 + a11 b9) + d8 := e.fp.Eval([][]*baseEl{{&a.A0, b[7]}, {&a.A1, b[6]}, {&a.A2, b[5]}, {&a.A4, b[4]}, {&a.A5, b[3]}, {&a.A6, b[2]}, {&a.A7, b[1]}, {&a.A8, b[0]}, {&a.A4, b[9]}, {&a.A5, b[8]}, {&a.A6, b[7]}, {&a.A7, b[6]}, {&a.A8, b[5]}, {&a.A10, b[4]}, {&a.A11, b[3]}, {&a.A10, b[9]}, {&a.A11, b[8]}}, []int{1, 1, 1, 1, 1, 1, 1, 1, 18, 18, 18, 18, 18, 18, 18, 242, 242}) -// FrobeniusSquareTorus raises a compressed elements y ∈ E6 to the square modulus p^2 -// and returns y^(p^2) / v^((p^2-1)/2) -func (e Ext12) FrobeniusSquareTorus(y *E6) *E6 { - v0 := emulated.ValueOf[emulated.BN254Fp]("2203960485148121921418603742825762020974279258880205651967") - t0 := e.Ext2.MulByElement(&y.B0, &v0) - t1 := e.Ext2.MulByNonResidue2Power2(&y.B1) - t1 = e.Ext2.MulByElement(t1, &v0) - t2 := e.Ext2.MulByNonResidue2Power4(&y.B2) - t2 = e.Ext2.MulByElement(t2, &v0) - - return &E6{B0: *t0, B1: *t1, B2: *t2} -} + // d9 = a0 b9 + a1 b8 + a2 b7 + a3 b6 + a5 b4 + a6 b3 + a7 b2 + a8 b1 + a9 b0 + 18 * (a5 b10 + a6 b9 + a7 b8 + a8 b7 + a9 b6 + a11 b4) + 242 * a11 b10 + d9 := e.fp.Eval([][]*baseEl{{&a.A0, b[8]}, {&a.A1, b[7]}, {&a.A2, b[6]}, {&a.A3, b[5]}, {&a.A5, b[4]}, {&a.A6, b[3]}, {&a.A7, b[2]}, {&a.A8, b[1]}, {&a.A9, b[0]}, {&a.A5, b[9]}, {&a.A6, b[8]}, {&a.A7, b[7]}, {&a.A8, b[6]}, {&a.A9, b[5]}, {&a.A11, b[4]}, {&a.A11, b[9]}}, []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 18, 18, 18, 18, 18, 242}) -// FrobeniusCubeTorus raises a compressed elements y ∈ E6 to the cube modulus p^3 -// and returns y^(p^3) / v^((p^3-1)/2) -func (e Ext12) FrobeniusCubeTorus(y *E6) *E6 { - t0 := e.Ext2.Conjugate(&y.B0) - t1 := e.Ext2.Conjugate(&y.B1) - t2 := e.Ext2.Conjugate(&y.B2) - t1 = e.Ext2.MulByNonResidue3Power2(t1) - t2 = e.Ext2.MulByNonResidue3Power4(t2) + // d10 = a0 b10 + a1 b9 + a2 b8 + a3 b7 + a4 b6 + a6 b4 + a7 b3 + a8 b2 + a9 b1 + a10 b0 + 18 * (a6 b10 + a7 b9 + a8 b8 + a9 b7 + a10 b6) + d10 := e.fp.Eval([][]*baseEl{{&a.A0, b[9]}, {&a.A1, b[8]}, {&a.A2, b[7]}, {&a.A3, b[6]}, {&a.A4, b[5]}, {&a.A6, b[4]}, {&a.A7, b[3]}, {&a.A8, b[2]}, {&a.A9, b[1]}, {&a.A10, b[0]}, {&a.A6, b[9]}, {&a.A7, b[8]}, {&a.A8, b[7]}, {&a.A9, b[6]}, {&a.A10, b[5]}}, []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 18, 18, 18, 18}) - v0 := E2{emulated.ValueOf[emulated.BN254Fp]("10190819375481120917420622822672549775783927716138318623895010788866272024264"), emulated.ValueOf[emulated.BN254Fp]("303847389135065887422783454877609941456349188919719272345083954437860409601")} - res := &E6{B0: *t0, B1: *t1, B2: *t2} - res = e.Ext6.MulBy0(res, &v0) - - return res -} - -// AssertFinalExponentiationIsOne checks that a Miller function output x lies in the -// same equivalence class as the reduced pairing. This replaces the final -// exponentiation step in-circuit. -// The method follows Section 4 of [On Proving Pairings] paper by A. Novakovic and L. Eagen. -// -// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf -func (e Ext12) AssertFinalExponentiationIsOne(x *E12) { - res, err := e.fp.NewHint(finalExpHint, 24, &x.C0.B0.A0, &x.C0.B0.A1, &x.C0.B1.A0, &x.C0.B1.A1, &x.C0.B2.A0, &x.C0.B2.A1, &x.C1.B0.A0, &x.C1.B0.A1, &x.C1.B1.A0, &x.C1.B1.A1, &x.C1.B2.A0, &x.C1.B2.A1) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - - residueWitness := E12{ - C0: E6{ - B0: E2{A0: *res[0], A1: *res[1]}, - B1: E2{A0: *res[2], A1: *res[3]}, - B2: E2{A0: *res[4], A1: *res[5]}, - }, - C1: E6{ - B0: E2{A0: *res[6], A1: *res[7]}, - B1: E2{A0: *res[8], A1: *res[9]}, - B2: E2{A0: *res[10], A1: *res[11]}, - }, - } - // constrain cubicNonResiduePower to be in Fp6 - cubicNonResiduePower := E6{ - B0: E2{A0: *res[12], A1: *res[13]}, - B1: E2{A0: *res[14], A1: *res[15]}, - B2: E2{A0: *res[16], A1: *res[17]}, - } - - // Check that x * cubicNonResiduePower == residueWitness^λ - // where λ = 6u + 2 + q^3 - q^2 + q, with u the BN254 seed - // and residueWitness, cubicNonResiduePower from the hint. - t2 := &E12{ - C0: *e.Ext6.Mul(&x.C0, &cubicNonResiduePower), - C1: *e.Ext6.Mul(&x.C1, &cubicNonResiduePower), - } - - t1 := e.FrobeniusCube(&residueWitness) - t0 := e.FrobeniusSquare(&residueWitness) - t1 = e.DivUnchecked(t1, t0) - t0 = e.Frobenius(&residueWitness) - t1 = e.Mul(t1, t0) - - // exponentiation by U=6u+2 - t0 = e.ExpByU(&residueWitness) - - t0 = e.Mul(t0, t1) - - e.AssertIsEqual(t0, t2) -} - -func (e Ext12) Frobenius(x *E12) *E12 { - t0 := e.Ext2.Conjugate(&x.C0.B0) - t1 := e.Ext2.Conjugate(&x.C0.B1) - t2 := e.Ext2.Conjugate(&x.C0.B2) - t3 := e.Ext2.Conjugate(&x.C1.B0) - t4 := e.Ext2.Conjugate(&x.C1.B1) - t5 := e.Ext2.Conjugate(&x.C1.B2) - t1 = e.Ext2.MulByNonResidue1Power2(t1) - t2 = e.Ext2.MulByNonResidue1Power4(t2) - t3 = e.Ext2.MulByNonResidue1Power1(t3) - t4 = e.Ext2.MulByNonResidue1Power3(t4) - t5 = e.Ext2.MulByNonResidue1Power5(t5) - return &E12{ - C0: E6{ - B0: *t0, - B1: *t1, - B2: *t2, - }, - C1: E6{ - B0: *t3, - B1: *t4, - B2: *t5, - }, - } -} - -func (e Ext12) FrobeniusSquare(x *E12) *E12 { - z00 := &x.C0.B0 - z01 := e.Ext2.MulByNonResidue2Power2(&x.C0.B1) - z02 := e.Ext2.MulByNonResidue2Power4(&x.C0.B2) - z10 := e.Ext2.MulByNonResidue2Power1(&x.C1.B0) - z11 := e.Ext2.MulByNonResidue2Power3(&x.C1.B1) - z12 := e.Ext2.MulByNonResidue2Power5(&x.C1.B2) - return &E12{ - C0: E6{B0: *z00, B1: *z01, B2: *z02}, - C1: E6{B0: *z10, B1: *z11, B2: *z12}, - } -} + // d11 = a1 b10 + a2 b9 + a3 b8 + a4 b7 + a5 b6 + a7 b4 + a8 b3 + a9 b2 + a10 b1 + a11 b0 + 18 * (a7 b10 + a8 b9 + a9 b8 + a10 b7 + a11 b6) + d11 := e.fp.Eval([][]*baseEl{{&a.A1, b[9]}, {&a.A2, b[8]}, {&a.A3, b[7]}, {&a.A4, b[6]}, {&a.A5, b[5]}, {&a.A7, b[4]}, {&a.A8, b[3]}, {&a.A9, b[2]}, {&a.A10, b[1]}, {&a.A11, b[0]}, {&a.A7, b[9]}, {&a.A8, b[8]}, {&a.A9, b[7]}, {&a.A10, b[6]}, {&a.A11, b[5]}}, []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 18, 18, 18, 18}) -func (e Ext12) FrobeniusCube(x *E12) *E12 { - t0 := e.Ext2.Conjugate(&x.C0.B0) - t1 := e.Ext2.Conjugate(&x.C0.B1) - t2 := e.Ext2.Conjugate(&x.C0.B2) - t3 := e.Ext2.Conjugate(&x.C1.B0) - t4 := e.Ext2.Conjugate(&x.C1.B1) - t5 := e.Ext2.Conjugate(&x.C1.B2) - t1 = e.Ext2.MulByNonResidue3Power2(t1) - t2 = e.Ext2.MulByNonResidue3Power4(t2) - t3 = e.Ext2.MulByNonResidue3Power1(t3) - t4 = e.Ext2.MulByNonResidue3Power3(t4) - t5 = e.Ext2.MulByNonResidue3Power5(t5) return &E12{ - C0: E6{ - B0: *t0, - B1: *t1, - B2: *t2, - }, - C1: E6{ - B0: *t3, - B1: *t4, - B2: *t5, - }, + A0: *d0, + A1: *d1, + A2: *d2, + A3: *d3, + A4: *d4, + A5: *d5, + A6: *d6, + A7: *d7, + A8: *d8, + A9: *d9, + A10: *d10, + A11: *d11, } } diff --git a/std/algebra/emulated/fields_bn254/e12_test.go b/std/algebra/emulated/fields_bn254/e12_test.go index a3289b469..b2f0ae68a 100644 --- a/std/algebra/emulated/fields_bn254/e12_test.go +++ b/std/algebra/emulated/fields_bn254/e12_test.go @@ -9,6 +9,34 @@ import ( "github.com/consensys/gnark/test" ) +type e12Convert struct { + A E12 +} + +func (circuit *e12Convert) Define(api frontend.API) error { + e := NewExt12(api) + tower := e.ToTower(&circuit.A) + expected := e.FromTower(tower) + e.AssertIsEqual(expected, &circuit.A) + return nil +} + +func TestConvertFp12(t *testing.T) { + + assert := test.NewAssert(t) + // witness values + var a bn254.E12 + _, _ = a.SetRandom() + + witness := e12Convert{ + A: FromE12(&a), + } + + err := test.IsSolved(&e12Convert{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + +} + type e12Add struct { A, B, C E12 } @@ -162,81 +190,18 @@ func TestSquareFp12(t *testing.T) { } -type e12Conjugate struct { - A E12 - C E12 `gnark:",public"` -} - -func (circuit *e12Conjugate) Define(api frontend.API) error { - e := NewExt12(api) - expected := e.Conjugate(&circuit.A) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestConjugateFp12(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bn254.E12 - _, _ = a.SetRandom() - c.Conjugate(&a) - - witness := e12Conjugate{ - A: FromE12(&a), - C: FromE12(&c), - } - - err := test.IsSolved(&e12Conjugate{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) -} - -type e12Inverse struct { - A E12 - C E12 `gnark:",public"` -} - -func (circuit *e12Inverse) Define(api frontend.API) error { - e := NewExt12(api) - expected := e.Inverse(&circuit.A) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestInverseFp12(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bn254.E12 - _, _ = a.SetRandom() - c.Inverse(&a) - - witness := e12Inverse{ - A: FromE12(&a), - C: FromE12(&c), - } - - err := test.IsSolved(&e12Inverse{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) -} - -type e12ExptTorus struct { - A E6 - C E12 `gnark:",public"` +type e12SquareGS struct { + A, C E12 } -func (circuit *e12ExptTorus) Define(api frontend.API) error { +func (circuit *e12SquareGS) Define(api frontend.API) error { e := NewExt12(api) - z := e.ExptTorus(&circuit.A) - expected := e.DecompressTorus(z) + expected := e.CyclotomicSquareGS(&circuit.A) e.AssertIsEqual(expected, &circuit.C) - return nil } -func TestFp12ExptTorus(t *testing.T) { +func TestSquareGSFp12(t *testing.T) { assert := test.NewAssert(t) // witness values @@ -250,371 +215,316 @@ func TestFp12ExptTorus(t *testing.T) { tmp.Mul(&tmp, &a) a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - c.Expt(&a) - _a, _ := a.CompressTorus() - witness := e12ExptTorus{ - A: FromE6(&_a), - C: FromE12(&c), - } - - err := test.IsSolved(&e12ExptTorus{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) -} - -type e12MulBy034 struct { - A E12 `gnark:",public"` - W E12 - B, C E2 -} - -func (circuit *e12MulBy034) Define(api frontend.API) error { - e := NewExt12(api) - res := e.MulBy034(&circuit.A, &circuit.B, &circuit.C) - e.AssertIsEqual(res, &circuit.W) - return nil -} - -func TestFp12MulBy034(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, w bn254.E12 - _, _ = a.SetRandom() - var one, b, c bn254.E2 - one.SetOne() - _, _ = b.SetRandom() - _, _ = c.SetRandom() - w.Set(&a) - w.MulBy034(&one, &b, &c) + c.Square(&a) - witness := e12MulBy034{ + witness := e12SquareGS{ A: FromE12(&a), - B: FromE2(&b), - C: FromE2(&c), - W: FromE12(&w), + C: FromE12(&c), } - err := test.IsSolved(&e12MulBy034{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&e12SquareGS{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } -// Torus-based arithmetic -type torusCompress struct { +type e12Conjugate struct { A E12 - C E6 `gnark:",public"` + C E12 `gnark:",public"` } -func (circuit *torusCompress) Define(api frontend.API) error { +func (circuit *e12Conjugate) Define(api frontend.API) error { e := NewExt12(api) - expected := e.CompressTorus(&circuit.A) - e.Ext6.AssertIsEqual(expected, &circuit.C) + expected := e.Conjugate(&circuit.A) + e.AssertIsEqual(expected, &circuit.C) + return nil } -func TestTorusCompress(t *testing.T) { +func TestConjugateFp12(t *testing.T) { assert := test.NewAssert(t) // witness values - var a bn254.E12 + var a, c bn254.E12 _, _ = a.SetRandom() + c.Conjugate(&a) - // put a in the cyclotomic subgroup - var tmp bn254.E12 - tmp.Conjugate(&a) - a.Inverse(&a) - tmp.Mul(&tmp, &a) - a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - - c, _ := a.CompressTorus() - - witness := torusCompress{ + witness := e12Conjugate{ A: FromE12(&a), - C: FromE6(&c), + C: FromE12(&c), } - err := test.IsSolved(&torusCompress{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&e12Conjugate{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } -type torusDecompress struct { +type e12Inverse struct { A E12 C E12 `gnark:",public"` } -func (circuit *torusDecompress) Define(api frontend.API) error { +func (circuit *e12Inverse) Define(api frontend.API) error { e := NewExt12(api) - compressed := e.CompressTorus(&circuit.A) - expected := e.DecompressTorus(compressed) + expected := e.Inverse(&circuit.A) e.AssertIsEqual(expected, &circuit.C) + return nil } -func TestTorusDecompress(t *testing.T) { +func TestInverseFp12(t *testing.T) { assert := test.NewAssert(t) // witness values - var a bn254.E12 + var a, c bn254.E12 _, _ = a.SetRandom() + c.Inverse(&a) - // put a in the cyclotomic subgroup - var tmp bn254.E12 - tmp.Conjugate(&a) - a.Inverse(&a) - tmp.Mul(&tmp, &a) - a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - - d, _ := a.CompressTorus() - c := d.DecompressTorus() - - witness := torusDecompress{ + witness := e12Inverse{ A: FromE12(&a), C: FromE12(&c), } - err := test.IsSolved(&torusDecompress{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&e12Inverse{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } -type torusMul struct { +type Frobenius struct { A E12 - B E12 C E12 `gnark:",public"` } -func (circuit *torusMul) Define(api frontend.API) error { +func (circuit *Frobenius) Define(api frontend.API) error { e := NewExt12(api) - compressedA := e.CompressTorus(&circuit.A) - compressedB := e.CompressTorus(&circuit.B) - compressedAB := e.MulTorus(compressedA, compressedB) - expected := e.DecompressTorus(compressedAB) + expected := e.Frobenius(&circuit.A) e.AssertIsEqual(expected, &circuit.C) return nil } -func TestTorusMul(t *testing.T) { +func TestFrobenius(t *testing.T) { assert := test.NewAssert(t) // witness values - var a, b, c, tmp bn254.E12 + var a, c bn254.E12 _, _ = a.SetRandom() - _, _ = b.SetRandom() - // put a in the cyclotomic subgroup - tmp.Conjugate(&a) - a.Inverse(&a) - tmp.Mul(&tmp, &a) - a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - // put b in the cyclotomic subgroup - tmp.Conjugate(&b) - b.Inverse(&b) - tmp.Mul(&tmp, &b) - b.FrobeniusSquare(&tmp).Mul(&b, &tmp) - - // uncompressed mul - c.Mul(&a, &b) + c.Frobenius(&a) - witness := torusMul{ + witness := Frobenius{ A: FromE12(&a), - B: FromE12(&b), C: FromE12(&c), } - err := test.IsSolved(&torusMul{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&Frobenius{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } -type torusInverse struct { +type FrobeniusSquare struct { A E12 C E12 `gnark:",public"` } -func (circuit *torusInverse) Define(api frontend.API) error { +func (circuit *FrobeniusSquare) Define(api frontend.API) error { e := NewExt12(api) - compressed := e.CompressTorus(&circuit.A) - compressed = e.InverseTorus(compressed) - expected := e.DecompressTorus(compressed) + expected := e.FrobeniusSquare(&circuit.A) e.AssertIsEqual(expected, &circuit.C) return nil } -func TestTorusInverse(t *testing.T) { +func TestFrobeniusSquare(t *testing.T) { assert := test.NewAssert(t) // witness values - var a, c, tmp bn254.E12 + var a, c bn254.E12 _, _ = a.SetRandom() - // put a in the cyclotomic subgroup - tmp.Conjugate(&a) - a.Inverse(&a) - tmp.Mul(&tmp, &a) - a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - - // uncompressed inverse - c.Inverse(&a) + c.FrobeniusSquare(&a) - witness := torusInverse{ + witness := FrobeniusSquare{ A: FromE12(&a), C: FromE12(&c), } - err := test.IsSolved(&torusInverse{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&FrobeniusSquare{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } -type torusFrobenius struct { +type FrobeniusCube struct { A E12 C E12 `gnark:",public"` } -func (circuit *torusFrobenius) Define(api frontend.API) error { +func (circuit *FrobeniusCube) Define(api frontend.API) error { e := NewExt12(api) - compressed := e.CompressTorus(&circuit.A) - compressed = e.FrobeniusTorus(compressed) - expected := e.DecompressTorus(compressed) + expected := e.FrobeniusCube(&circuit.A) e.AssertIsEqual(expected, &circuit.C) return nil } -func TestTorusFrobenius(t *testing.T) { +func TestFrobeniusCube(t *testing.T) { assert := test.NewAssert(t) // witness values - var a, c, tmp bn254.E12 + var a, c bn254.E12 _, _ = a.SetRandom() - // put a in the cyclotomic subgroup - tmp.Conjugate(&a) - a.Inverse(&a) - tmp.Mul(&tmp, &a) - a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - - // uncompressed frobenius - c.Frobenius(&a) + c.FrobeniusCube(&a) - witness := torusFrobenius{ + witness := FrobeniusCube{ A: FromE12(&a), C: FromE12(&c), } - err := test.IsSolved(&torusFrobenius{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&FrobeniusCube{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } -type torusFrobeniusSquare struct { - A E12 - C E12 `gnark:",public"` +type e12MulBy01379 struct { + A E12 `gnark:",public"` + W E12 + B, C E2 } -func (circuit *torusFrobeniusSquare) Define(api frontend.API) error { +func (circuit *e12MulBy01379) Define(api frontend.API) error { e := NewExt12(api) - compressed := e.CompressTorus(&circuit.A) - compressed = e.FrobeniusSquareTorus(compressed) - expected := e.DecompressTorus(compressed) - e.AssertIsEqual(expected, &circuit.C) + res := e.MulBy01379(&circuit.A, &circuit.B, &circuit.C) + e.AssertIsEqual(res, &circuit.W) return nil } -func TestTorusFrobeniusSquare(t *testing.T) { +func TestFp12MulBy01379(t *testing.T) { assert := test.NewAssert(t) // witness values - var a, c, tmp bn254.E12 + var a, w bn254.E12 _, _ = a.SetRandom() + var one, b, c bn254.E2 + one.SetOne() + _, _ = b.SetRandom() + _, _ = c.SetRandom() + w.Set(&a) + w.MulBy034(&one, &b, &c) - // put a in the cyclotomic subgroup - tmp.Conjugate(&a) - a.Inverse(&a) - tmp.Mul(&tmp, &a) - a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - - // uncompressed frobeniusSquare - c.FrobeniusSquare(&a) - - witness := torusFrobeniusSquare{ + witness := e12MulBy01379{ A: FromE12(&a), - C: FromE12(&c), + B: FromE2(&b), + C: FromE2(&c), + W: FromE12(&w), } - err := test.IsSolved(&torusFrobeniusSquare{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&e12MulBy01379{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) + } -type torusFrobeniusCube struct { - A E12 - C E12 `gnark:",public"` +type e12Mul01379By01379 struct { + A, B E2 `gnark:",public"` + C, D E2 `gnark:",public"` + W E12 } -func (circuit *torusFrobeniusCube) Define(api frontend.API) error { +func (circuit *e12Mul01379By01379) Define(api frontend.API) error { e := NewExt12(api) - compressed := e.CompressTorus(&circuit.A) - compressed = e.FrobeniusCubeTorus(compressed) - expected := e.DecompressTorus(compressed) - e.AssertIsEqual(expected, &circuit.C) + res := e.Mul01379By01379(&circuit.A, &circuit.B, &circuit.C, &circuit.D) + e.AssertIsEqual( + &E12{*res[0], *res[1], *res[2], *res[3], *res[4], *e.fp.Zero(), *res[5], *res[6], *res[7], *res[8], *res[9], *e.fp.Zero()}, + &circuit.W, + ) return nil } -func TestTorusFrobeniusCube(t *testing.T) { +func TestFp12Mul01379By01379(t *testing.T) { assert := test.NewAssert(t) // witness values - var a, c, tmp bn254.E12 + var one, a, b, c, d bn254.E2 + one.SetOne() _, _ = a.SetRandom() - - // put a in the cyclotomic subgroup - tmp.Conjugate(&a) - a.Inverse(&a) - tmp.Mul(&tmp, &a) - a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - - // uncompressed frobeniusCube - c.FrobeniusCube(&a) - - witness := torusFrobeniusCube{ - A: FromE12(&a), - C: FromE12(&c), + _, _ = b.SetRandom() + _, _ = c.SetRandom() + _, _ = d.SetRandom() + prod := Mul034By034(&one, &a, &b, &one, &c, &d) + var w bn254.E12 + w.C0.B0.Set(&prod[0]) + w.C0.B1.Set(&prod[1]) + w.C0.B2.Set(&prod[2]) + w.C1.B0.Set(&prod[3]) + w.C1.B1.Set(&prod[4]) + w.C1.B2.SetZero() + + witness := e12Mul01379By01379{ + A: FromE2(&a), + B: FromE2(&b), + C: FromE2(&c), + D: FromE2(&d), + W: FromE12(&w), } - err := test.IsSolved(&torusFrobeniusCube{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&e12Mul01379By01379{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) + } -type torusSquare struct { +type e12Expt struct { A E12 C E12 `gnark:",public"` } -func (circuit *torusSquare) Define(api frontend.API) error { +func (circuit *e12Expt) Define(api frontend.API) error { e := NewExt12(api) - compressed := e.CompressTorus(&circuit.A) - compressed = e.SquareTorus(compressed) - expected := e.DecompressTorus(compressed) + expected := e.Expt(&circuit.A) e.AssertIsEqual(expected, &circuit.C) + return nil } -func TestTorusSquare(t *testing.T) { +func TestFp12Expt(t *testing.T) { assert := test.NewAssert(t) // witness values - var a, c, tmp bn254.E12 + var a, c bn254.E12 _, _ = a.SetRandom() // put a in the cyclotomic subgroup + var tmp bn254.E12 tmp.Conjugate(&a) a.Inverse(&a) tmp.Mul(&tmp, &a) a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - // uncompressed square - c.Square(&a) - - witness := torusSquare{ + c.Expt(&a) + witness := e12Expt{ A: FromE12(&a), C: FromE12(&c), } - err := test.IsSolved(&torusSquare{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&e12Expt{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } + +// utils +// Mul034By034 multiplication of sparse element (c0,0,0,c3,c4,0) by sparse element (d0,0,0,d3,d4,0) +func Mul034By034(d0, d3, d4, c0, c3, c4 *bn254.E2) [5]bn254.E2 { + var z00, tmp, x0, x3, x4, x04, x03, x34 bn254.E2 + x0.Mul(c0, d0) + x3.Mul(c3, d3) + x4.Mul(c4, d4) + tmp.Add(c0, c4) + x04.Add(d0, d4). + Mul(&x04, &tmp). + Sub(&x04, &x0). + Sub(&x04, &x4) + tmp.Add(c0, c3) + x03.Add(d0, d3). + Mul(&x03, &tmp). + Sub(&x03, &x0). + Sub(&x03, &x3) + tmp.Add(c3, c4) + x34.Add(d3, d4). + Mul(&x34, &tmp). + Sub(&x34, &x3). + Sub(&x34, &x4) + + z00.MulByNonResidue(&x4). + Add(&z00, &x0) + + return [5]bn254.E2{z00, x3, x34, x03, x04} +} diff --git a/std/algebra/emulated/fields_bn254/e2.go b/std/algebra/emulated/fields_bn254/e2.go index 5d38c50ea..7af33cbd8 100644 --- a/std/algebra/emulated/fields_bn254/e2.go +++ b/std/algebra/emulated/fields_bn254/e2.go @@ -201,16 +201,10 @@ func (e Ext2) MulByNonResidue3Power5(x *E2) *E2 { } func (e Ext2) Mul(x, y *E2) *E2 { - - v0 := e.fp.Mul(&x.A0, &y.A0) - v1 := e.fp.Mul(&x.A1, &y.A1) - - b0 := e.fp.Sub(v0, v1) - b1 := e.fp.Add(&x.A0, &x.A1) - tmp := e.fp.Add(&y.A0, &y.A1) - b1 = e.fp.Mul(b1, tmp) - tmp = e.fp.Add(v0, v1) - b1 = e.fp.Sub(b1, tmp) + // b0 = x0*y0 - x1*y1 + b0 := e.fp.Eval([][]*baseEl{{&x.A0, &y.A0}, {e.fp.NewElement(-1), &x.A1, &y.A1}}, []int{1, 1}) + // b1 = x0*y1 + x1*y0 + b1 := e.fp.Eval([][]*baseEl{{&x.A0, &y.A1}, {&x.A1, &y.A0}}, []int{1, 1}) return &E2{ A0: *b0, @@ -270,11 +264,22 @@ func (e Ext2) IsZero(z *E2) frontend.Variable { } func (e Ext2) Square(x *E2) *E2 { - a := e.fp.Add(&x.A0, &x.A1) - b := e.fp.Sub(&x.A0, &x.A1) - a = e.fp.Mul(a, b) - b = e.fp.Mul(&x.A0, &x.A1) - b = e.fp.MulConst(b, big.NewInt(2)) + // a = (x0+x1)(x0-x1) = x0^2 - x1^2 + a := e.fp.Eval([][]*baseEl{{&x.A0, &x.A0}, {e.fp.NewElement(-1), &x.A1, &x.A1}}, []int{1, 1}) + // b = 2*x0*x1 + b := e.fp.Eval([][]*baseEl{{&x.A0, &x.A1}}, []int{2}) + return &E2{ + A0: *a, + A1: *b, + } +} + +func (e Ext2) Cube(x *E2) *E2 { + mone := e.fp.NewElement(-1) + // a = x0^3 - 3*x0*x1^2 + a := e.fp.Eval([][]*baseEl{{&x.A0, &x.A0, &x.A0}, {mone, &x.A0, &x.A1, &x.A1}}, []int{1, 3}) + // b = 3*x1*x0^2 - x1^3 + b := e.fp.Eval([][]*baseEl{{&x.A1, &x.A0, &x.A0}, {mone, &x.A1, &x.A1, &x.A1}}, []int{3, 1}) return &E2{ A0: *a, A1: *b, diff --git a/std/algebra/emulated/fields_bn254/e6.go b/std/algebra/emulated/fields_bn254/e6.go deleted file mode 100644 index feca59a5f..000000000 --- a/std/algebra/emulated/fields_bn254/e6.go +++ /dev/null @@ -1,495 +0,0 @@ -package fields_bn254 - -import ( - "math/big" - - "github.com/consensys/gnark-crypto/ecc/bn254" - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/internal/frontendtype" -) - -type E6 struct { - B0, B1, B2 E2 -} - -type Ext6 struct { - *Ext2 -} - -func NewExt6(api frontend.API) *Ext6 { - return &Ext6{Ext2: NewExt2(api)} -} - -func (e Ext6) One() *E6 { - z0 := e.Ext2.One() - z1 := e.Ext2.Zero() - z2 := e.Ext2.Zero() - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) Zero() *E6 { - z0 := e.Ext2.Zero() - z1 := e.Ext2.Zero() - z2 := e.Ext2.Zero() - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) IsZero(z *E6) frontend.Variable { - b0 := e.Ext2.IsZero(&z.B0) - b1 := e.Ext2.IsZero(&z.B1) - b2 := e.Ext2.IsZero(&z.B2) - return e.api.And(e.api.And(b0, b1), b2) -} - -func (e Ext6) Add(x, y *E6) *E6 { - z0 := e.Ext2.Add(&x.B0, &y.B0) - z1 := e.Ext2.Add(&x.B1, &y.B1) - z2 := e.Ext2.Add(&x.B2, &y.B2) - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) Neg(x *E6) *E6 { - z0 := e.Ext2.Neg(&x.B0) - z1 := e.Ext2.Neg(&x.B1) - z2 := e.Ext2.Neg(&x.B2) - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) Sub(x, y *E6) *E6 { - z0 := e.Ext2.Sub(&x.B0, &y.B0) - z1 := e.Ext2.Sub(&x.B1, &y.B1) - z2 := e.Ext2.Sub(&x.B2, &y.B2) - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -// Mul multiplies two E6 elmts -func (e Ext6) Mul(x, y *E6) *E6 { - if ft, ok := e.api.(frontendtype.FrontendTyper); ok { - switch ft.FrontendType() { - case frontendtype.R1CS: - return e.mulToom3OverKaratsuba(x, y) - case frontendtype.SCS: - return e.mulKaratsubaOverKaratsuba(x, y) - } - } - return e.mulKaratsubaOverKaratsuba(x, y) -} - -func (e Ext6) mulToom3OverKaratsuba(x, y *E6) *E6 { - // Toom-Cook-3x over Karatsuba: - // We start by computing five interpolation points – these are evaluations of - // the product x(u)y(u) with u ∈ {0, ±1, 2, ∞}: - // - // v0 = x(0)y(0) = x.A0 * y.A0 - // v1 = x(1)y(1) = (x.A0 + x.A1 + x.A2)(y.A0 + y.A1 + y.A2) - // v2 = x(−1)y(−1) = (x.A0 − x.A1 + x.A2)(y.A0 − y.A1 + y.A2) - // v3 = x(2)y(2) = (x.A0 + 2x.A1 + 4x.A2)(y.A0 + 2y.A1 + 4y.A2) - // v4 = x(∞)y(∞) = x.A2 * y.A2 - - v0 := e.Ext2.Mul(&x.B0, &y.B0) - - t1 := e.Ext2.Add(&x.B0, &x.B2) - t2 := e.Ext2.Add(&y.B0, &y.B2) - t3 := e.Ext2.Add(t2, &y.B1) - v1 := e.Ext2.Add(t1, &x.B1) - v1 = e.Ext2.Mul(v1, t3) - - t3 = e.Ext2.Sub(t2, &y.B1) - v2 := e.Ext2.Sub(t1, &x.B1) - v2 = e.Ext2.Mul(v2, t3) - - t1 = e.Ext2.MulByConstElement(&x.B1, big.NewInt(2)) - t2 = e.Ext2.MulByConstElement(&x.B2, big.NewInt(4)) - v3 := e.Ext2.Add(t1, t2) - v3 = e.Ext2.Add(v3, &x.B0) - t1 = e.Ext2.MulByConstElement(&y.B1, big.NewInt(2)) - t2 = e.Ext2.MulByConstElement(&y.B2, big.NewInt(4)) - t3 = e.Ext2.Add(t1, t2) - t3 = e.Ext2.Add(t3, &y.B0) - v3 = e.Ext2.Mul(v3, t3) - - v4 := e.Ext2.Mul(&x.B2, &y.B2) - - // Then the interpolation is performed as: - // - // a0 = v0 + β((1/2)v0 − (1/2)v1 − (1/6)v2 + (1/6)v3 − 2v4) - // a1 = −(1/2)v0 + v1 − (1/3)v2 − (1/6)v3 + 2v4 + βv4 - // a2 = −v0 + (1/2)v1 + (1/2)v2 − v4 - // - // where β is the cubic non-residue. - // - // In-circuit, we compute 6*x*y as - // c0 = 6v0 + β(3v0 − 3v1 − v2 + v3 − 12v4) - // a1 = -(3v0 + 2v2 + v3) + 6(v1 + 2v4 + βv4) - // a2 = 3(v1 + v2 - 2(v0 + v4)) - // - // and then divide a0, a1 and a2 by 6 using a hint. - - a0 := e.Ext2.MulByConstElement(v0, big.NewInt(6)) - t1 = e.Ext2.Sub(v0, v1) - t1 = e.Ext2.MulByConstElement(t1, big.NewInt(3)) - t1 = e.Ext2.Sub(t1, v2) - t1 = e.Ext2.Add(t1, v3) - t2 = e.Ext2.MulByConstElement(v4, big.NewInt(12)) - t1 = e.Ext2.Sub(t1, t2) - t1 = e.Ext2.MulByNonResidue(t1) - a0 = e.Ext2.Add(a0, t1) - - a1 := e.Ext2.MulByConstElement(v0, big.NewInt(3)) - t1 = e.Ext2.MulByConstElement(v2, big.NewInt(2)) - a1 = e.Ext2.Add(a1, t1) - a1 = e.Ext2.Add(a1, v3) - t1 = e.Ext2.MulByConstElement(v4, big.NewInt(2)) - t1 = e.Ext2.Add(t1, v1) - t2 = e.Ext2.MulByNonResidue(v4) - t1 = e.Ext2.Add(t1, t2) - t1 = e.Ext2.MulByConstElement(t1, big.NewInt(6)) - a1 = e.Ext2.Sub(t1, a1) - - a2 := e.Ext2.Add(v1, v2) - a2 = e.Ext2.MulByConstElement(a2, big.NewInt(3)) - t1 = e.Ext2.Add(v0, v4) - t1 = e.Ext2.MulByConstElement(t1, big.NewInt(6)) - a2 = e.Ext2.Sub(a2, t1) - - res := e.divE6By6([6]*baseEl{&a0.A0, &a0.A1, &a1.A0, &a1.A1, &a2.A0, &a2.A1}) - return &E6{ - B0: E2{ - A0: *res[0], - A1: *res[1], - }, - B1: E2{ - A0: *res[2], - A1: *res[3], - }, - B2: E2{ - A0: *res[4], - A1: *res[5], - }, - } -} - -func (e Ext6) mulKaratsubaOverKaratsuba(x, y *E6) *E6 { - // Karatsuba over Karatsuba: - // Algorithm 13 from https://eprint.iacr.org/2010/354.pdf - t0 := e.Ext2.Mul(&x.B0, &y.B0) - t1 := e.Ext2.Mul(&x.B1, &y.B1) - t2 := e.Ext2.Mul(&x.B2, &y.B2) - c0 := e.Ext2.Add(&x.B1, &x.B2) - tmp := e.Ext2.Add(&y.B1, &y.B2) - c0 = e.Ext2.Mul(c0, tmp) - tmp = e.Ext2.Add(t2, t1) - c0 = e.Ext2.Sub(c0, tmp) - c0 = e.Ext2.MulByNonResidue(c0) - c0 = e.Ext2.Add(c0, t0) - c1 := e.Ext2.Add(&x.B0, &x.B1) - tmp = e.Ext2.Add(&y.B0, &y.B1) - c1 = e.Ext2.Mul(c1, tmp) - tmp = e.Ext2.Add(t0, t1) - c1 = e.Ext2.Sub(c1, tmp) - tmp = e.Ext2.MulByNonResidue(t2) - c1 = e.Ext2.Add(c1, tmp) - tmp = e.Ext2.Add(&x.B0, &x.B2) - c2 := e.Ext2.Add(&y.B0, &y.B2) - c2 = e.Ext2.Mul(c2, tmp) - tmp = e.Ext2.Add(t0, t2) - c2 = e.Ext2.Sub(c2, tmp) - c2 = e.Ext2.Add(c2, t1) - return &E6{ - B0: *c0, - B1: *c1, - B2: *c2, - } -} - -func (e Ext6) Double(x *E6) *E6 { - z0 := e.Ext2.Double(&x.B0) - z1 := e.Ext2.Double(&x.B1) - z2 := e.Ext2.Double(&x.B2) - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) Square(x *E6) *E6 { - c4 := e.Ext2.Mul(&x.B0, &x.B1) - c4 = e.Ext2.Double(c4) - c5 := e.Ext2.Square(&x.B2) - c1 := e.Ext2.MulByNonResidue(c5) - c1 = e.Ext2.Add(c1, c4) - c2 := e.Ext2.Sub(c4, c5) - c3 := e.Ext2.Square(&x.B0) - c4 = e.Ext2.Sub(&x.B0, &x.B1) - c4 = e.Ext2.Add(c4, &x.B2) - c5 = e.Ext2.Mul(&x.B1, &x.B2) - c5 = e.Ext2.Double(c5) - c4 = e.Ext2.Square(c4) - c0 := e.Ext2.MulByNonResidue(c5) - c0 = e.Ext2.Add(c0, c3) - z2 := e.Ext2.Add(c2, c4) - z2 = e.Ext2.Add(z2, c5) - z2 = e.Ext2.Sub(z2, c3) - z0 := c0 - z1 := c1 - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) MulByConstE2(x *E6, y *E2) *E6 { - z0 := e.Ext2.Mul(&x.B0, y) - z1 := e.Ext2.Mul(&x.B1, y) - z2 := e.Ext2.Mul(&x.B2, y) - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) MulByE2(x *E6, y *E2) *E6 { - z0 := e.Ext2.Mul(&x.B0, y) - z1 := e.Ext2.Mul(&x.B1, y) - z2 := e.Ext2.Mul(&x.B2, y) - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -// MulBy0 multiplies z by an E6 sparse element of the form -// -// E6{ -// B0: c0, -// B1: 0, -// B2: 0, -// } -func (e Ext6) MulBy0(z *E6, c0 *E2) *E6 { - a := e.Ext2.Mul(&z.B0, c0) - tmp := e.Ext2.Add(&z.B0, &z.B2) - t2 := e.Ext2.Mul(c0, tmp) - t2 = e.Ext2.Sub(t2, a) - tmp = e.Ext2.Add(&z.B0, &z.B1) - t1 := e.Ext2.Mul(c0, tmp) - t1 = e.Ext2.Sub(t1, a) - return &E6{ - B0: *a, - B1: *t1, - B2: *t2, - } -} - -// MulBy01 multiplies z by an E6 sparse element of the form -// -// E6{ -// B0: c0, -// B1: c1, -// B2: 0, -// } -func (e Ext6) MulBy01(z *E6, c0, c1 *E2) *E6 { - a := e.Ext2.Mul(&z.B0, c0) - b := e.Ext2.Mul(&z.B1, c1) - tmp := e.Ext2.Add(&z.B1, &z.B2) - t0 := e.Ext2.Mul(c1, tmp) - t0 = e.Ext2.Sub(t0, b) - t0 = e.Ext2.MulByNonResidue(t0) - t0 = e.Ext2.Add(t0, a) - // for t2, schoolbook is faster than karatsuba - // c2 = a0b2 + a1b1 + a2b0, - // c2 = a2b0 + b ∵ b2 = 0, b = a1b1 - t2 := e.Ext2.Mul(&z.B2, c0) - t2 = e.Ext2.Add(t2, b) - t1 := e.Ext2.Add(c0, c1) - tmp = e.Ext2.Add(&z.B0, &z.B1) - t1 = e.Ext2.Mul(t1, tmp) - tmp = e.Ext2.Add(a, b) - t1 = e.Ext2.Sub(t1, tmp) - return &E6{ - B0: *t0, - B1: *t1, - B2: *t2, - } -} - -// Mul01By01 multiplies two E6 sparse element of the form: -// -// E6{ -// B0: c0, -// B1: c1, -// B2: 0, -// } -// -// and -// -// E6{ -// B0: d0, -// B1: d1, -// B2: 0, -// } -func (e Ext6) Mul01By01(c0, c1, d0, d1 *E2) *E6 { - a := e.Ext2.Mul(d0, c0) - b := e.Ext2.Mul(d1, c1) - t1 := e.Ext2.Add(c0, c1) - tmp := e.Ext2.Add(d0, d1) - t1 = e.Ext2.Mul(t1, tmp) - tmp = e.Ext2.Add(a, b) - t1 = e.Ext2.Sub(t1, tmp) - return &E6{ - B0: *a, - B1: *t1, - B2: *b, - } -} - -func (e Ext6) MulByNonResidue(x *E6) *E6 { - z2, z1, z0 := &x.B1, &x.B0, &x.B2 - z0 = e.Ext2.MulByNonResidue(z0) - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) FrobeniusSquare(x *E6) *E6 { - z01 := e.Ext2.MulByNonResidue2Power2(&x.B1) - z02 := e.Ext2.MulByNonResidue2Power4(&x.B2) - return &E6{B0: x.B0, B1: *z01, B2: *z02} -} - -func (e Ext6) IsEqual(x, y *E6) frontend.Variable { - isB0Equal := e.Ext2.IsEqual(&x.B0, &y.B0) - isB1Equal := e.Ext2.IsEqual(&x.B1, &y.B1) - isB2Equal := e.Ext2.IsEqual(&x.B2, &y.B2) - res := e.api.And(isB0Equal, isB1Equal) - res = e.api.And(res, isB2Equal) - return res -} - -func (e Ext6) AssertIsEqual(x, y *E6) { - e.Ext2.AssertIsEqual(&x.B0, &y.B0) - e.Ext2.AssertIsEqual(&x.B1, &y.B1) - e.Ext2.AssertIsEqual(&x.B2, &y.B2) -} - -func FromE6(y *bn254.E6) E6 { - return E6{ - B0: FromE2(&y.B0), - B1: FromE2(&y.B1), - B2: FromE2(&y.B2), - } - -} - -func (e Ext6) Inverse(x *E6) *E6 { - res, err := e.fp.NewHint(inverseE6Hint, 6, &x.B0.A0, &x.B0.A1, &x.B1.A0, &x.B1.A1, &x.B2.A0, &x.B2.A1) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - - inv := E6{ - B0: E2{A0: *res[0], A1: *res[1]}, - B1: E2{A0: *res[2], A1: *res[3]}, - B2: E2{A0: *res[4], A1: *res[5]}, - } - - one := e.One() - - // 1 == inv * x - _one := e.Mul(&inv, x) - e.AssertIsEqual(one, _one) - - return &inv - -} - -func (e Ext6) DivUnchecked(x, y *E6) *E6 { - res, err := e.fp.NewHint(divE6Hint, 6, &x.B0.A0, &x.B0.A1, &x.B1.A0, &x.B1.A1, &x.B2.A0, &x.B2.A1, &y.B0.A0, &y.B0.A1, &y.B1.A0, &y.B1.A1, &y.B2.A0, &y.B2.A1) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - - div := E6{ - B0: E2{A0: *res[0], A1: *res[1]}, - B1: E2{A0: *res[2], A1: *res[3]}, - B2: E2{A0: *res[4], A1: *res[5]}, - } - - // x == div * y - _x := e.Mul(&div, y) - e.AssertIsEqual(x, _x) - - return &div -} - -func (e Ext6) divE6By6(x [6]*baseEl) [6]*baseEl { - res, err := e.fp.NewHint(divE6By6Hint, 6, x[0], x[1], x[2], x[3], x[4], x[5]) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - - y0 := *res[0] - y1 := *res[1] - y2 := *res[2] - y3 := *res[3] - y4 := *res[4] - y5 := *res[5] - - // xi == 6 * yi - x0 := e.fp.MulConst(&y0, big.NewInt(6)) - x1 := e.fp.MulConst(&y1, big.NewInt(6)) - x2 := e.fp.MulConst(&y2, big.NewInt(6)) - x3 := e.fp.MulConst(&y3, big.NewInt(6)) - x4 := e.fp.MulConst(&y4, big.NewInt(6)) - x5 := e.fp.MulConst(&y5, big.NewInt(6)) - e.fp.AssertIsEqual(x[0], x0) - e.fp.AssertIsEqual(x[1], x1) - e.fp.AssertIsEqual(x[2], x2) - e.fp.AssertIsEqual(x[3], x3) - e.fp.AssertIsEqual(x[4], x4) - e.fp.AssertIsEqual(x[5], x5) - - return [6]*baseEl{&y0, &y1, &y2, &y3, &y4, &y5} -} - -func (e Ext6) Select(selector frontend.Variable, z1, z0 *E6) *E6 { - b0 := e.Ext2.Select(selector, &z1.B0, &z0.B0) - b1 := e.Ext2.Select(selector, &z1.B1, &z0.B1) - b2 := e.Ext2.Select(selector, &z1.B2, &z0.B2) - return &E6{B0: *b0, B1: *b1, B2: *b2} -} - -func (e Ext6) Lookup2(s1, s2 frontend.Variable, a, b, c, d *E6) *E6 { - b0 := e.Ext2.Lookup2(s1, s2, &a.B0, &b.B0, &c.B0, &d.B0) - b1 := e.Ext2.Lookup2(s1, s2, &a.B1, &b.B1, &c.B1, &d.B1) - b2 := e.Ext2.Lookup2(s1, s2, &a.B2, &b.B2, &c.B2, &d.B2) - return &E6{B0: *b0, B1: *b1, B2: *b2} -} diff --git a/std/algebra/emulated/fields_bn254/e6_test.go b/std/algebra/emulated/fields_bn254/e6_test.go deleted file mode 100644 index 4fbfcdea8..000000000 --- a/std/algebra/emulated/fields_bn254/e6_test.go +++ /dev/null @@ -1,444 +0,0 @@ -package fields_bn254 - -import ( - "testing" - - "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark-crypto/ecc/bn254" - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/test" -) - -type e6Add struct { - A, B, C E6 -} - -func (circuit *e6Add) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.Add(&circuit.A, &circuit.B) - e.AssertIsEqual(expected, &circuit.C) - return nil -} - -func TestAddFp6(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, b, c bn254.E6 - _, _ = a.SetRandom() - _, _ = b.SetRandom() - c.Add(&a, &b) - - witness := e6Add{ - A: FromE6(&a), - B: FromE6(&b), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Add{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6Sub struct { - A, B, C E6 -} - -func (circuit *e6Sub) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.Sub(&circuit.A, &circuit.B) - e.AssertIsEqual(expected, &circuit.C) - return nil -} - -func TestSubFp6(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, b, c bn254.E6 - _, _ = a.SetRandom() - _, _ = b.SetRandom() - c.Sub(&a, &b) - - witness := e6Sub{ - A: FromE6(&a), - B: FromE6(&b), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Sub{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6Mul struct { - A, B, C E6 -} - -func (circuit *e6Mul) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.Mul(&circuit.A, &circuit.B) - e.AssertIsEqual(expected, &circuit.C) - return nil -} - -func TestMulFp6(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, b, c bn254.E6 - _, _ = a.SetRandom() - _, _ = b.SetRandom() - c.Mul(&a, &b) - - witness := e6Mul{ - A: FromE6(&a), - B: FromE6(&b), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Mul{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6MulVariant struct { - A, B, C E6 -} - -func (circuit *e6MulVariant) Define(api frontend.API) error { - e := NewExt6(api) - expected1 := e.mulKaratsubaOverKaratsuba(&circuit.A, &circuit.B) - expected2 := e.mulToom3OverKaratsuba(&circuit.A, &circuit.B) - e.AssertIsEqual(expected1, &circuit.C) - e.AssertIsEqual(expected2, &circuit.C) - return nil -} - -func TestMulFp6Variants(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, b, c bn254.E6 - _, _ = a.SetRandom() - _, _ = b.SetRandom() - c.Mul(&a, &b) - - witness := e6Mul{ - A: FromE6(&a), - B: FromE6(&b), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Mul{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6Square struct { - A, C E6 -} - -func (circuit *e6Square) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.Square(&circuit.A) - e.AssertIsEqual(expected, &circuit.C) - return nil -} - -func TestSquareFp6(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bn254.E6 - _, _ = a.SetRandom() - c.Square(&a) - - witness := e6Square{ - A: FromE6(&a), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Square{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6Div struct { - A, B, C E6 -} - -func (circuit *e6Div) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.DivUnchecked(&circuit.A, &circuit.B) - e.AssertIsEqual(expected, &circuit.C) - return nil -} - -func TestDivFp6(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, b, c bn254.E6 - _, _ = a.SetRandom() - _, _ = b.SetRandom() - c.Div(&a, &b) - - witness := e6Div{ - A: FromE6(&a), - B: FromE6(&b), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Div{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6MulByNonResidue struct { - A E6 - C E6 `gnark:",public"` -} - -func (circuit *e6MulByNonResidue) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.MulByNonResidue(&circuit.A) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestMulFp6ByNonResidue(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bn254.E6 - _, _ = a.SetRandom() - c.MulByNonResidue(&a) - - witness := e6MulByNonResidue{ - A: FromE6(&a), - C: FromE6(&c), - } - - err := test.IsSolved(&e6MulByNonResidue{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6MulByE2 struct { - A E6 - B E2 - C E6 `gnark:",public"` -} - -func (circuit *e6MulByE2) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.MulByE2(&circuit.A, &circuit.B) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestMulFp6ByE2(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bn254.E6 - var b bn254.E2 - _, _ = a.SetRandom() - _, _ = b.SetRandom() - c.MulByE2(&a, &b) - - witness := e6MulByE2{ - A: FromE6(&a), - B: FromE2(&b), - C: FromE6(&c), - } - - err := test.IsSolved(&e6MulByE2{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6MulBy01 struct { - A E6 - C0, C1 E2 - C E6 `gnark:",public"` -} - -func (circuit *e6MulBy01) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.MulBy01(&circuit.A, &circuit.C0, &circuit.C1) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestMulFp6By01(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bn254.E6 - var C0, C1 bn254.E2 - _, _ = a.SetRandom() - _, _ = C0.SetRandom() - _, _ = C1.SetRandom() - c.Set(&a) - c.MulBy01(&C0, &C1) - - witness := e6MulBy01{ - A: FromE6(&a), - C0: FromE2(&C0), - C1: FromE2(&C1), - C: FromE6(&c), - } - - err := test.IsSolved(&e6MulBy01{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6Mul01By01 struct { - A0, A1 E2 - B0, B1 E2 - C E6 `gnark:",public"` -} - -func (circuit *e6Mul01By01) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.Mul01By01(&circuit.A0, &circuit.A1, &circuit.B0, &circuit.B1) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestMul01By01(t *testing.T) { - - // we test our new E3.Mul01By01 against E3.MulBy01 - assert := test.NewAssert(t) - // witness values - var a, c bn254.E6 - var A0, A1, B0, B1 bn254.E2 - _, _ = A0.SetRandom() - _, _ = A1.SetRandom() - _, _ = B0.SetRandom() - _, _ = B1.SetRandom() - - // build a 01 sparse E3 with, - // first two elements as A1 and A2, - // and the third as 0 - a.B0 = A0 - a.B1 = A1 - a.B2.SetZero() - c.Set(&a) - c.MulBy01(&B0, &B1) - - witness := e6Mul01By01{ - A0: FromE2(&A0), - A1: FromE2(&A1), - B0: FromE2(&B0), - B1: FromE2(&B1), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Mul01By01{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6MulBy0 struct { - A E6 - C0 E2 - C E6 `gnark:",public"` -} - -func (circuit *e6MulBy0) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.MulBy0(&circuit.A, &circuit.C0) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestMulFp6By0(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bn254.E6 - var C0, zero bn254.E2 - _, _ = a.SetRandom() - _, _ = C0.SetRandom() - c.Set(&a) - c.MulBy01(&C0, &zero) - - witness := e6MulBy0{ - A: FromE6(&a), - C0: FromE2(&C0), - C: FromE6(&c), - } - - err := test.IsSolved(&e6MulBy0{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6Neg struct { - A E6 - C E6 `gnark:",public"` -} - -func (circuit *e6Neg) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.Neg(&circuit.A) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestNegFp6(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bn254.E6 - _, _ = a.SetRandom() - c.Neg(&a) - - witness := e6Neg{ - A: FromE6(&a), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Neg{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) -} - -type e6Inverse struct { - A E6 - C E6 `gnark:",public"` -} - -func (circuit *e6Inverse) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.Inverse(&circuit.A) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestInverseFp6(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bn254.E6 - _, _ = a.SetRandom() - c.Inverse(&a) - - witness := e6Inverse{ - A: FromE6(&a), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Inverse{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) -} diff --git a/std/algebra/emulated/fields_bn254/hints.go b/std/algebra/emulated/fields_bn254/hints.go index c5d68ed67..556558f6d 100644 --- a/std/algebra/emulated/fields_bn254/hints.go +++ b/std/algebra/emulated/fields_bn254/hints.go @@ -19,15 +19,9 @@ func GetHints() []solver.Hint { // E2 divE2Hint, inverseE2Hint, - // E6 - divE6Hint, - inverseE6Hint, - squareTorusHint, - divE6By6Hint, // E12 divE12Hint, inverseE12Hint, - finalExpHint, } } @@ -67,153 +61,71 @@ func divE2Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error }) } -// E6 hints -func inverseE6Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { - return emulated.UnwrapHint(nativeInputs, nativeOutputs, - func(mod *big.Int, inputs, outputs []*big.Int) error { - var a, c bn254.E6 - - a.B0.A0.SetBigInt(inputs[0]) - a.B0.A1.SetBigInt(inputs[1]) - a.B1.A0.SetBigInt(inputs[2]) - a.B1.A1.SetBigInt(inputs[3]) - a.B2.A0.SetBigInt(inputs[4]) - a.B2.A1.SetBigInt(inputs[5]) - - c.Inverse(&a) - - c.B0.A0.BigInt(outputs[0]) - c.B0.A1.BigInt(outputs[1]) - c.B1.A0.BigInt(outputs[2]) - c.B1.A1.BigInt(outputs[3]) - c.B2.A0.BigInt(outputs[4]) - c.B2.A1.BigInt(outputs[5]) - - return nil - }) -} - -func divE6Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { - return emulated.UnwrapHint(nativeInputs, nativeOutputs, - func(mod *big.Int, inputs, outputs []*big.Int) error { - var a, b, c bn254.E6 - - a.B0.A0.SetBigInt(inputs[0]) - a.B0.A1.SetBigInt(inputs[1]) - a.B1.A0.SetBigInt(inputs[2]) - a.B1.A1.SetBigInt(inputs[3]) - a.B2.A0.SetBigInt(inputs[4]) - a.B2.A1.SetBigInt(inputs[5]) - - b.B0.A0.SetBigInt(inputs[6]) - b.B0.A1.SetBigInt(inputs[7]) - b.B1.A0.SetBigInt(inputs[8]) - b.B1.A1.SetBigInt(inputs[9]) - b.B2.A0.SetBigInt(inputs[10]) - b.B2.A1.SetBigInt(inputs[11]) - - c.Inverse(&b).Mul(&c, &a) - - c.B0.A0.BigInt(outputs[0]) - c.B0.A1.BigInt(outputs[1]) - c.B1.A0.BigInt(outputs[2]) - c.B1.A1.BigInt(outputs[3]) - c.B2.A0.BigInt(outputs[4]) - c.B2.A1.BigInt(outputs[5]) - - return nil - }) -} - -func squareTorusHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { - return emulated.UnwrapHint(nativeInputs, nativeOutputs, - func(mod *big.Int, inputs, outputs []*big.Int) error { - var a, c bn254.E6 - - a.B0.A0.SetBigInt(inputs[0]) - a.B0.A1.SetBigInt(inputs[1]) - a.B1.A0.SetBigInt(inputs[2]) - a.B1.A1.SetBigInt(inputs[3]) - a.B2.A0.SetBigInt(inputs[4]) - a.B2.A1.SetBigInt(inputs[5]) - - _c := a.DecompressTorus() - _c.CyclotomicSquare(&_c) - c, _ = _c.CompressTorus() - - c.B0.A0.BigInt(outputs[0]) - c.B0.A1.BigInt(outputs[1]) - c.B1.A0.BigInt(outputs[2]) - c.B1.A1.BigInt(outputs[3]) - c.B2.A0.BigInt(outputs[4]) - c.B2.A1.BigInt(outputs[5]) - - return nil - }) -} - -func divE6By6Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { - return emulated.UnwrapHint(nativeInputs, nativeOutputs, - func(mod *big.Int, inputs, outputs []*big.Int) error { - var a, c bn254.E6 - - a.B0.A0.SetBigInt(inputs[0]) - a.B0.A1.SetBigInt(inputs[1]) - a.B1.A0.SetBigInt(inputs[2]) - a.B1.A1.SetBigInt(inputs[3]) - a.B2.A0.SetBigInt(inputs[4]) - a.B2.A1.SetBigInt(inputs[5]) - - var sixInv fp.Element - sixInv.SetString("6") - sixInv.Inverse(&sixInv) - c.B0.MulByElement(&a.B0, &sixInv) - c.B1.MulByElement(&a.B1, &sixInv) - c.B2.MulByElement(&a.B2, &sixInv) - - c.B0.A0.BigInt(outputs[0]) - c.B0.A1.BigInt(outputs[1]) - c.B1.A0.BigInt(outputs[2]) - c.B1.A1.BigInt(outputs[3]) - c.B2.A0.BigInt(outputs[4]) - c.B2.A1.BigInt(outputs[5]) - - return nil - }) -} - // E12 hints func inverseE12Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { return emulated.UnwrapHint(nativeInputs, nativeOutputs, func(mod *big.Int, inputs, outputs []*big.Int) error { + var d [12]big.Int + var t1 big.Int + t1.SetUint64(9).Mul(&t1, inputs[6]) + d[0].Add(inputs[0], &t1) + d[1].Set(inputs[6]) + t1.SetUint64(9).Mul(&t1, inputs[8]) + d[2].Add(inputs[2], &t1) + d[3].Set(inputs[8]) + t1.SetUint64(9).Mul(&t1, inputs[10]) + d[4].Add(inputs[4], &t1) + d[5].Set(inputs[10]) + t1.SetUint64(9).Mul(&t1, inputs[7]) + d[6].Add(inputs[1], &t1) + d[7].Set(inputs[7]) + t1.SetUint64(9).Mul(&t1, inputs[9]) + d[8].Add(inputs[3], &t1) + d[9].Set(inputs[9]) + t1.SetUint64(9).Mul(&t1, inputs[11]) + d[10].Add(inputs[5], &t1) + d[11].Set(inputs[11]) var a, c bn254.E12 - - a.C0.B0.A0.SetBigInt(inputs[0]) - a.C0.B0.A1.SetBigInt(inputs[1]) - a.C0.B1.A0.SetBigInt(inputs[2]) - a.C0.B1.A1.SetBigInt(inputs[3]) - a.C0.B2.A0.SetBigInt(inputs[4]) - a.C0.B2.A1.SetBigInt(inputs[5]) - a.C1.B0.A0.SetBigInt(inputs[6]) - a.C1.B0.A1.SetBigInt(inputs[7]) - a.C1.B1.A0.SetBigInt(inputs[8]) - a.C1.B1.A1.SetBigInt(inputs[9]) - a.C1.B2.A0.SetBigInt(inputs[10]) - a.C1.B2.A1.SetBigInt(inputs[11]) + a.C0.B0.A0.SetBigInt(&d[0]) + a.C0.B0.A1.SetBigInt(&d[1]) + a.C0.B1.A0.SetBigInt(&d[2]) + a.C0.B1.A1.SetBigInt(&d[3]) + a.C0.B2.A0.SetBigInt(&d[4]) + a.C0.B2.A1.SetBigInt(&d[5]) + a.C1.B0.A0.SetBigInt(&d[6]) + a.C1.B0.A1.SetBigInt(&d[7]) + a.C1.B1.A0.SetBigInt(&d[8]) + a.C1.B1.A1.SetBigInt(&d[9]) + a.C1.B2.A0.SetBigInt(&d[10]) + a.C1.B2.A1.SetBigInt(&d[11]) c.Inverse(&a) - c.C0.B0.A0.BigInt(outputs[0]) - c.C0.B0.A1.BigInt(outputs[1]) - c.C0.B1.A0.BigInt(outputs[2]) - c.C0.B1.A1.BigInt(outputs[3]) - c.C0.B2.A0.BigInt(outputs[4]) - c.C0.B2.A1.BigInt(outputs[5]) - c.C1.B0.A0.BigInt(outputs[6]) + var c0, c1, c2, c3, c4, c5, t2 fp.Element + t2.SetUint64(9).Mul(&t2, &c.C0.B0.A1) + c0.Sub(&c.C0.B0.A0, &t2) + t2.SetUint64(9).Mul(&t2, &c.C1.B0.A1) + c1.Sub(&c.C1.B0.A0, &t2) + t2.SetUint64(9).Mul(&t2, &c.C0.B1.A1) + c2.Sub(&c.C0.B1.A0, &t2) + t2.SetUint64(9).Mul(&t2, &c.C1.B1.A1) + c3.Sub(&c.C1.B1.A0, &t2) + t2.SetUint64(9).Mul(&t2, &c.C0.B2.A1) + c4.Sub(&c.C0.B2.A0, &t2) + t2.SetUint64(9).Mul(&t2, &c.C1.B2.A1) + c5.Sub(&c.C1.B2.A0, &t2) + + c0.BigInt(outputs[0]) + c1.BigInt(outputs[1]) + c2.BigInt(outputs[2]) + c3.BigInt(outputs[3]) + c4.BigInt(outputs[4]) + c5.BigInt(outputs[5]) + c.C0.B0.A1.BigInt(outputs[6]) c.C1.B0.A1.BigInt(outputs[7]) - c.C1.B1.A0.BigInt(outputs[8]) + c.C0.B1.A1.BigInt(outputs[8]) c.C1.B1.A1.BigInt(outputs[9]) - c.C1.B2.A0.BigInt(outputs[10]) + c.C0.B2.A1.BigInt(outputs[10]) c.C1.B2.A1.BigInt(outputs[11]) return nil @@ -225,170 +137,99 @@ func divE12Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) erro func(mod *big.Int, inputs, outputs []*big.Int) error { var a, b, c bn254.E12 - a.C0.B0.A0.SetBigInt(inputs[0]) - a.C0.B0.A1.SetBigInt(inputs[1]) - a.C0.B1.A0.SetBigInt(inputs[2]) - a.C0.B1.A1.SetBigInt(inputs[3]) - a.C0.B2.A0.SetBigInt(inputs[4]) - a.C0.B2.A1.SetBigInt(inputs[5]) - a.C1.B0.A0.SetBigInt(inputs[6]) - a.C1.B0.A1.SetBigInt(inputs[7]) - a.C1.B1.A0.SetBigInt(inputs[8]) - a.C1.B1.A1.SetBigInt(inputs[9]) - a.C1.B2.A0.SetBigInt(inputs[10]) - a.C1.B2.A1.SetBigInt(inputs[11]) - - b.C0.B0.A0.SetBigInt(inputs[12]) - b.C0.B0.A1.SetBigInt(inputs[13]) - b.C0.B1.A0.SetBigInt(inputs[14]) - b.C0.B1.A1.SetBigInt(inputs[15]) - b.C0.B2.A0.SetBigInt(inputs[16]) - b.C0.B2.A1.SetBigInt(inputs[17]) - b.C1.B0.A0.SetBigInt(inputs[18]) - b.C1.B0.A1.SetBigInt(inputs[19]) - b.C1.B1.A0.SetBigInt(inputs[20]) - b.C1.B1.A1.SetBigInt(inputs[21]) - b.C1.B2.A0.SetBigInt(inputs[22]) - b.C1.B2.A1.SetBigInt(inputs[23]) + var d [12]big.Int + var t1 big.Int + t1.SetUint64(9).Mul(&t1, inputs[6]) + d[0].Add(inputs[0], &t1) + d[1].Set(inputs[6]) + t1.SetUint64(9).Mul(&t1, inputs[8]) + d[2].Add(inputs[2], &t1) + d[3].Set(inputs[8]) + t1.SetUint64(9).Mul(&t1, inputs[10]) + d[4].Add(inputs[4], &t1) + d[5].Set(inputs[10]) + t1.SetUint64(9).Mul(&t1, inputs[7]) + d[6].Add(inputs[1], &t1) + d[7].Set(inputs[7]) + t1.SetUint64(9).Mul(&t1, inputs[9]) + d[8].Add(inputs[3], &t1) + d[9].Set(inputs[9]) + t1.SetUint64(9).Mul(&t1, inputs[11]) + d[10].Add(inputs[5], &t1) + d[11].Set(inputs[11]) + a.C0.B0.A0.SetBigInt(&d[0]) + a.C0.B0.A1.SetBigInt(&d[1]) + a.C0.B1.A0.SetBigInt(&d[2]) + a.C0.B1.A1.SetBigInt(&d[3]) + a.C0.B2.A0.SetBigInt(&d[4]) + a.C0.B2.A1.SetBigInt(&d[5]) + a.C1.B0.A0.SetBigInt(&d[6]) + a.C1.B0.A1.SetBigInt(&d[7]) + a.C1.B1.A0.SetBigInt(&d[8]) + a.C1.B1.A1.SetBigInt(&d[9]) + a.C1.B2.A0.SetBigInt(&d[10]) + a.C1.B2.A1.SetBigInt(&d[11]) + + t1.SetUint64(9).Mul(&t1, inputs[18]) + d[0].Add(inputs[12], &t1) + d[1].Set(inputs[18]) + t1.SetUint64(9).Mul(&t1, inputs[20]) + d[2].Add(inputs[14], &t1) + d[3].Set(inputs[20]) + t1.SetUint64(9).Mul(&t1, inputs[22]) + d[4].Add(inputs[16], &t1) + d[5].Set(inputs[22]) + t1.SetUint64(9).Mul(&t1, inputs[19]) + d[6].Add(inputs[13], &t1) + d[7].Set(inputs[19]) + t1.SetUint64(9).Mul(&t1, inputs[21]) + d[8].Add(inputs[15], &t1) + d[9].Set(inputs[21]) + t1.SetUint64(9).Mul(&t1, inputs[23]) + d[10].Add(inputs[17], &t1) + d[11].Set(inputs[23]) + b.C0.B0.A0.SetBigInt(&d[0]) + b.C0.B0.A1.SetBigInt(&d[1]) + b.C0.B1.A0.SetBigInt(&d[2]) + b.C0.B1.A1.SetBigInt(&d[3]) + b.C0.B2.A0.SetBigInt(&d[4]) + b.C0.B2.A1.SetBigInt(&d[5]) + b.C1.B0.A0.SetBigInt(&d[6]) + b.C1.B0.A1.SetBigInt(&d[7]) + b.C1.B1.A0.SetBigInt(&d[8]) + b.C1.B1.A1.SetBigInt(&d[9]) + b.C1.B2.A0.SetBigInt(&d[10]) + b.C1.B2.A1.SetBigInt(&d[11]) c.Inverse(&b).Mul(&c, &a) - c.C0.B0.A0.BigInt(outputs[0]) - c.C0.B0.A1.BigInt(outputs[1]) - c.C0.B1.A0.BigInt(outputs[2]) - c.C0.B1.A1.BigInt(outputs[3]) - c.C0.B2.A0.BigInt(outputs[4]) - c.C0.B2.A1.BigInt(outputs[5]) - c.C1.B0.A0.BigInt(outputs[6]) + var c0, c1, c2, c3, c4, c5, t2 fp.Element + t2.SetUint64(9).Mul(&t2, &c.C0.B0.A1) + c0.Sub(&c.C0.B0.A0, &t2) + t2.SetUint64(9).Mul(&t2, &c.C1.B0.A1) + c1.Sub(&c.C1.B0.A0, &t2) + t2.SetUint64(9).Mul(&t2, &c.C0.B1.A1) + c2.Sub(&c.C0.B1.A0, &t2) + t2.SetUint64(9).Mul(&t2, &c.C1.B1.A1) + c3.Sub(&c.C1.B1.A0, &t2) + t2.SetUint64(9).Mul(&t2, &c.C0.B2.A1) + c4.Sub(&c.C0.B2.A0, &t2) + t2.SetUint64(9).Mul(&t2, &c.C1.B2.A1) + c5.Sub(&c.C1.B2.A0, &t2) + + c0.BigInt(outputs[0]) + c1.BigInt(outputs[1]) + c2.BigInt(outputs[2]) + c3.BigInt(outputs[3]) + c4.BigInt(outputs[4]) + c5.BigInt(outputs[5]) + c.C0.B0.A1.BigInt(outputs[6]) c.C1.B0.A1.BigInt(outputs[7]) - c.C1.B1.A0.BigInt(outputs[8]) + c.C0.B1.A1.BigInt(outputs[8]) c.C1.B1.A1.BigInt(outputs[9]) - c.C1.B2.A0.BigInt(outputs[10]) + c.C0.B2.A1.BigInt(outputs[10]) c.C1.B2.A1.BigInt(outputs[11]) return nil }) } - -func finalExpHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { - // This follows section 4.3.2 of https://eprint.iacr.org/2024/640.pdf - return emulated.UnwrapHint(nativeInputs, nativeOutputs, - func(mod *big.Int, inputs, outputs []*big.Int) error { - var tmp, x3, cubicNonResiduePower, x, millerLoop, residueWitness, residueWitnessInv, one, root27thOf1 bn254.E12 - var exp1, exp2, rInv, mInv big.Int - - millerLoop.C0.B0.A0.SetBigInt(inputs[0]) - millerLoop.C0.B0.A1.SetBigInt(inputs[1]) - millerLoop.C0.B1.A0.SetBigInt(inputs[2]) - millerLoop.C0.B1.A1.SetBigInt(inputs[3]) - millerLoop.C0.B2.A0.SetBigInt(inputs[4]) - millerLoop.C0.B2.A1.SetBigInt(inputs[5]) - millerLoop.C1.B0.A0.SetBigInt(inputs[6]) - millerLoop.C1.B0.A1.SetBigInt(inputs[7]) - millerLoop.C1.B1.A0.SetBigInt(inputs[8]) - millerLoop.C1.B1.A1.SetBigInt(inputs[9]) - millerLoop.C1.B2.A0.SetBigInt(inputs[10]) - millerLoop.C1.B2.A1.SetBigInt(inputs[11]) - - // exp1 = (p^12-1)/3 - exp1.SetString("4030969696062745741797811005853058291874379204406359442560681893891674450106959530046539719647151210908190211459382793062006703141168852426020468083171325367934590379984666859998399967609544754664110191464072930598755441160008826659219834762354786403012110463250131961575955268597858015384895449311534622125256548620283853223733396368939858981844663598065852816056384933498610930035891058807598891752166582271931875150099691598048016175399382213304673796601585080509443902692818733420199004555566113537482054218823936116647313678747500267068559627206777530424029211671772692598157901876223857571299238046741502089890557442500582300718504160740314926185458079985126192563953772118929726791041828902047546977272656240744693339962973939047279285351052107950250121751682659529260304162131862468322644288196213423232132152125277136333208005221619443705106431645884840489295409272576227859206166894626854018093044908314720", 10) - // root27thOf1 = (0, c010, c011, 0, 0, 0, 0, 0, 0, 0, 0, 0) - // is a 27-th root of unity which is necessarily a cubic non-residue - // since h/r = (p^12-1)/r = 27·l and 3 does not divide l. - // it was computed as w^((p^12-1)/27) = c2 * w^2 + c8 * w^8 where - // Fp12 = Fp[w]/w^12-18w^6+82 which is isomorphic to our Fp12 tower - // then c010 = (c2 + 9 * c8) % p and c011 = c8 - root27thOf1.C0.B1.A0.SetString("9483667112135124394372960210728142145589475128897916459350428495526310884707") - root27thOf1.C0.B1.A1.SetString("4534159768373982659291990808346042891252278737770656686799127720849666919525") - - if one.Exp(millerLoop, &exp1).IsOne() { - // residueWitness = millerLoop is a cubic residue - cubicNonResiduePower.SetOne() - residueWitness.Set(&millerLoop) - } else if one.Exp(*millerLoop.Mul(&millerLoop, &root27thOf1), &exp1).IsOne() { - // residueWitness = millerLoop * root27thOf1 is a cubic residue - cubicNonResiduePower.Set(&root27thOf1) - residueWitness.Set(&millerLoop) - } else { - // residueWitness = millerLoop * root27thOf1^2 is a cubic residue - cubicNonResiduePower.Square(&root27thOf1) - residueWitness.Mul(&millerLoop, &root27thOf1) - } - - // 1. compute r-th root: - // Exponentiate to rInv where - // rInv = 1/r mod (p^12-1)/r - rInv.SetString("495819184011867778744231927046742333492451180917315223017345540833046880485481720031136878341141903241966521818658471092566752321606779256340158678675679238405722886654128392203338228575623261160538734808887996935946888297414610216445334190959815200956855428635568184508263913274453942864817234480763055154719338281461936129150171789463489422401982681230261920147923652438266934726901346095892093443898852488218812468761027620988447655860644584419583586883569984588067403598284748297179498734419889699245081714359110559679136004228878808158639412436468707589339209058958785568729925402190575720856279605832146553573981587948304340677613460685405477047119496887534881410757668344088436651291444274840864486870663164657544390995506448087189408281061890434467956047582679858345583941396130713046072603335601764495918026585155498301896749919393", 10) - residueWitness.Exp(residueWitness, &rInv) - - // 2. compute m-th root: - // where m = (6x + 2 + q^3 - q^2 + q)/(3r) - // Exponentiate to mInv where - // mInv = 1/m mod p^12-1 - mInv.SetString("17840267520054779749190587238017784600702972825655245554504342129614427201836516118803396948809179149954197175783449826546445899524065131269177708416982407215963288737761615699967145070776364294542559324079147363363059480104341231360692143673915822421222230661528586799190306058519400019024762424366780736540525310403098758015600523609594113357130678138304964034267260758692953579514899054295817541844330584721967571697039986079722203518034173581264955381924826388858518077894154909963532054519350571947910625755075099598588672669612434444513251495355121627496067454526862754597351094345783576387352673894873931328099247263766690688395096280633426669535619271711975898132416216382905928886703963310231865346128293216316379527200971959980873989485521004596686352787540034457467115536116148612884807380187255514888720048664139404687086409399", 10) - residueWitness.Exp(residueWitness, &mInv) - - // 3. compute cube root: - // since gcd(3, (p^12-1)/r) ≠ 1 we use a modified Toneelli-Shanks algorithm - // see Alg.4 of https://eprint.iacr.org/2024/640.pdf - // Typo in the paper: p^k-1 = 3^n * s instead of p-1 = 3^r * s - // where k=12 and n=3 here and exp2 = (s+1)/3 - residueWitnessInv.Inverse(&residueWitness) - exp2.SetString("149295173928249842288807815031594751550902933496531831205951181255247201855813315927649619246190785589192230054051214557852100116339587126889646966043382421034614458517950624444385183985538694617189266350521219651805757080000326913304438324531658755667115202342597480058368713651772519088329461085612393412046538837788290860138273939590365147475728281409846400594680923462911515927255224400281440435265428973034513894448136725853630228718495637529802733207466114092942366766400693830377740909465411612499335341437923559875826432546203713595131838044695464089778859691547136762894737106526809539677749557286722299625576201574095640767352005953344997266128077036486155280146436004404804695964512181557316554713802082990544197776406442186936269827816744738898152657469728130713344598597476387715653492155415311971560450078713968012341037230430349766855793764662401499603533676762082513303932107208402000670112774382027", 10) - x.Exp(residueWitness, &exp2) - - // 3^t is ord(x^3 / residueWitness) - x3.Square(&x).Mul(&x3, &x).Mul(&x3, &residueWitnessInv) - t := 0 - for !x3.IsOne() { - t++ - tmp.Square(&x3) - x3.Mul(&tmp, &x3) - } - - for t != 0 { - x.Mul(&x, tmp.Exp(root27thOf1, &exp2)) - - // 3^t is ord(x^3 / residueWitness) - x3.Square(&x).Mul(&x3, &x).Mul(&x3, &residueWitnessInv) - t = 0 - for !x3.IsOne() { - t++ - tmp.Square(&x3) - x3.Mul(&tmp, &x3) - } - } - - // x is now the cube root of residueWitness - residueWitness.Set(&x) - - residueWitness.C0.B0.A0.BigInt(outputs[0]) - residueWitness.C0.B0.A1.BigInt(outputs[1]) - residueWitness.C0.B1.A0.BigInt(outputs[2]) - residueWitness.C0.B1.A1.BigInt(outputs[3]) - residueWitness.C0.B2.A0.BigInt(outputs[4]) - residueWitness.C0.B2.A1.BigInt(outputs[5]) - residueWitness.C1.B0.A0.BigInt(outputs[6]) - residueWitness.C1.B0.A1.BigInt(outputs[7]) - residueWitness.C1.B1.A0.BigInt(outputs[8]) - residueWitness.C1.B1.A1.BigInt(outputs[9]) - residueWitness.C1.B2.A0.BigInt(outputs[10]) - residueWitness.C1.B2.A1.BigInt(outputs[11]) - - // we also need to return the cubic non-residue power - cubicNonResiduePower.C0.B0.A0.BigInt(outputs[12]) - cubicNonResiduePower.C0.B0.A1.BigInt(outputs[13]) - cubicNonResiduePower.C0.B1.A0.BigInt(outputs[14]) - cubicNonResiduePower.C0.B1.A1.BigInt(outputs[15]) - cubicNonResiduePower.C0.B2.A0.BigInt(outputs[16]) - cubicNonResiduePower.C0.B2.A1.BigInt(outputs[17]) - cubicNonResiduePower.C1.B0.A0.BigInt(outputs[18]) - cubicNonResiduePower.C1.B0.A1.BigInt(outputs[19]) - cubicNonResiduePower.C1.B1.A0.BigInt(outputs[20]) - cubicNonResiduePower.C1.B1.A1.BigInt(outputs[21]) - cubicNonResiduePower.C1.B2.A0.BigInt(outputs[22]) - cubicNonResiduePower.C1.B2.A1.BigInt(outputs[23]) - - return nil - }) -} diff --git a/std/algebra/emulated/sw_bn254/g2.go b/std/algebra/emulated/sw_bn254/g2.go index 8edb7953c..6058f6e6c 100644 --- a/std/algebra/emulated/sw_bn254/g2.go +++ b/std/algebra/emulated/sw_bn254/g2.go @@ -11,6 +11,7 @@ import ( type G2 struct { api frontend.API + fp *emulated.Field[BaseField] *fields_bn254.Ext2 w *emulated.Element[BaseField] u, v *fields_bn254.E2 @@ -40,6 +41,11 @@ func newG2AffP(v bn254.G2Affine) g2AffP { } func NewG2(api frontend.API) *G2 { + fp, err := emulated.NewField[emulated.BN254Fp](api) + if err != nil { + // TODO: we start returning errors when generifying + panic(err) + } w := emulated.ValueOf[BaseField]("21888242871839275220042445260109153167277707414472061641714758635765020556616") u := fields_bn254.E2{ A0: emulated.ValueOf[BaseField]("21575463638280843010398324269430826099269044274347216827212613867836435027261"), @@ -51,6 +57,7 @@ func NewG2(api frontend.API) *G2 { } return &G2{ api: api, + fp: fp, Ext2: fields_bn254.NewExt2(api), w: &w, u: &u, @@ -149,20 +156,23 @@ func (g2 *G2) scalarMulBySeed(q *G2Affine) *G2Affine { } func (g2 G2) add(p, q *G2Affine) *G2Affine { + mone := g2.fp.NewElement(-1) + // 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) λ := g2.Ext2.DivUnchecked(qypy, qxpx) // xr = λ²-p.x-q.x - λλ := g2.Ext2.Square(λ) - qxpx = g2.Ext2.Add(&p.P.X, &q.P.X) - xr := g2.Ext2.Sub(λλ, qxpx) + xr0 := g2.fp.Eval([][]*baseEl{{&λ.A0, &λ.A0}, {mone, &λ.A1, &λ.A1}, {mone, &p.P.X.A0}, {mone, &q.P.X.A0}}, []int{1, 1, 1, 1}) + xr1 := g2.fp.Eval([][]*baseEl{{&λ.A0, &λ.A1}, {mone, &p.P.X.A1}, {mone, &q.P.X.A1}}, []int{2, 1, 1}) + xr := &fields_bn254.E2{A0: *xr0, A1: *xr1} // p.y = λ(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) + yr := g2.Ext2.Sub(&p.P.X, xr) + yr0 := g2.fp.Eval([][]*baseEl{{&λ.A0, &yr.A0}, {mone, &λ.A1, &yr.A1}, {mone, &p.P.Y.A0}}, []int{1, 1, 1}) + yr1 := g2.fp.Eval([][]*baseEl{{&λ.A0, &yr.A1}, {&λ.A1, &yr.A0}, {mone, &p.P.Y.A1}}, []int{1, 1, 1}) + yr = &fields_bn254.E2{A0: *yr0, A1: *yr1} return &G2Affine{ P: g2AffP{ @@ -189,6 +199,8 @@ func (g2 G2) sub(p, q *G2Affine) *G2Affine { } func (g2 *G2) double(p *G2Affine) *G2Affine { + mone := g2.fp.NewElement(-1) + // compute λ = (3p.x²)/2*p.y xx3a := g2.Square(&p.P.X) xx3a = g2.MulByConstElement(xx3a, big.NewInt(3)) @@ -196,14 +208,15 @@ func (g2 *G2) double(p *G2Affine) *G2Affine { λ := g2.DivUnchecked(xx3a, y2) // xr = λ²-2p.x - x2 := g2.Double(&p.P.X) - λλ := g2.Square(λ) - xr := g2.Sub(λλ, x2) + xr0 := g2.fp.Eval([][]*baseEl{{&λ.A0, &λ.A0}, {mone, &λ.A1, &λ.A1}, {mone, &p.P.X.A0}}, []int{1, 1, 2}) + xr1 := g2.fp.Eval([][]*baseEl{{&λ.A0, &λ.A1}, {mone, &p.P.X.A1}}, []int{2, 2}) + xr := &fields_bn254.E2{A0: *xr0, A1: *xr1} // yr = λ(p-xr) - p.y - pxrx := g2.Sub(&p.P.X, xr) - λpxrx := g2.Mul(λ, pxrx) - yr := g2.Sub(λpxrx, &p.P.Y) + yr := g2.Ext2.Sub(&p.P.X, xr) + yr0 := g2.fp.Eval([][]*baseEl{{&λ.A0, &yr.A0}, {mone, &λ.A1, &yr.A1}, {mone, &p.P.Y.A0}}, []int{1, 1, 1}) + yr1 := g2.fp.Eval([][]*baseEl{{&λ.A0, &yr.A1}, {&λ.A1, &yr.A0}, {mone, &p.P.Y.A1}}, []int{1, 1, 1}) + yr = &fields_bn254.E2{A0: *yr0, A1: *yr1} return &G2Affine{ P: g2AffP{ @@ -222,6 +235,7 @@ func (g2 *G2) doubleN(p *G2Affine, n int) *G2Affine { } func (g2 G2) doubleAndAdd(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) @@ -229,9 +243,9 @@ func (g2 G2) doubleAndAdd(p, q *G2Affine) *G2Affine { λ1 := g2.Ext2.DivUnchecked(yqyp, xqxp) // compute x2 = λ1²-p.x-q.x - λ1λ1 := g2.Ext2.Square(λ1) - xqxp = g2.Ext2.Add(&p.P.X, &q.P.X) - x2 := g2.Ext2.Sub(λ1λ1, xqxp) + 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_bn254.E2{A0: *x20, A1: *x21} // omit y2 computation // compute λ2 = -λ1-2*p.y/(x2-p.x) @@ -241,15 +255,16 @@ func (g2 G2) doubleAndAdd(p, q *G2Affine) *G2Affine { λ2 = g2.Ext2.Add(λ1, λ2) λ2 = g2.Ext2.Neg(λ2) - // compute x3 =λ2²-p.x-x3 - λ2λ2 := g2.Ext2.Square(λ2) - x3 := g2.Ext2.Sub(λ2λ2, &p.P.X) - x3 = g2.Ext2.Sub(x3, x2) + // compute x3 =λ2²-p.x-x2 + x30 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &λ2.A0}, {mone, &λ2.A1, &λ2.A1}, {mone, &p.P.X.A0}, {mone, x20}}, []int{1, 1, 1, 1}) + x31 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &λ2.A1}, {mone, &p.P.X.A1}, {mone, x21}}, []int{2, 1, 1}) + x3 := &fields_bn254.E2{A0: *x30, A1: *x31} // compute y3 = λ2*(p.x - x3)-p.y y3 := g2.Ext2.Sub(&p.P.X, x3) - y3 = g2.Ext2.Mul(λ2, y3) - y3 = g2.Ext2.Sub(y3, &p.P.Y) + y30 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &y3.A0}, {mone, &λ2.A1, &y3.A1}, {mone, &p.P.Y.A0}}, []int{1, 1, 1}) + y31 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &y3.A1}, {&λ2.A1, &y3.A0}, {mone, &p.P.Y.A1}}, []int{1, 1, 1}) + y3 = &fields_bn254.E2{A0: *y30, A1: *y31} return &G2Affine{ P: g2AffP{ diff --git a/std/algebra/emulated/sw_bn254/hints.go b/std/algebra/emulated/sw_bn254/hints.go index 3c4d5642f..c73329d82 100644 --- a/std/algebra/emulated/sw_bn254/hints.go +++ b/std/algebra/emulated/sw_bn254/hints.go @@ -17,6 +17,7 @@ func init() { func GetHints() []solver.Hint { return []solver.Hint{ millerLoopAndCheckFinalExpHint, + finalExpHint, } } @@ -161,3 +162,126 @@ func millerLoopAndCheckFinalExpHint(nativeMod *big.Int, nativeInputs, nativeOutp return nil }) } + +func finalExpHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { + // This follows section 4.3.2 of https://eprint.iacr.org/2024/640.pdf + return emulated.UnwrapHint(nativeInputs, nativeOutputs, + func(mod *big.Int, inputs, outputs []*big.Int) error { + var tmp, x3, cubicNonResiduePower, x, millerLoop, residueWitness, residueWitnessInv, one, root27thOf1 bn254.E12 + var exp1, exp2, rInv, mInv big.Int + + millerLoop.C0.B0.A0.SetBigInt(inputs[0]) + millerLoop.C0.B0.A1.SetBigInt(inputs[1]) + millerLoop.C0.B1.A0.SetBigInt(inputs[2]) + millerLoop.C0.B1.A1.SetBigInt(inputs[3]) + millerLoop.C0.B2.A0.SetBigInt(inputs[4]) + millerLoop.C0.B2.A1.SetBigInt(inputs[5]) + millerLoop.C1.B0.A0.SetBigInt(inputs[6]) + millerLoop.C1.B0.A1.SetBigInt(inputs[7]) + millerLoop.C1.B1.A0.SetBigInt(inputs[8]) + millerLoop.C1.B1.A1.SetBigInt(inputs[9]) + millerLoop.C1.B2.A0.SetBigInt(inputs[10]) + millerLoop.C1.B2.A1.SetBigInt(inputs[11]) + + // exp1 = (p^12-1)/3 + exp1.SetString("4030969696062745741797811005853058291874379204406359442560681893891674450106959530046539719647151210908190211459382793062006703141168852426020468083171325367934590379984666859998399967609544754664110191464072930598755441160008826659219834762354786403012110463250131961575955268597858015384895449311534622125256548620283853223733396368939858981844663598065852816056384933498610930035891058807598891752166582271931875150099691598048016175399382213304673796601585080509443902692818733420199004555566113537482054218823936116647313678747500267068559627206777530424029211671772692598157901876223857571299238046741502089890557442500582300718504160740314926185458079985126192563953772118929726791041828902047546977272656240744693339962973939047279285351052107950250121751682659529260304162131862468322644288196213423232132152125277136333208005221619443705106431645884840489295409272576227859206166894626854018093044908314720", 10) + // root27thOf1 = (0, c010, c011, 0, 0, 0, 0, 0, 0, 0, 0, 0) + // is a 27-th root of unity which is necessarily a cubic non-residue + // since h/r = (p^12-1)/r = 27·l and 3 does not divide l. + // it was computed as w^((p^12-1)/27) = c2 * w^2 + c8 * w^8 where + // Fp12 = Fp[w]/w^12-18w^6+82 which is isomorphic to our Fp12 tower + // then c010 = (c2 + 9 * c8) % p and c011 = c8 + root27thOf1.C0.B1.A0.SetString("9483667112135124394372960210728142145589475128897916459350428495526310884707") + root27thOf1.C0.B1.A1.SetString("4534159768373982659291990808346042891252278737770656686799127720849666919525") + + if one.Exp(millerLoop, &exp1).IsOne() { + // residueWitness = millerLoop is a cubic residue + cubicNonResiduePower.SetOne() + residueWitness.Set(&millerLoop) + } else if one.Exp(*millerLoop.Mul(&millerLoop, &root27thOf1), &exp1).IsOne() { + // residueWitness = millerLoop * root27thOf1 is a cubic residue + cubicNonResiduePower.Set(&root27thOf1) + residueWitness.Set(&millerLoop) + } else { + // residueWitness = millerLoop * root27thOf1^2 is a cubic residue + cubicNonResiduePower.Square(&root27thOf1) + residueWitness.Mul(&millerLoop, &root27thOf1) + } + + // 1. compute r-th root: + // Exponentiate to rInv where + // rInv = 1/r mod (p^12-1)/r + rInv.SetString("495819184011867778744231927046742333492451180917315223017345540833046880485481720031136878341141903241966521818658471092566752321606779256340158678675679238405722886654128392203338228575623261160538734808887996935946888297414610216445334190959815200956855428635568184508263913274453942864817234480763055154719338281461936129150171789463489422401982681230261920147923652438266934726901346095892093443898852488218812468761027620988447655860644584419583586883569984588067403598284748297179498734419889699245081714359110559679136004228878808158639412436468707589339209058958785568729925402190575720856279605832146553573981587948304340677613460685405477047119496887534881410757668344088436651291444274840864486870663164657544390995506448087189408281061890434467956047582679858345583941396130713046072603335601764495918026585155498301896749919393", 10) + residueWitness.Exp(residueWitness, &rInv) + + // 2. compute m-th root: + // where m = (6x + 2 + q^3 - q^2 + q)/(3r) + // Exponentiate to mInv where + // mInv = 1/m mod p^12-1 + mInv.SetString("17840267520054779749190587238017784600702972825655245554504342129614427201836516118803396948809179149954197175783449826546445899524065131269177708416982407215963288737761615699967145070776364294542559324079147363363059480104341231360692143673915822421222230661528586799190306058519400019024762424366780736540525310403098758015600523609594113357130678138304964034267260758692953579514899054295817541844330584721967571697039986079722203518034173581264955381924826388858518077894154909963532054519350571947910625755075099598588672669612434444513251495355121627496067454526862754597351094345783576387352673894873931328099247263766690688395096280633426669535619271711975898132416216382905928886703963310231865346128293216316379527200971959980873989485521004596686352787540034457467115536116148612884807380187255514888720048664139404687086409399", 10) + residueWitness.Exp(residueWitness, &mInv) + + // 3. compute cube root: + // since gcd(3, (p^12-1)/r) ≠ 1 we use a modified Toneelli-Shanks algorithm + // see Alg.4 of https://eprint.iacr.org/2024/640.pdf + // Typo in the paper: p^k-1 = 3^n * s instead of p-1 = 3^r * s + // where k=12 and n=3 here and exp2 = (s+1)/3 + residueWitnessInv.Inverse(&residueWitness) + exp2.SetString("149295173928249842288807815031594751550902933496531831205951181255247201855813315927649619246190785589192230054051214557852100116339587126889646966043382421034614458517950624444385183985538694617189266350521219651805757080000326913304438324531658755667115202342597480058368713651772519088329461085612393412046538837788290860138273939590365147475728281409846400594680923462911515927255224400281440435265428973034513894448136725853630228718495637529802733207466114092942366766400693830377740909465411612499335341437923559875826432546203713595131838044695464089778859691547136762894737106526809539677749557286722299625576201574095640767352005953344997266128077036486155280146436004404804695964512181557316554713802082990544197776406442186936269827816744738898152657469728130713344598597476387715653492155415311971560450078713968012341037230430349766855793764662401499603533676762082513303932107208402000670112774382027", 10) + x.Exp(residueWitness, &exp2) + + // 3^t is ord(x^3 / residueWitness) + x3.Square(&x).Mul(&x3, &x).Mul(&x3, &residueWitnessInv) + t := 0 + for !x3.IsOne() { + t++ + tmp.Square(&x3) + x3.Mul(&tmp, &x3) + } + + for t != 0 { + x.Mul(&x, tmp.Exp(root27thOf1, &exp2)) + + // 3^t is ord(x^3 / residueWitness) + x3.Square(&x).Mul(&x3, &x).Mul(&x3, &residueWitnessInv) + t = 0 + for !x3.IsOne() { + t++ + tmp.Square(&x3) + x3.Mul(&tmp, &x3) + } + } + + // x is now the cube root of residueWitness + residueWitness.Set(&x) + + residueWitness.C0.B0.A0.BigInt(outputs[0]) + residueWitness.C0.B0.A1.BigInt(outputs[1]) + residueWitness.C0.B1.A0.BigInt(outputs[2]) + residueWitness.C0.B1.A1.BigInt(outputs[3]) + residueWitness.C0.B2.A0.BigInt(outputs[4]) + residueWitness.C0.B2.A1.BigInt(outputs[5]) + residueWitness.C1.B0.A0.BigInt(outputs[6]) + residueWitness.C1.B0.A1.BigInt(outputs[7]) + residueWitness.C1.B1.A0.BigInt(outputs[8]) + residueWitness.C1.B1.A1.BigInt(outputs[9]) + residueWitness.C1.B2.A0.BigInt(outputs[10]) + residueWitness.C1.B2.A1.BigInt(outputs[11]) + + // we also need to return the cubic non-residue power + cubicNonResiduePower.C0.B0.A0.BigInt(outputs[12]) + cubicNonResiduePower.C0.B0.A1.BigInt(outputs[13]) + cubicNonResiduePower.C0.B1.A0.BigInt(outputs[14]) + cubicNonResiduePower.C0.B1.A1.BigInt(outputs[15]) + cubicNonResiduePower.C0.B2.A0.BigInt(outputs[16]) + cubicNonResiduePower.C0.B2.A1.BigInt(outputs[17]) + cubicNonResiduePower.C1.B0.A0.BigInt(outputs[18]) + cubicNonResiduePower.C1.B0.A1.BigInt(outputs[19]) + cubicNonResiduePower.C1.B1.A0.BigInt(outputs[20]) + cubicNonResiduePower.C1.B1.A1.BigInt(outputs[21]) + cubicNonResiduePower.C1.B2.A0.BigInt(outputs[22]) + cubicNonResiduePower.C1.B2.A1.BigInt(outputs[23]) + + return nil + }) +} diff --git a/std/algebra/emulated/sw_bn254/pairing.go b/std/algebra/emulated/sw_bn254/pairing.go index def19d265..fe0d13b80 100644 --- a/std/algebra/emulated/sw_bn254/pairing.go +++ b/std/algebra/emulated/sw_bn254/pairing.go @@ -6,54 +6,54 @@ import ( "math/big" "github.com/consensys/gnark-crypto/ecc/bn254" + fp_bn "github.com/consensys/gnark-crypto/ecc/bn254/fp" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/algebra/emulated/fields_bn254" "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" "github.com/consensys/gnark/std/math/emulated" ) +type baseEl = emulated.Element[BaseField] +type GTEl = fields_bn254.E12 + type Pairing struct { api frontend.API *fields_bn254.Ext12 + *fields_bn254.Ext2 curveF *emulated.Field[BaseField] curve *sw_emulated.Curve[BaseField, ScalarField] g2 *G2 bTwist *fields_bn254.E2 - g2gen *G2Affine } -type GTEl = fields_bn254.E12 +func NewGTEl(a bn254.GT) GTEl { + var c0, c1, c2, c3, c4, c5, t fp_bn.Element + t.SetUint64(9).Mul(&t, &a.C0.B0.A1) + c0.Sub(&a.C0.B0.A0, &t) + t.SetUint64(9).Mul(&t, &a.C1.B0.A1) + c1.Sub(&a.C1.B0.A0, &t) + t.SetUint64(9).Mul(&t, &a.C0.B1.A1) + c2.Sub(&a.C0.B1.A0, &t) + t.SetUint64(9).Mul(&t, &a.C1.B1.A1) + c3.Sub(&a.C1.B1.A0, &t) + t.SetUint64(9).Mul(&t, &a.C0.B2.A1) + c4.Sub(&a.C0.B2.A0, &t) + t.SetUint64(9).Mul(&t, &a.C1.B2.A1) + c5.Sub(&a.C1.B2.A0, &t) -func NewGTEl(v bn254.GT) GTEl { return GTEl{ - C0: fields_bn254.E6{ - B0: fields_bn254.E2{ - A0: emulated.ValueOf[BaseField](v.C0.B0.A0), - A1: emulated.ValueOf[BaseField](v.C0.B0.A1), - }, - B1: fields_bn254.E2{ - A0: emulated.ValueOf[BaseField](v.C0.B1.A0), - A1: emulated.ValueOf[BaseField](v.C0.B1.A1), - }, - B2: fields_bn254.E2{ - A0: emulated.ValueOf[BaseField](v.C0.B2.A0), - A1: emulated.ValueOf[BaseField](v.C0.B2.A1), - }, - }, - C1: fields_bn254.E6{ - B0: fields_bn254.E2{ - A0: emulated.ValueOf[BaseField](v.C1.B0.A0), - A1: emulated.ValueOf[BaseField](v.C1.B0.A1), - }, - B1: fields_bn254.E2{ - A0: emulated.ValueOf[BaseField](v.C1.B1.A0), - A1: emulated.ValueOf[BaseField](v.C1.B1.A1), - }, - B2: fields_bn254.E2{ - A0: emulated.ValueOf[BaseField](v.C1.B2.A0), - A1: emulated.ValueOf[BaseField](v.C1.B2.A1), - }, - }, + A0: emulated.ValueOf[emulated.BN254Fp](c0), + A1: emulated.ValueOf[emulated.BN254Fp](c1), + A2: emulated.ValueOf[emulated.BN254Fp](c2), + A3: emulated.ValueOf[emulated.BN254Fp](c3), + A4: emulated.ValueOf[emulated.BN254Fp](c4), + A5: emulated.ValueOf[emulated.BN254Fp](c5), + A6: emulated.ValueOf[emulated.BN254Fp](a.C0.B0.A1), + A7: emulated.ValueOf[emulated.BN254Fp](a.C1.B0.A1), + A8: emulated.ValueOf[emulated.BN254Fp](a.C0.B1.A1), + A9: emulated.ValueOf[emulated.BN254Fp](a.C1.B1.A1), + A10: emulated.ValueOf[emulated.BN254Fp](a.C0.B2.A1), + A11: emulated.ValueOf[emulated.BN254Fp](a.C1.B2.A1), } } @@ -73,6 +73,7 @@ func NewPairing(api frontend.API) (*Pairing, error) { return &Pairing{ api: api, Ext12: fields_bn254.NewExt12(api), + Ext2: fields_bn254.NewExt2(api), curveF: ba, curve: curve, g2: NewG2(api), @@ -80,161 +81,129 @@ func NewPairing(api frontend.API) (*Pairing, error) { }, nil } -func (pr Pairing) generators() *G2Affine { - if pr.g2gen == nil { - _, _, _, g2gen := bn254.Generators() - cg2gen := NewG2AffineFixed(g2gen) - pr.g2gen = &cg2gen +// Pair calculates the reduced pairing for a set of points +// ∏ᵢ e(Pᵢ, Qᵢ). +// +// This function doesn't check that the inputs are in the correct subgroups. See AssertIsOnG1 and AssertIsOnG2. +func (pr Pairing) Pair(P []*G1Affine, Q []*G2Affine) (*GTEl, error) { + res, err := pr.MillerLoop(P, Q) + if err != nil { + return nil, fmt.Errorf("miller loop: %w", err) } - return pr.g2gen + res = pr.FinalExponentiation(res) + return res, nil } -// FinalExponentiation computes the exponentiation eᵈ where -// -// d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r. -// -// We use instead d'= s ⋅ d, where s is the cofactor -// -// 2x₀(6x₀²+3x₀+1) -// -// and r does NOT divide d' -// -// FinalExponentiation returns a decompressed element in E12. -// -// This is the safe version of the method where e may be {-1,1}. If it is known -// that e ≠ {-1,1} then using the unsafe version of the method saves -// considerable amount of constraints. When called with the result of -// [MillerLoop], then current method is applicable when length of the inputs to -// Miller loop is 1. +// FinalExponentiation computes the exponentiation (∏ᵢ zᵢ)ᵈ +// where d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r +// we use instead d=s ⋅ (p⁶-1)(p²+1)(p⁴ - p² +1)/r +// where s is the cofactor 2x₀(6x₀²+3x₀+1) func (pr Pairing) FinalExponentiation(e *GTEl) *GTEl { - return pr.finalExponentiation(e, false) -} - -// FinalExponentiationUnsafe computes the exponentiation eᵈ where -// -// d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r. -// -// We use instead d'= s ⋅ d, where s is the cofactor -// -// 2x₀(6x₀²+3x₀+1) -// -// and r does NOT divide d' -// -// FinalExponentiationUnsafe returns a decompressed element in E12. -// -// This is the unsafe version of the method where e may NOT be {-1,1}. If e ∈ -// {-1, 1}, then there exists no valid solution to the circuit. This method is -// applicable when called with the result of [MillerLoop] method when the length -// of the inputs to Miller loop is 1. -func (pr Pairing) FinalExponentiationUnsafe(e *GTEl) *GTEl { - return pr.finalExponentiation(e, true) -} - -// finalExponentiation computes the exponentiation eᵈ where -// -// d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r. -// -// We use instead d'= s ⋅ d, where s is the cofactor -// -// 2x₀(6x₀²+3x₀+1) -// -// and r does NOT divide d' -// -// finalExponentiation returns a decompressed element in E12 -func (pr Pairing) finalExponentiation(e *GTEl, unsafe bool) *GTEl { - - // 1. Easy part + // Easy part // (p⁶-1)(p²+1) - var selector1, selector2 frontend.Variable - _dummy := pr.Ext6.One() - - if unsafe { - // The Miller loop result is ≠ {-1,1}, otherwise this means P and Q are - // linearly dependent and not from G1 and G2 respectively. - // So e ∈ G_{q,2} \ {-1,1} and hence e.C1 ≠ 0. - // Nothing to do. + t0 := pr.Ext12.Conjugate(e) + e = pr.Ext12.Inverse(e) + t0 = pr.Ext12.Mul(t0, e) + e = pr.Ext12.FrobeniusSquare(t0) + e = pr.Ext12.Mul(e, t0) - } else { - // However, for a product of Miller loops (n>=2) this might happen. If this is - // the case, the result is 1 in the torus. We assign a dummy value (1) to e.C1 - // and proceed further. - selector1 = pr.Ext6.IsZero(&e.C1) - e.C1.B0.A0 = *pr.curveF.Select(selector1, pr.curveF.One(), &e.C1.B0.A0) - } - - // Torus compression absorbed: - // Raising e to (p⁶-1) is - // e^(p⁶) / e = (e.C0 - w*e.C1) / (e.C0 + w*e.C1) - // = (-e.C0/e.C1 + w) / (-e.C0/e.C1 - w) - // So the fraction -e.C0/e.C1 is already in the torus. - // This absorbs the torus compression in the easy part. - c := pr.Ext6.DivUnchecked(&e.C0, &e.C1) - c = pr.Ext6.Neg(c) - t0 := pr.FrobeniusSquareTorus(c) - c = pr.MulTorus(t0, c) - - // 2. Hard part (up to permutation) + // Hard part (up to permutation) // 2x₀(6x₀²+3x₀+1)(p⁴-p²+1)/r // Duquesne and Ghammam // https://eprint.iacr.org/2015/192.pdf // Fuentes et al. (alg. 6) - // performed in torus compressed form - t0 = pr.ExptTorus(c) - t0 = pr.InverseTorus(t0) - t0 = pr.SquareTorus(t0) - t1 := pr.SquareTorus(t0) - t1 = pr.MulTorus(t0, t1) - t2 := pr.ExptTorus(t1) - t2 = pr.InverseTorus(t2) - t3 := pr.InverseTorus(t1) - t1 = pr.MulTorus(t2, t3) - t3 = pr.SquareTorus(t2) - t4 := pr.ExptTorus(t3) - t4 = pr.MulTorus(t1, t4) - t3 = pr.MulTorus(t0, t4) - t0 = pr.MulTorus(t2, t4) - t0 = pr.MulTorus(c, t0) - t2 = pr.FrobeniusTorus(t3) - t0 = pr.MulTorus(t2, t0) - t2 = pr.FrobeniusSquareTorus(t4) - t0 = pr.MulTorus(t2, t0) - t2 = pr.InverseTorus(c) - t2 = pr.MulTorus(t2, t3) - t2 = pr.FrobeniusCubeTorus(t2) - - var result GTEl - // MulTorus(t0, t2) requires t0 ≠ -t2. When t0 = -t2, it means the - // product is 1 in the torus. - if unsafe { - // For a single pairing, this does not happen because the pairing is non-degenerate. - result = *pr.DecompressTorus(pr.MulTorus(t2, t0)) - } else { - // For a product of pairings this might happen when the result is expected to be 1. - // We assign a dummy value (1) to t0 and proceed further. - // Finally we do a select on both edge cases: - // - Only if seletor1=0 and selector2=0, we return MulTorus(t2, t0) decompressed. - // - Otherwise, we return 1. - _sum := pr.Ext6.Add(t0, t2) - selector2 = pr.Ext6.IsZero(_sum) - t0 = pr.Ext6.Select(selector2, _dummy, t0) - selector := pr.api.Mul(pr.api.Sub(1, selector1), pr.api.Sub(1, selector2)) - result = *pr.Select(selector, pr.DecompressTorus(pr.MulTorus(t2, t0)), pr.One()) - } - - return &result + t0 = pr.Ext12.Expt(e) + t0 = pr.Ext12.Conjugate(t0) + t0 = pr.Ext12.CyclotomicSquareGS(t0) + t1 := pr.Ext12.CyclotomicSquareGS(t0) + t1 = pr.Ext12.Mul(t0, t1) + t2 := pr.Ext12.Expt(t1) + t2 = pr.Ext12.Conjugate(t2) + t3 := pr.Ext12.Conjugate(t1) + t1 = pr.Ext12.Mul(t2, t3) + t3 = pr.Ext12.CyclotomicSquareGS(t2) + t4 := pr.Ext12.Expt(t3) + t4 = pr.Ext12.Mul(t1, t4) + t3 = pr.Ext12.Mul(t0, t4) + t0 = pr.Ext12.Mul(t2, t4) + t0 = pr.Ext12.Mul(e, t0) + t2 = pr.Ext12.Frobenius(t3) + t0 = pr.Ext12.Mul(t2, t0) + t2 = pr.Ext12.FrobeniusSquare(t4) + t0 = pr.Ext12.Mul(t2, t0) + t2 = pr.Ext12.Conjugate(e) + t2 = pr.Ext12.Mul(t2, t3) + t2 = pr.Ext12.FrobeniusCube(t2) + t0 = pr.Ext12.Mul(t2, t0) + + return t0 } -// Pair calculates the reduced pairing for a set of points -// ∏ᵢ e(Pᵢ, Qᵢ). +// AssertFinalExponentiationIsOne checks that a Miller function output x lies in the +// same equivalence class as the reduced pairing. This replaces the final +// exponentiation step in-circuit. +// The method follows Section 4 of [On Proving Pairings] paper by A. Novakovic and L. Eagen. // -// This function doesn't check that the inputs are in the correct subgroups. See AssertIsOnG1 and AssertIsOnG2. -func (pr Pairing) Pair(P []*G1Affine, Q []*G2Affine) (*GTEl, error) { - res, err := pr.MillerLoop(P, Q) +// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf +func (pr Pairing) AssertFinalExponentiationIsOne(a *GTEl) { + tower := pr.Ext12.ToTower(a) + + res, err := pr.curveF.NewHint(finalExpHint, 24, 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 { - return nil, fmt.Errorf("miller loop: %w", err) + // err is non-nil only for invalid number of inputs + panic(err) } - res = pr.finalExponentiation(res, len(P) == 1) - return res, nil + + nine := big.NewInt(9) + residueWitness := pr.Ext12.FromTower([12]*baseEl{res[0], res[1], res[2], res[3], res[4], res[5], res[6], res[7], res[8], res[9], res[10], res[11]}) + + // constrain cubicNonResiduePower to be in Fp6 + // that is: a100=a101=a110=a111=a120=a121=0 + // or + // A0 = a000 - 9 * a001 + // A1 = 0 + // A2 = a010 - 9 * a011 + // A3 = 0 + // A4 = a020 - 9 * a021 + // A5 = 0 + // A6 = a001 + // A7 = 0 + // A8 = a011 + // A9 = 0 + // A10 = a021 + // A11 = 0 + cubicNonResiduePower := GTEl{ + A0: *pr.curveF.Sub(res[12], pr.curveF.MulConst(res[13], nine)), + A1: *pr.curveF.Zero(), + A2: *pr.curveF.Sub(res[14], pr.curveF.MulConst(res[15], nine)), + A3: *pr.curveF.Zero(), + A4: *pr.curveF.Sub(res[16], pr.curveF.MulConst(res[17], nine)), + A5: *pr.curveF.Zero(), + A6: *res[13], + A7: *pr.curveF.Zero(), + A8: *res[15], + A9: *pr.curveF.Zero(), + A10: *res[17], + A11: *pr.curveF.Zero(), + } + + // Check that x * cubicNonResiduePower == residueWitness^λ + // where λ = 6u + 2 + q^3 - q^2 + q, with u the BN254 seed + // and residueWitness, cubicNonResiduePower from the hint. + t2 := pr.Ext12.Mul(&cubicNonResiduePower, a) + + t1 := pr.Ext12.FrobeniusCube(residueWitness) + t0 := pr.Ext12.FrobeniusSquare(residueWitness) + t1 = pr.Ext12.DivUnchecked(t1, t0) + t0 = pr.Ext12.Frobenius(residueWitness) + t1 = pr.Ext12.Mul(t1, t0) + + // exponentiation by U=6u+2 + t0 = pr.Ext12.ExpByU(residueWitness) + + t0 = pr.Ext12.Mul(t0, t1) + + pr.AssertIsEqual(t0, t2) } // PairingCheck calculates the reduced pairing for a set of points and asserts if the result is One @@ -247,15 +216,6 @@ func (pr Pairing) PairingCheck(P []*G1Affine, Q []*G2Affine) error { return err } - // We perform the easy part of the final exp to push f to the cyclotomic - // subgroup so that AssertFinalExponentiationIsOne is carried with optimized - // cyclotomic squaring (e.g. Karabina12345). - // - // f = f^(p⁶-1)(p²+1) - buf := pr.Conjugate(f) - buf = pr.DivUnchecked(buf, f) - f = pr.FrobeniusSquare(buf) - f = pr.Mul(f, buf) pr.AssertFinalExponentiationIsOne(f) @@ -283,8 +243,7 @@ func (pr Pairing) computeTwistEquation(Q *G2Affine) (left, right *fields_bn254.E 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.Cube(&Q.P.X) right = pr.Ext2.Add(right, b) return left, right } @@ -390,8 +349,8 @@ func (pr Pairing) millerLoopLines(P []*G1Affine, lines []lineEvaluations) (*GTEl } // precomputations - yInv := make([]*emulated.Element[BaseField], n) - xNegOverY := make([]*emulated.Element[BaseField], n) + yInv := make([]*baseEl, n) + xNegOverY := make([]*baseEl, n) for k := 0; k < n; k++ { // P are supposed to be on G1 respectively of prime order r. @@ -402,114 +361,89 @@ func (pr Pairing) millerLoopLines(P []*G1Affine, lines []lineEvaluations) (*GTEl xNegOverY[k] = pr.curveF.Neg(xNegOverY[k]) } - var prodLines [5]*fields_bn254.E2 - res := pr.Ext12.One() + var prodLines [10]*baseEl // Compute f_{6x₀+2,Q}(P) - // i = 64, separately to avoid an E12 Square - // (Square(res) = 1² = 1) - - // k = 0, separately to avoid MulBy034 (res × ℓ) - // (assign line to res) - // line evaluation at P[0] - res = &fields_bn254.E12{ - C0: res.C0, - C1: fields_bn254.E6{ - B0: *pr.MulByElement(&lines[0][0][64].R0, xNegOverY[0]), - B1: *pr.MulByElement(&lines[0][0][64].R1, yInv[0]), - B2: res.C1.B2, - }, + // i = 64 + // + // k = 0 + c3 := pr.Ext2.MulByElement(&lines[0][0][64].R0, xNegOverY[0]) + c4 := pr.Ext2.MulByElement(&lines[0][0][64].R1, yInv[0]) + nine := big.NewInt(9) + res := >El{ + A0: *pr.curveF.One(), + A1: *pr.curveF.Sub(&c3.A0, pr.curveF.MulConst(&c3.A1, nine)), + A2: *pr.curveF.Zero(), + A3: *pr.curveF.Sub(&c4.A0, pr.curveF.MulConst(&c4.A1, nine)), + A4: *pr.curveF.Zero(), + A5: *pr.curveF.Zero(), + A6: *pr.curveF.Zero(), + A7: c3.A1, + A8: *pr.curveF.Zero(), + A9: c4.A1, + A10: *pr.curveF.Zero(), + A11: *pr.curveF.Zero(), } if n >= 2 { - // k = 1, separately to avoid MulBy034 (res × ℓ) - // (res is also a line at this point, so we use Mul034By034 ℓ × ℓ) + // k = 1, separately to avoid MulBy01379 (res × ℓ) + // (res is also a line at this point, so we use Mul01379By01379 ℓ × ℓ) // line evaluation at P[1] - // ℓ × res - prodLines = pr.Mul034By034( - pr.MulByElement(&lines[1][0][64].R0, xNegOverY[1]), - pr.MulByElement(&lines[1][0][64].R1, yInv[1]), - &res.C1.B0, - &res.C1.B1, + prodLines = pr.Mul01379By01379( + pr.Ext2.MulByElement(&lines[1][0][64].R0, xNegOverY[1]), + pr.Ext2.MulByElement(&lines[1][0][64].R1, yInv[1]), + c3, + c4, ) - res = &fields_bn254.E12{ - C0: fields_bn254.E6{ - B0: *prodLines[0], - B1: *prodLines[1], - B2: *prodLines[2], - }, - C1: fields_bn254.E6{ - B0: *prodLines[3], - B1: *prodLines[4], - B2: res.C1.B2, - }, + res = >El{ + A0: *prodLines[0], + A1: *prodLines[1], + A2: *prodLines[2], + A3: *prodLines[3], + A4: *prodLines[4], + A5: *pr.curveF.Zero(), + A6: *prodLines[5], + A7: *prodLines[6], + A8: *prodLines[7], + A9: *prodLines[8], + A10: *prodLines[9], + A11: *pr.curveF.Zero(), } } if n >= 3 { - // k = 2, separately to avoid MulBy034 (res × ℓ) - // (res has a zero E2 element, so we use Mul01234By034) - // line evaluation at P[1] - // ℓ × res - res = pr.Mul01234By034( - prodLines, - pr.MulByElement(&lines[2][0][64].R0, xNegOverY[2]), - pr.MulByElement(&lines[2][0][64].R1, yInv[2]), - ) - - // k >= 3 - for k := 3; k < n; k++ { + // k >= 2 + for k := 2; k < n; k++ { // line evaluation at P[k] // ℓ × res - res = pr.MulBy034( + res = pr.MulBy01379( res, - pr.MulByElement(&lines[k][0][64].R0, xNegOverY[k]), - pr.MulByElement(&lines[k][0][64].R1, yInv[k]), + pr.Ext2.MulByElement(&lines[k][0][64].R0, xNegOverY[k]), + pr.Ext2.MulByElement(&lines[k][0][64].R1, yInv[k]), ) } } for i := 63; i >= 0; i-- { - res = pr.Square(res) - - if loopCounter[i] == 0 { - // if number of lines is odd, mul last line by res - // works for n=1 as well - if n%2 != 0 { - // ℓ × res - res = pr.MulBy034( + res = pr.Ext12.Square(res) + + for k := 0; k < n; k++ { + if loopCounter[i] == 0 { + res = pr.MulBy01379( res, - pr.MulByElement(&lines[n-1][0][i].R0, xNegOverY[n-1]), - pr.MulByElement(&lines[n-1][0][i].R1, yInv[n-1]), + pr.Ext2.MulByElement(&lines[k][0][i].R0, xNegOverY[k]), + pr.Ext2.MulByElement(&lines[k][0][i].R1, yInv[k]), ) - } - - // mul lines 2-by-2 - for k := 1; k < n; k += 2 { + } else { // ℓ × ℓ - prodLines = pr.Mul034By034( - pr.MulByElement(&lines[k][0][i].R0, xNegOverY[k]), - pr.MulByElement(&lines[k][0][i].R1, yInv[k]), - pr.MulByElement(&lines[k-1][0][i].R0, xNegOverY[k-1]), - pr.MulByElement(&lines[k-1][0][i].R1, yInv[k-1]), - ) - // (ℓ × ℓ) × res - res = pr.MulBy01234(res, prodLines) - } - - } else { - for k := 0; k < n; k++ { - // lines evaluations at P - // and ℓ × ℓ - prodLines := pr.Mul034By034( - pr.MulByElement(&lines[k][0][i].R0, xNegOverY[k]), - pr.MulByElement(&lines[k][0][i].R1, yInv[k]), - pr.MulByElement(&lines[k][1][i].R0, xNegOverY[k]), - pr.MulByElement(&lines[k][1][i].R1, yInv[k]), + prodLines = pr.Mul01379By01379( + pr.Ext2.MulByElement(&lines[k][0][i].R0, xNegOverY[k]), + pr.Ext2.MulByElement(&lines[k][0][i].R1, yInv[k]), + pr.Ext2.MulByElement(&lines[k][1][i].R0, xNegOverY[k]), + pr.Ext2.MulByElement(&lines[k][1][i].R1, yInv[k]), ) // (ℓ × ℓ) × res - res = pr.MulBy01234(res, prodLines) - + res = pr.Ext12.MulBy012346789(res, prodLines) } } } @@ -518,14 +452,13 @@ func (pr Pairing) millerLoopLines(P []*G1Affine, lines []lineEvaluations) (*GTEl // lines evaluations at P // and ℓ × ℓ for k := 0; k < n; k++ { - prodLines := pr.Mul034By034( - pr.MulByElement(&lines[k][0][65].R0, xNegOverY[k]), - pr.MulByElement(&lines[k][0][65].R1, yInv[k]), - pr.MulByElement(&lines[k][1][65].R0, xNegOverY[k]), - pr.MulByElement(&lines[k][1][65].R1, yInv[k]), + prodLines = pr.Mul01379By01379( + pr.Ext2.MulByElement(&lines[k][0][65].R0, xNegOverY[k]), + pr.Ext2.MulByElement(&lines[k][0][65].R1, yInv[k]), + pr.Ext2.MulByElement(&lines[k][1][65].R0, xNegOverY[k]), + pr.Ext2.MulByElement(&lines[k][1][65].R1, yInv[k]), ) - // (ℓ × ℓ) × res - res = pr.MulBy01234(res, prodLines) + res = pr.Ext12.MulBy012346789(res, prodLines) } return res, nil @@ -538,6 +471,7 @@ func (pr Pairing) doubleAndAddStep(p1, p2 *g2AffP, isSub bool) (*g2AffP, *lineEv var line1, line2 lineEvaluation var p g2AffP + mone := pr.curveF.NewElement(-1) // compute λ1 = (y1-y2)/(x1-x2) or λ1 = (y1+y2)/(x1-x2) if isSub is true var n *fields_bn254.E2 @@ -550,15 +484,16 @@ func (pr Pairing) doubleAndAddStep(p1, p2 *g2AffP, isSub bool) (*g2AffP, *lineEv λ1 := pr.Ext2.DivUnchecked(n, d) // compute x3 =λ1²-x1-x2 - x3 := pr.Ext2.Square(λ1) - x3 = pr.Ext2.Sub(x3, pr.Ext2.Add(&p1.X, &p2.X)) + x30 := pr.curveF.Eval([][]*baseEl{{&λ1.A0, &λ1.A0}, {mone, &λ1.A1, &λ1.A1}, {mone, &p1.X.A0}, {mone, &p2.X.A0}}, []int{1, 1, 1, 1}) + x31 := pr.curveF.Eval([][]*baseEl{{&λ1.A0, &λ1.A1}, {mone, &p1.X.A1}, {mone, &p2.X.A1}}, []int{2, 1, 1}) + x3 := &fields_bn254.E2{A0: *x30, A1: *x31} // omit y3 computation // compute line1 line1.R0 = *λ1 - line1.R1 = *pr.Ext2.Mul(λ1, &p1.X) - line1.R1 = *pr.Ext2.Sub(&line1.R1, &p1.Y) + line1.R1.A0 = *pr.curveF.Eval([][]*baseEl{{&λ1.A0, &p1.X.A0}, {mone, &λ1.A1, &p1.X.A1}, {mone, &p1.Y.A0}}, []int{1, 1, 1}) + line1.R1.A1 = *pr.curveF.Eval([][]*baseEl{{&λ1.A0, &p1.X.A1}, {&λ1.A1, &p1.X.A0}, {mone, &p1.Y.A1}}, []int{1, 1, 1}) // compute λ2 = -λ1-2y1/(x3-x1) n = pr.Ext2.MulByConstElement(&p1.Y, big.NewInt(2)) @@ -568,21 +503,23 @@ func (pr Pairing) doubleAndAddStep(p1, p2 *g2AffP, isSub bool) (*g2AffP, *lineEv λ2 = pr.Ext2.Neg(λ2) // compute x4 = λ2²-x1-x3 - x4 := pr.Ext2.Square(λ2) - x4 = pr.Ext2.Sub(x4, pr.Ext2.Add(&p1.X, x3)) + x40 := pr.curveF.Eval([][]*baseEl{{&λ2.A0, &λ2.A0}, {mone, &λ2.A1, &λ2.A1}, {mone, &p1.X.A0}, {mone, x30}}, []int{1, 1, 1, 1}) + x41 := pr.curveF.Eval([][]*baseEl{{&λ2.A0, &λ2.A1}, {mone, &p1.X.A1}, {mone, x31}}, []int{2, 1, 1}) + x4 := &fields_bn254.E2{A0: *x40, A1: *x41} // compute y4 = λ2(x1 - x4)-y1 y4 := pr.Ext2.Sub(&p1.X, x4) - y4 = pr.Ext2.Mul(λ2, y4) - y4 = pr.Ext2.Sub(y4, &p1.Y) + y40 := pr.curveF.Eval([][]*baseEl{{&λ2.A0, &y4.A0}, {mone, &λ2.A1, &y4.A1}, {mone, &p1.Y.A0}}, []int{1, 1, 1}) + y41 := pr.curveF.Eval([][]*baseEl{{&λ2.A0, &y4.A1}, {&λ2.A1, &y4.A0}, {mone, &p1.Y.A1}}, []int{1, 1, 1}) + y4 = &fields_bn254.E2{A0: *y40, A1: *y41} p.X = *x4 p.Y = *y4 // compute line2 line2.R0 = *λ2 - line2.R1 = *pr.Ext2.Mul(λ2, &p1.X) - line2.R1 = *pr.Ext2.Sub(&line2.R1, &p1.Y) + line2.R1.A0 = *pr.curveF.Eval([][]*baseEl{{&λ2.A0, &p1.X.A0}, {mone, &λ2.A1, &p1.X.A1}, {mone, &p1.Y.A0}}, []int{1, 1, 1}) + line2.R1.A1 = *pr.curveF.Eval([][]*baseEl{{&λ2.A0, &p1.X.A1}, {&λ2.A1, &p1.X.A0}, {mone, &p1.Y.A1}}, []int{1, 1, 1}) return &p, &line1, &line2 } @@ -593,6 +530,7 @@ func (pr Pairing) doubleStep(p1 *g2AffP) (*g2AffP, *lineEvaluation) { var p g2AffP var line lineEvaluation + mone := pr.curveF.NewElement(-1) // λ = 3x²/2y n := pr.Ext2.Square(&p1.X) @@ -601,20 +539,22 @@ func (pr Pairing) doubleStep(p1 *g2AffP) (*g2AffP, *lineEvaluation) { λ := pr.Ext2.DivUnchecked(n, d) // xr = λ²-2x - xr := pr.Ext2.Square(λ) - xr = pr.Ext2.Sub(xr, pr.Ext2.MulByConstElement(&p1.X, big.NewInt(2))) + xr0 := pr.curveF.Eval([][]*baseEl{{&λ.A0, &λ.A0}, {mone, &λ.A1, &λ.A1}, {mone, &p1.X.A0}}, []int{1, 1, 2}) + xr1 := pr.curveF.Eval([][]*baseEl{{&λ.A0, &λ.A1}, {mone, &p1.X.A1}}, []int{2, 2}) + xr := &fields_bn254.E2{A0: *xr0, A1: *xr1} // yr = λ(x-xr)-y yr := pr.Ext2.Sub(&p1.X, xr) - yr = pr.Ext2.Mul(λ, yr) - yr = pr.Ext2.Sub(yr, &p1.Y) + yr0 := pr.curveF.Eval([][]*baseEl{{&λ.A0, &yr.A0}, {mone, &λ.A1, &yr.A1}, {mone, &p1.Y.A0}}, []int{1, 1, 1}) + yr1 := pr.curveF.Eval([][]*baseEl{{&λ.A0, &yr.A1}, {&λ.A1, &yr.A0}, {mone, &p1.Y.A1}}, []int{1, 1, 1}) + yr = &fields_bn254.E2{A0: *yr0, A1: *yr1} p.X = *xr p.Y = *yr line.R0 = *λ - line.R1 = *pr.Ext2.Mul(λ, &p1.X) - line.R1 = *pr.Ext2.Sub(&line.R1, &p1.Y) + 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 &p, &line @@ -624,19 +564,23 @@ func (pr Pairing) doubleStep(p1 *g2AffP) (*g2AffP, *lineEvaluation) { // https://eprint.iacr.org/2022/1162 (Section 6.1) func (pr Pairing) addStep(p1, p2 *g2AffP) (*g2AffP, *lineEvaluation) { + mone := pr.curveF.NewElement(-1) + // compute λ = (y2-y1)/(x2-x1) p2ypy := pr.Ext2.Sub(&p2.Y, &p1.Y) p2xpx := pr.Ext2.Sub(&p2.X, &p1.X) λ := pr.Ext2.DivUnchecked(p2ypy, p2xpx) // xr = λ²-x1-x2 - xr := pr.Ext2.Square(λ) - xr = pr.Ext2.Sub(xr, pr.Ext2.Add(&p1.X, &p2.X)) + xr0 := pr.curveF.Eval([][]*baseEl{{&λ.A0, &λ.A0}, {mone, &λ.A1, &λ.A1}, {mone, &p1.X.A0}, {mone, &p2.X.A0}}, []int{1, 1, 1, 1}) + xr1 := pr.curveF.Eval([][]*baseEl{{&λ.A0, &λ.A1}, {mone, &p1.X.A1}, {mone, &p2.X.A1}}, []int{2, 1, 1}) + xr := &fields_bn254.E2{A0: *xr0, A1: *xr1} // yr = λ(x1-xr) - y1 - pxrx := pr.Ext2.Sub(&p1.X, xr) - λpxrx := pr.Ext2.Mul(λ, pxrx) - yr := pr.Ext2.Sub(λpxrx, &p1.Y) + yr := pr.Ext2.Sub(&p1.X, xr) + yr0 := pr.curveF.Eval([][]*baseEl{{&λ.A0, &yr.A0}, {mone, &λ.A1, &yr.A1}, {mone, &p1.Y.A0}}, []int{1, 1, 1}) + yr1 := pr.curveF.Eval([][]*baseEl{{&λ.A0, &yr.A1}, {&λ.A1, &yr.A0}, {mone, &p1.Y.A1}}, []int{1, 1, 1}) + yr = &fields_bn254.E2{A0: *yr0, A1: *yr1} var res g2AffP res.X = *xr @@ -644,8 +588,8 @@ func (pr Pairing) addStep(p1, p2 *g2AffP) (*g2AffP, *lineEvaluation) { var line lineEvaluation line.R0 = *λ - line.R1 = *pr.Ext2.Mul(λ, &p1.X) - line.R1 = *pr.Ext2.Sub(&line.R1, &p1.Y) + 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 &res, &line @@ -654,6 +598,8 @@ func (pr Pairing) addStep(p1, p2 *g2AffP) (*g2AffP, *lineEvaluation) { // lineCompute computes the line through p1 and p2, but does not compute p1+p2. func (pr Pairing) lineCompute(p1, p2 *g2AffP) *lineEvaluation { + mone := pr.curveF.NewElement(-1) + // compute λ = (y2+y1)/(x2-x1) qypy := pr.Ext2.Add(&p1.Y, &p2.Y) qxpx := pr.Ext2.Sub(&p1.X, &p2.X) @@ -661,8 +607,8 @@ func (pr Pairing) lineCompute(p1, p2 *g2AffP) *lineEvaluation { var line lineEvaluation line.R0 = *λ - line.R1 = *pr.Ext2.Mul(λ, &p1.X) - line.R1 = *pr.Ext2.Sub(&line.R1, &p1.Y) + 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 @@ -677,42 +623,56 @@ func (pr Pairing) MillerLoopAndMul(P *G1Affine, Q *G2Affine, previous *GTEl) (*G if err != nil { return nil, fmt.Errorf("miller loop: %w", err) } - res = pr.Mul(res, previous) + res = pr.Ext12.Mul(res, previous) return res, err } // 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, &previous.C0.B0.A0, &previous.C0.B0.A1, &previous.C0.B1.A0, &previous.C0.B1.A1, &previous.C0.B2.A0, &previous.C0.B2.A1, &previous.C1.B0.A0, &previous.C1.B0.A1, &previous.C1.B1.A0, &previous.C1.B1.A1, &previous.C1.B2.A0, &previous.C1.B2.A1) + 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) } + residueWitness := pr.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]}) - residueWitness := fields_bn254.E12{ - C0: fields_bn254.E6{ - B0: fields_bn254.E2{A0: *hint[0], A1: *hint[1]}, - B1: fields_bn254.E2{A0: *hint[2], A1: *hint[3]}, - B2: fields_bn254.E2{A0: *hint[4], A1: *hint[5]}, - }, - C1: fields_bn254.E6{ - B0: fields_bn254.E2{A0: *hint[6], A1: *hint[7]}, - B1: fields_bn254.E2{A0: *hint[8], A1: *hint[9]}, - B2: fields_bn254.E2{A0: *hint[10], A1: *hint[11]}, - }, - } // constrain cubicNonResiduePower to be in Fp6 - cubicNonResiduePower := fields_bn254.E6{ - B0: fields_bn254.E2{A0: *hint[12], A1: *hint[13]}, - B1: fields_bn254.E2{A0: *hint[14], A1: *hint[15]}, - B2: fields_bn254.E2{A0: *hint[16], A1: *hint[17]}, + // that is: a100=a101=a110=a111=a120=a121=0 + // or + // A0 = a000 - 9 * a001 + // A1 = 0 + // A2 = a010 - 9 * a011 + // A3 = 0 + // A4 = a020 - 9 * a021 + // A5 = 0 + // A6 = a001 + // A7 = 0 + // A8 = a011 + // A9 = 0 + // A10 = a021 + // A11 = 0 + nine := big.NewInt(9) + cubicNonResiduePower := GTEl{ + A0: *pr.curveF.Sub(hint[12], pr.curveF.MulConst(hint[13], nine)), + A1: *pr.curveF.Zero(), + A2: *pr.curveF.Sub(hint[14], pr.curveF.MulConst(hint[15], nine)), + A3: *pr.curveF.Zero(), + A4: *pr.curveF.Sub(hint[16], pr.curveF.MulConst(hint[17], nine)), + 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(), } // residueWitnessInv = 1 / residueWitness - residueWitnessInv := pr.Inverse(&residueWitness) + residueWitnessInv := pr.Ext12.Inverse(residueWitness) if Q.Lines == nil { Qlines := pr.computeLines(&Q.P) @@ -731,42 +691,42 @@ func (pr Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previous // Compute f_{6x₀+2,Q}(P) for i := 64; i >= 0; i-- { - res = pr.Square(res) + res = pr.Ext12.Square(res) switch loopCounter[i] { case 0: // ℓ × res - res = pr.MulBy034( + res = pr.MulBy01379( res, - pr.MulByElement(&lines[0][i].R0, xNegOverY), - pr.MulByElement(&lines[0][i].R1, yInv), + pr.Ext2.MulByElement(&lines[0][i].R0, xNegOverY), + pr.Ext2.MulByElement(&lines[0][i].R1, yInv), ) case 1: // multiply by residueWitnessInv when bit=1 - res = pr.Mul(res, residueWitnessInv) + res = pr.Ext12.Mul(res, residueWitnessInv) // lines evaluations at P // and ℓ × ℓ - prodLines := pr.Mul034By034( - pr.MulByElement(&lines[0][i].R0, xNegOverY), - pr.MulByElement(&lines[0][i].R1, yInv), - pr.MulByElement(&lines[1][i].R0, xNegOverY), - pr.MulByElement(&lines[1][i].R1, yInv), + prodLines := pr.Mul01379By01379( + pr.Ext2.MulByElement(&lines[0][i].R0, xNegOverY), + pr.Ext2.MulByElement(&lines[0][i].R1, yInv), + pr.Ext2.MulByElement(&lines[1][i].R0, xNegOverY), + pr.Ext2.MulByElement(&lines[1][i].R1, yInv), ) // (ℓ × ℓ) × res - res = pr.MulBy01234(res, prodLines) + res = pr.Ext12.MulBy012346789(res, prodLines) case -1: // multiply by residueWitness when bit=-1 - res = pr.Mul(res, &residueWitness) + res = pr.Ext12.Mul(res, residueWitness) // lines evaluations at P // and ℓ × ℓ - prodLines := pr.Mul034By034( - pr.MulByElement(&lines[0][i].R0, xNegOverY), - pr.MulByElement(&lines[0][i].R1, yInv), - pr.MulByElement(&lines[1][i].R0, xNegOverY), - pr.MulByElement(&lines[1][i].R1, yInv), + prodLines := pr.Mul01379By01379( + pr.Ext2.MulByElement(&lines[0][i].R0, xNegOverY), + pr.Ext2.MulByElement(&lines[0][i].R1, yInv), + pr.Ext2.MulByElement(&lines[1][i].R0, xNegOverY), + pr.Ext2.MulByElement(&lines[1][i].R1, yInv), ) // (ℓ × ℓ) × res - res = pr.MulBy01234(res, prodLines) + res = pr.Ext12.MulBy012346789(res, prodLines) default: panic(fmt.Sprintf("invalid loop counter value %d", loopCounter[i])) } @@ -774,36 +734,31 @@ func (pr Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previous // Compute ℓ_{[6x₀+2]Q,π(Q)}(P) · ℓ_{[6x₀+2]Q+π(Q),-π²(Q)}(P) // lines evaluations at P - // and ℓ × ℓ - prodLines := pr.Mul034By034( - pr.MulByElement(&lines[0][65].R0, xNegOverY), - pr.MulByElement(&lines[0][65].R1, yInv), - pr.MulByElement(&lines[1][65].R0, xNegOverY), - pr.MulByElement(&lines[1][65].R1, yInv), + prodLines := pr.Mul01379By01379( + pr.Ext2.MulByElement(&lines[0][65].R0, xNegOverY), + pr.Ext2.MulByElement(&lines[0][65].R1, yInv), + pr.Ext2.MulByElement(&lines[1][65].R0, xNegOverY), + pr.Ext2.MulByElement(&lines[1][65].R1, yInv), ) - // (ℓ × ℓ) × res - res = pr.MulBy01234(res, prodLines) + res = pr.Ext12.MulBy012346789(res, prodLines) // multiply by previous multi-Miller function - res = pr.Mul(res, previous) + res = pr.Ext12.Mul(res, previous) // Check that res * cubicNonResiduePower * residueWitnessInv^λ' == 1 // where λ' = q^3 - q^2 + q, with u the BN254 seed // and residueWitnessInv, cubicNonResiduePower from the hint. // Note that res is already MillerLoop(P,Q) * residueWitnessInv^{6x₀+2} since // we initialized the Miller loop accumulator with residueWitnessInv. - t2 := &fields_bn254.E12{ - C0: *pr.Ext6.Mul(&res.C0, &cubicNonResiduePower), - C1: *pr.Ext6.Mul(&res.C1, &cubicNonResiduePower), - } + t2 := pr.Ext12.Mul(&cubicNonResiduePower, res) t1 := pr.FrobeniusCube(residueWitnessInv) t0 := pr.FrobeniusSquare(residueWitnessInv) - t1 = pr.DivUnchecked(t1, t0) + t1 = pr.Ext12.DivUnchecked(t1, t0) t0 = pr.Frobenius(residueWitnessInv) - t1 = pr.Mul(t1, t0) + t1 = pr.Ext12.Mul(t1, t0) - t2 = pr.Mul(t2, t1) + t2 = pr.Ext12.Mul(t2, t1) return t2 } @@ -821,7 +776,7 @@ func (pr Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previous func (pr Pairing) IsMillerLoopAndFinalExpOne(P *G1Affine, Q *G2Affine, previous *GTEl) frontend.Variable { t2 := pr.millerLoopAndFinalExpResult(P, Q, previous) - res := pr.IsEqual(t2, pr.One()) + res := pr.IsEqual(t2, pr.Ext12.One()) return res } @@ -836,5 +791,5 @@ func (pr Pairing) IsMillerLoopAndFinalExpOne(P *G1Affine, Q *G2Affine, previous // [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.One()) + pr.AssertIsEqual(t2, pr.Ext12.One()) } diff --git a/std/algebra/emulated/sw_bn254/pairing_test.go b/std/algebra/emulated/sw_bn254/pairing_test.go index eb84bf9b9..ae2fe87e8 100644 --- a/std/algebra/emulated/sw_bn254/pairing_test.go +++ b/std/algebra/emulated/sw_bn254/pairing_test.go @@ -33,20 +33,18 @@ func randomG1G2Affines() (bn254.G1Affine, bn254.G2Affine) { return p, q } -type FinalExponentiationCircuit struct { +type FinalExponentiation struct { InGt GTEl Res GTEl } -func (c *FinalExponentiationCircuit) Define(api frontend.API) error { +func (c *FinalExponentiation) Define(api frontend.API) error { pairing, err := NewPairing(api) if err != nil { return fmt.Errorf("new pairing: %w", err) } - res1 := pairing.FinalExponentiation(&c.InGt) - pairing.AssertIsEqual(res1, &c.Res) - res2 := pairing.FinalExponentiationUnsafe(&c.InGt) - pairing.AssertIsEqual(res2, &c.Res) + expected := pairing.FinalExponentiation(&c.InGt) + pairing.AssertIsEqual(expected, &c.Res) return nil } @@ -55,93 +53,45 @@ func TestFinalExponentiationTestSolve(t *testing.T) { var gt bn254.GT gt.SetRandom() res := bn254.FinalExponentiation(>) - witness := FinalExponentiationCircuit{ + witness := FinalExponentiation{ InGt: NewGTEl(gt), Res: NewGTEl(res), } - err := test.IsSolved(&FinalExponentiationCircuit{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&FinalExponentiation{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } -type MillerLoopCircuit struct { - InG1 G1Affine - InG2 G2Affine - Res GTEl -} - -func (c *MillerLoopCircuit) Define(api frontend.API) error { - pairing, err := NewPairing(api) - if err != nil { - return fmt.Errorf("new pairing: %w", err) - } - res, err := pairing.MillerLoop([]*G1Affine{&c.InG1}, []*G2Affine{&c.InG2}) - if err != nil { - return fmt.Errorf("pair: %w", err) - } - pairing.AssertIsEqual(res, &c.Res) - return nil -} - -func TestMillerLoopTestSolve(t *testing.T) { - assert := test.NewAssert(t) - p, q := randomG1G2Affines() - lines := bn254.PrecomputeLines(q) - res, err := bn254.MillerLoopFixedQ( - []bn254.G1Affine{p}, - [][2][len(bn254.LoopCounter)]bn254.LineEvaluationAff{lines}, - ) - assert.NoError(err) - witness := MillerLoopCircuit{ - InG1: NewG1Affine(p), - InG2: NewG2Affine(q), - Res: NewGTEl(res), - } - err = test.IsSolved(&MillerLoopCircuit{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) -} - -type MillerLoopAndMulCircuit struct { - Prev GTEl - P G1Affine - Q G2Affine - Current GTEl +type FinalExponentiationIsOne struct { + InGt GTEl } -func (c *MillerLoopAndMulCircuit) Define(api frontend.API) error { +func (c *FinalExponentiationIsOne) Define(api frontend.API) error { pairing, err := NewPairing(api) if err != nil { return fmt.Errorf("new pairing: %w", err) } - res, err := pairing.MillerLoopAndMul(&c.P, &c.Q, &c.Prev) - if err != nil { - return fmt.Errorf("pair: %w", err) - } - pairing.AssertIsEqual(res, &c.Current) + pairing.AssertFinalExponentiationIsOne(&c.InGt) return nil - } -func TestMillerLoopAndMulTestSolve(t *testing.T) { +func TestFinalExponentiationIsOneTestSolve(t *testing.T) { assert := test.NewAssert(t) - var prev, curr bn254.GT - prev.SetRandom() - p, q := randomG1G2Affines() - lines := bn254.PrecomputeLines(q) - // need to use ML with precomputed lines. Otherwise, the result will be different - mlres, err := bn254.MillerLoopFixedQ( - []bn254.G1Affine{p}, - [][2][len(bn254.LoopCounter)]bn254.LineEvaluationAff{lines}, + // e(a,2b) * e(-2a,b) == 1 + p1, q1 := randomG1G2Affines() + var p2 bn254.G1Affine + p2.Double(&p1).Neg(&p2) + var q2 bn254.G2Affine + q2.Set(&q1) + q1.Double(&q1) + ml, err := bn254.MillerLoop( + []bn254.G1Affine{p1, p2}, + []bn254.G2Affine{q1, q2}, ) assert.NoError(err) - curr.Mul(&prev, &mlres) - - witness := MillerLoopAndMulCircuit{ - Prev: NewGTEl(prev), - P: NewG1Affine(p), - Q: NewG2Affine(q), - Current: NewGTEl(curr), + witness := FinalExponentiationIsOne{ + InGt: NewGTEl(ml), } - err = test.IsSolved(&MillerLoopAndMulCircuit{}, &witness, ecc.BN254.ScalarField()) + err = test.IsSolved(&FinalExponentiationIsOne{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } @@ -156,8 +106,6 @@ func (c *PairCircuit) Define(api frontend.API) error { if err != nil { return fmt.Errorf("new pairing: %w", err) } - pairing.AssertIsOnG1(&c.InG1) - pairing.AssertIsOnG2(&c.InG2) res, err := pairing.Pair([]*G1Affine{&c.InG1}, []*G2Affine{&c.InG2}) if err != nil { return fmt.Errorf("pair: %w", err) @@ -544,65 +492,3 @@ func BenchmarkPairing(b *testing.B) { } }) } - -// bench -func BenchmarkFinalExponentiation(b *testing.B) { - // e(a,2b) * e(-2a,b) == 1 - var gt bn254.GT - gt.SetRandom() - res := bn254.FinalExponentiation(>) - witness := FinalExponentiationCircuit{ - InGt: NewGTEl(gt), - Res: NewGTEl(res), - } - 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, &FinalExponentiationCircuit{}); 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, &FinalExponentiationCircuit{}); 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) - } - } - }) -} diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 2cd9e71a6..7fce40b4c 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -214,14 +214,15 @@ func (c *Curve[B, S]) AssertIsOnCurve(p *AffinePoint[B]) { selector := c.api.And(c.baseApi.IsZero(&p.X), c.baseApi.IsZero(&p.Y)) b := c.baseApi.Select(selector, c.baseApi.Zero(), &c.b) - left := c.baseApi.Mul(&p.Y, &p.Y) - right := c.baseApi.Mul(&p.X, c.baseApi.Mul(&p.X, &p.X)) - right = c.baseApi.Add(right, b) - if c.addA { - ax := c.baseApi.Mul(&c.a, &p.X) - right = c.baseApi.Add(right, ax) + mone := c.baseApi.NewElement(-1) + + var check *emulated.Element[B] + if !c.addA { + check = c.baseApi.Eval([][]*emulated.Element[B]{{&p.X, &p.X, &p.X}, {b}, {mone, &p.Y, &p.Y}}, []int{1, 1, 1}) + } else { + check = c.baseApi.Eval([][]*emulated.Element[B]{{&p.X, &p.X, &p.X}, {&c.a, &p.X}, {b}, {mone, &p.Y, &p.Y}}, []int{1, 1, 1, 1}) } - c.baseApi.AssertIsEqual(left, right) + c.baseApi.AssertIsEqual(check, c.baseApi.Zero()) } // AddUnified adds p and q and returns it. It doesn't modify p nor q. diff --git a/std/evmprecompiles/08-bnpairing.go b/std/evmprecompiles/08-bnpairing.go index 4a31e1f05..25ab396b0 100644 --- a/std/evmprecompiles/08-bnpairing.go +++ b/std/evmprecompiles/08-bnpairing.go @@ -47,7 +47,7 @@ func ECPair(api frontend.API, P []*sw_bn254.G1Affine, Q []*sw_bn254.G2Affine) { } // 3- Check that ∏ᵢ e(Pᵢ, Qᵢ) == 1 - ml := pair.One() + ml := pair.Ext12.One() for i := 0; i < n-1; i++ { // fixed circuit 1 ml, err = pair.MillerLoopAndMul(P[i], Q[i], ml) From 148d5ec1025395291ce51d64d1d7a02809e463ab Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Sat, 14 Dec 2024 11:04:02 -0500 Subject: [PATCH 04/12] feat(bls12-381): pairing using direct Fp12 + non-native `Eval()` (#1349) --- internal/stats/latest_stats.csv | 4 +- std/algebra/emulated/fields_bls12381/doc.go | 6 +- std/algebra/emulated/fields_bls12381/e12.go | 791 ++++++++++++++---- .../emulated/fields_bls12381/e12_pairing.go | 496 ++--------- .../emulated/fields_bls12381/e12_test.go | 387 +++------ std/algebra/emulated/fields_bls12381/e2.go | 34 +- std/algebra/emulated/fields_bls12381/e6.go | 465 ---------- .../emulated/fields_bls12381/e6_test.go | 360 -------- std/algebra/emulated/fields_bls12381/hints.go | 395 +++------ std/algebra/emulated/sw_bls12381/hints.go | 128 +++ std/algebra/emulated/sw_bls12381/pairing.go | 436 +++++----- .../emulated/sw_bls12381/pairing_test.go | 101 +-- 12 files changed, 1327 insertions(+), 2276 deletions(-) delete mode 100644 std/algebra/emulated/fields_bls12381/e6.go delete mode 100644 std/algebra/emulated/fields_bls12381/e6_test.go create mode 100644 std/algebra/emulated/sw_bls12381/hints.go diff --git a/internal/stats/latest_stats.csv b/internal/stats/latest_stats.csv index 5b272ec86..3062542cb 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,1419904,2366999 +pairing_bls12381,bn254,groth16,947528,1567714 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,5593770,5250897 +pairing_bls12381,bn254,plonk,3642638,3233378 pairing_bls12381,bls12_377,plonk,0,0 pairing_bls12381,bls12_381,plonk,0,0 pairing_bls12381,bls24_315,plonk,0,0 diff --git a/std/algebra/emulated/fields_bls12381/doc.go b/std/algebra/emulated/fields_bls12381/doc.go index c94c08b68..f61b20877 100644 --- a/std/algebra/emulated/fields_bls12381/doc.go +++ b/std/algebra/emulated/fields_bls12381/doc.go @@ -1,6 +1,10 @@ -// Package fields_bls12381 implements the fields arithmetic of the Fp12 tower +// Package fields_bls12381 implements the fields arithmetic of the direct 𝔽p¹² extension // used to compute the pairing over the BLS12-381 curve. // +// 𝔽p¹²[i] = 𝔽p/i¹²-2i⁶+2 +// +// This direct tower is isomorphic to the 2-3-2 tower: +// // 𝔽p²[u] = 𝔽p/u²+1 // 𝔽p⁶[v] = 𝔽p²/v³-1-u // 𝔽p¹²[w] = 𝔽p⁶/w²-v diff --git a/std/algebra/emulated/fields_bls12381/e12.go b/std/algebra/emulated/fields_bls12381/e12.go index dbc394db1..07edffc9e 100644 --- a/std/algebra/emulated/fields_bls12381/e12.go +++ b/std/algebra/emulated/fields_bls12381/e12.go @@ -1,211 +1,397 @@ package fields_bls12381 import ( + "math/big" + 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" ) type E12 struct { - C0, C1 E6 + A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11 baseEl } type Ext12 struct { - *Ext6 + *Ext2 + api frontend.API + fp *curveF } func NewExt12(api frontend.API) *Ext12 { - return &Ext12{Ext6: NewExt6(api)} + fp, err := emulated.NewField[emulated.BLS12381Fp](api) + if err != nil { + panic(err) + } + return &Ext12{ + Ext2: NewExt2(api), + api: api, + fp: fp, + } } -func (e Ext12) Add(x, y *E12) *E12 { - z0 := e.Ext6.Add(&x.C0, &y.C0) - z1 := e.Ext6.Add(&x.C1, &y.C1) +func (e Ext12) Zero() *E12 { + zero := e.fp.Zero() return &E12{ - C0: *z0, - C1: *z1, + A0: *zero, + A1: *zero, + A2: *zero, + A3: *zero, + A4: *zero, + A5: *zero, + A6: *zero, + A7: *zero, + A8: *zero, + A9: *zero, + A10: *zero, + A11: *zero, } } -func (e Ext12) Sub(x, y *E12) *E12 { - z0 := e.Ext6.Sub(&x.C0, &y.C0) - z1 := e.Ext6.Sub(&x.C1, &y.C1) +func (e Ext12) One() *E12 { + one := e.fp.One() + zero := e.fp.Zero() return &E12{ - C0: *z0, - C1: *z1, + A0: *one, + A1: *zero, + A2: *zero, + A3: *zero, + A4: *zero, + A5: *zero, + A6: *zero, + A7: *zero, + A8: *zero, + A9: *zero, + A10: *zero, + A11: *zero, } } -func (e Ext12) Conjugate(x *E12) *E12 { - z1 := e.Ext6.Neg(&x.C1) +func (e Ext12) Neg(x *E12) *E12 { + a0 := e.fp.Neg(&x.A0) + a1 := e.fp.Neg(&x.A1) + a2 := e.fp.Neg(&x.A2) + a3 := e.fp.Neg(&x.A3) + a4 := e.fp.Neg(&x.A4) + a5 := e.fp.Neg(&x.A5) + a6 := e.fp.Neg(&x.A6) + a7 := e.fp.Neg(&x.A7) + a8 := e.fp.Neg(&x.A8) + a9 := e.fp.Neg(&x.A9) + a10 := e.fp.Neg(&x.A10) + a11 := e.fp.Neg(&x.A11) + return &E12{ - C0: x.C0, - C1: *z1, + A0: *a0, + A1: *a1, + A2: *a2, + A3: *a3, + A4: *a4, + A5: *a5, + A6: *a6, + A7: *a7, + A8: *a8, + A9: *a9, + A10: *a10, + A11: *a11, } } -func (e Ext12) Mul(x, y *E12) *E12 { - a := e.Ext6.Add(&x.C0, &x.C1) - b := e.Ext6.Add(&y.C0, &y.C1) - a = e.Ext6.Mul(a, b) - b = e.Ext6.Mul(&x.C0, &y.C0) - c := e.Ext6.Mul(&x.C1, &y.C1) - d := e.Ext6.Add(c, b) - z1 := e.Ext6.Sub(a, d) - z0 := e.Ext6.MulByNonResidue(c) - z0 = e.Ext6.Add(z0, b) +func (e Ext12) Add(x, y *E12) *E12 { + a0 := e.fp.Add(&x.A0, &y.A0) + a1 := e.fp.Add(&x.A1, &y.A1) + a2 := e.fp.Add(&x.A2, &y.A2) + a3 := e.fp.Add(&x.A3, &y.A3) + a4 := e.fp.Add(&x.A4, &y.A4) + a5 := e.fp.Add(&x.A5, &y.A5) + a6 := e.fp.Add(&x.A6, &y.A6) + a7 := e.fp.Add(&x.A7, &y.A7) + a8 := e.fp.Add(&x.A8, &y.A8) + a9 := e.fp.Add(&x.A9, &y.A9) + a10 := e.fp.Add(&x.A10, &y.A10) + a11 := e.fp.Add(&x.A11, &y.A11) + return &E12{ - C0: *z0, - C1: *z1, + A0: *a0, + A1: *a1, + A2: *a2, + A3: *a3, + A4: *a4, + A5: *a5, + A6: *a6, + A7: *a7, + A8: *a8, + A9: *a9, + A10: *a10, + A11: *a11, } } -func (e Ext12) Zero() *E12 { - zero := e.fp.Zero() +func (e Ext12) Sub(x, y *E12) *E12 { + a0 := e.fp.Sub(&x.A0, &y.A0) + a1 := e.fp.Sub(&x.A1, &y.A1) + a2 := e.fp.Sub(&x.A2, &y.A2) + a3 := e.fp.Sub(&x.A3, &y.A3) + a4 := e.fp.Sub(&x.A4, &y.A4) + a5 := e.fp.Sub(&x.A5, &y.A5) + a6 := e.fp.Sub(&x.A6, &y.A6) + a7 := e.fp.Sub(&x.A7, &y.A7) + a8 := e.fp.Sub(&x.A8, &y.A8) + a9 := e.fp.Sub(&x.A9, &y.A9) + a10 := e.fp.Sub(&x.A10, &y.A10) + a11 := e.fp.Sub(&x.A11, &y.A11) + return &E12{ - C0: E6{ - B0: E2{A0: *zero, A1: *zero}, - B1: E2{A0: *zero, A1: *zero}, - B2: E2{A0: *zero, A1: *zero}, - }, - C1: E6{ - B0: E2{A0: *zero, A1: *zero}, - B1: E2{A0: *zero, A1: *zero}, - B2: E2{A0: *zero, A1: *zero}, - }, + A0: *a0, + A1: *a1, + A2: *a2, + A3: *a3, + A4: *a4, + A5: *a5, + A6: *a6, + A7: *a7, + A8: *a8, + A9: *a9, + A10: *a10, + A11: *a11, } } -func (e Ext12) One() *E12 { - z000 := e.fp.One() - zero := e.fp.Zero() +func (e Ext12) Double(x *E12) *E12 { + two := big.NewInt(2) + a0 := e.fp.MulConst(&x.A0, two) + a1 := e.fp.MulConst(&x.A1, two) + a2 := e.fp.MulConst(&x.A2, two) + a3 := e.fp.MulConst(&x.A3, two) + a4 := e.fp.MulConst(&x.A4, two) + a5 := e.fp.MulConst(&x.A5, two) + a6 := e.fp.MulConst(&x.A6, two) + a7 := e.fp.MulConst(&x.A7, two) + a8 := e.fp.MulConst(&x.A8, two) + a9 := e.fp.MulConst(&x.A9, two) + a10 := e.fp.MulConst(&x.A10, two) + a11 := e.fp.MulConst(&x.A11, two) + return &E12{ - C0: E6{ - B0: E2{A0: *z000, A1: *zero}, - B1: E2{A0: *zero, A1: *zero}, - B2: E2{A0: *zero, A1: *zero}, - }, - C1: E6{ - B0: E2{A0: *zero, A1: *zero}, - B1: E2{A0: *zero, A1: *zero}, - B2: E2{A0: *zero, A1: *zero}, - }, + A0: *a0, + A1: *a1, + A2: *a2, + A3: *a3, + A4: *a4, + A5: *a5, + A6: *a6, + A7: *a7, + A8: *a8, + A9: *a9, + A10: *a10, + A11: *a11, } } -func (e Ext12) IsZero(z *E12) frontend.Variable { - c0 := e.Ext6.IsZero(&z.C0) - c1 := e.Ext6.IsZero(&z.C1) - return e.api.And(c0, c1) -} - -func (e Ext12) Square(x *E12) *E12 { - c0 := e.Ext6.Sub(&x.C0, &x.C1) - c3 := e.Ext6.MulByNonResidue(&x.C1) - c3 = e.Ext6.Sub(&x.C0, c3) - c2 := e.Ext6.Mul(&x.C0, &x.C1) - c0 = e.Ext6.Mul(c0, c3) - c0 = e.Ext6.Add(c0, c2) - z1 := e.Ext6.Double(c2) - c2 = e.Ext6.MulByNonResidue(c2) - z0 := e.Ext6.Add(c0, c2) +func (e Ext12) Conjugate(x *E12) *E12 { return &E12{ - C0: *z0, - C1: *z1, + A0: x.A0, + A1: *e.fp.Neg(&x.A1), + A2: x.A2, + A3: *e.fp.Neg(&x.A3), + A4: x.A4, + A5: *e.fp.Neg(&x.A5), + A6: x.A6, + A7: *e.fp.Neg(&x.A7), + A8: x.A8, + A9: *e.fp.Neg(&x.A9), + A10: x.A10, + A11: *e.fp.Neg(&x.A11), } } -// Granger--Scott cyclotomic square -func (e Ext12) CyclotomicSquare(x *E12) *E12 { - t0 := e.Ext2.Square(&x.C1.B1) - t1 := e.Ext2.Square(&x.C0.B0) - t6 := e.Ext2.Add(&x.C1.B1, &x.C0.B0) - t6 = e.Ext2.Square(t6) - t6 = e.Ext2.Sub(t6, t0) - t6 = e.Ext2.Sub(t6, t1) - t2 := e.Ext2.Square(&x.C0.B2) - t3 := e.Ext2.Square(&x.C1.B0) - t7 := e.Ext2.Add(&x.C0.B2, &x.C1.B0) - t7 = e.Ext2.Square(t7) - t7 = e.Ext2.Sub(t7, t2) - t7 = e.Ext2.Sub(t7, t3) - t4 := e.Ext2.Square(&x.C1.B2) - t5 := e.Ext2.Square(&x.C0.B1) - t8 := e.Ext2.Add(&x.C1.B2, &x.C0.B1) - t8 = e.Ext2.Square(t8) - t8 = e.Ext2.Sub(t8, t4) - t8 = e.Ext2.Sub(t8, t5) - t8 = e.Ext2.MulByNonResidue(t8) - t0 = e.Ext2.MulByNonResidue(t0) - t0 = e.Ext2.Add(t0, t1) - t2 = e.Ext2.MulByNonResidue(t2) - t2 = e.Ext2.Add(t2, t3) - t4 = e.Ext2.MulByNonResidue(t4) - t4 = e.Ext2.Add(t4, t5) - z00 := e.Ext2.Sub(t0, &x.C0.B0) - z00 = e.Ext2.Double(z00) - z00 = e.Ext2.Add(z00, t0) - z01 := e.Ext2.Sub(t2, &x.C0.B1) - z01 = e.Ext2.Double(z01) - z01 = e.Ext2.Add(z01, t2) - z02 := e.Ext2.Sub(t4, &x.C0.B2) - z02 = e.Ext2.Double(z02) - z02 = e.Ext2.Add(z02, t4) - z10 := e.Ext2.Add(t8, &x.C1.B0) - z10 = e.Ext2.Double(z10) - z10 = e.Ext2.Add(z10, t8) - z11 := e.Ext2.Add(t6, &x.C1.B1) - z11 = e.Ext2.Double(z11) - z11 = e.Ext2.Add(z11, t6) - z12 := e.Ext2.Add(t7, &x.C1.B2) - z12 = e.Ext2.Double(z12) - z12 = e.Ext2.Add(z12, t7) +func (e Ext12) Mul(x, y *E12) *E12 { + return e.mulDirect(x, y) +} + +func (e Ext12) mulDirect(a, b *E12) *E12 { + + // a = a11 w^11 + a10 w^10 + a9 w^9 + a8 w^8 + a7 w^7 + a6 w^6 + a5 w^5 + a4 w^4 + a3 w^3 + a2 w^2 + a1 w + a0 + // b = b11 w^11 + b10 w^10 + b9 w^9 + b8 w^8 + b7 w^7 + b6 w^6 + b5 w^5 + b4 w^4 + b3 w^3 + b2 w^2 + b1 w + b0 + // + // Given that w^12 = 2 w^6 - 2, we can compute the product a * b as follows: + // + // a * b = d11 w^11 + d10 w^10 + d9 w^9 + d8 w^8 + d7 w^7 + d6 w^6 + d5 w^5 + d4 w^4 + d3 w^3 + d2 w^2 + d1 w + d0 + // + // where: + // + // d0 = c0 - 2 * c12 - 4 * c18 + // d1 = c1 - 2 * c13 - 4 * c19 + // d2 = c2 - 2 * c14 - 4 * c20 + // d3 = c3 - 2 * c15 - 4 * c21 + // d4 = c4 - 2 * c16 - 4 * c22 + // d5 = c5 - 2 * c17 + // d6 = c6 + 2 * c12 + 2 * c18 + // d7 = c7 + 2 * c13 + 2 * c19 + // d8 = c8 + 2 * c14 + 2 * c20 + // d9 = c9 + 2 * c15 + 2 * c21 + // d10 = c10 + 2 * c16 + 2 * c22 + // d11 = c11 + 2 * c17 + // + // and: + // + // c0 = a0 b0 + // c1 = a0 b1 + a1 b0 + // c2 = a0 b2 + a1 b1 + a2 b0 + // c3 = a0 b3 + a1 b2 + a2 b1 + a3 b0 + // c4 = a0 b4 + a1 b3 + a2 b2 + a3 b1 + a4 b0 + // c5 = a0 b5 + a1 b4 + a2 b3 + a3 b2 + a4 b1 + a5 b0 + // c6 = a0 b6 + a1 b5 + a2 b4 + a3 b3 + a4 b2 + a5 b1 + a6 b0 + // c7 = a0 b7 + a1 b6 + a2 b5 + a3 b4 + a4 b3 + a5 b2 + a6 b1 + a7 b0 + // c8 = a0 b8 + a1 b7 + a2 b6 + a3 b5 + a4 b4 + a5 b3 + a6 b2 + a7 b1 + a8 b0 + // c9 = a0 b9 + a1 b8 + a2 b7 + a3 b6 + a4 b5 + a5 b4 + a6 b3 + a7 b2 + a8 b1 + a9 b0 + // c10 = a0 b10 + a1 b9 + a2 b8 + a3 b7 + a4 b6 + a5 b5 + a6 b4 + a7 b3 + a8 b2 + a9 b1 + a10 b0 + // c11 = a0 b11 + a1 b10 + a2 b9 + a3 b8 + a4 b7 + a5 b6 + a6 b5 + a7 b4 + a8 b3 + a9 b2 + a10 b1 + a11 b0 + // c12 = a1 b11 + a2 b10 + a3 b9 + a4 b8 + a5 b7 + a6 b6 + a7 b5 + a8 b4 + a9 b3 + a10 b2 + a11 b1 + // c13 = a2 b11 + a3 b10 + a4 b9 + a5 b8 + a6 b7 + a7 b6 + a8 b5 + a9 b4 + a10 b3 + a11 b2 + // c14 = a3 b11 + a4 b10 + a5 b9 + a6 b8 + a7 b7 + a8 b6 + a9 b5 + a10 b4 + a11 b3 + // c15 = a4 b11 + a5 b10 + a6 b9 + a7 b8 + a8 b7 + a9 b6 + a10 b5 + a11 b4 + // c16 = a5 b11 + a6 b10 + a7 b9 + a8 b8 + a9 b7 + a10 b6 + a11 b5 + // c17 = a6 b11 + a7 b10 + a8 b9 + a9 b8 + a10 b7 + a11 b6 + // c18 = a7 b11 + a8 b10 + a9 b9 + a10 b8 + a11 b7 + // c19 = a8 b11 + a9 b10 + a10 b9 + a11 b8 + // c20 = a9 b11 + a10 b10 + a11 b9 + // c21 = a10 b11 + a11 b10 + // c22 = a11 b11 + + // d0 = c0 - 2 * c12 - 4 * c18 + // = a0 b0 - 2 * (a1 b11 + a2 b10 + a3 b9 + a4 b8 + a5 b7 + a6 b6 + a7 b5 + a8 b4 + a9 b3 + a10 b2 + a11 b1) - 4 * (a7 b11 + a8 b10 + a9 b9 + a10 b8 + a11 b7) + mone := e.fp.NewElement(-1) + d0 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A0}, {mone, &a.A1, &b.A11}, {mone, &a.A2, &b.A10}, {mone, &a.A3, &b.A9}, {mone, &a.A4, &b.A8}, {mone, &a.A5, &b.A7}, {mone, &a.A6, &b.A6}, {mone, &a.A7, &b.A5}, {mone, &a.A8, &b.A4}, {mone, &a.A9, &b.A3}, {mone, &a.A10, &b.A2}, {mone, &a.A11, &b.A1}, {mone, &a.A7, &b.A11}, {mone, &a.A8, &b.A10}, {mone, &a.A9, &b.A9}, {mone, &a.A10, &b.A8}, {mone, &a.A11, &b.A7}}, []int{1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4}) + + // d1 = c1 - 2 * c13 - 4 * c19 + // = a0 b1 + a1 b0 - 2 * (a2 b11 + a3 b10 + a4 b9 + a5 b8 + a6 b7 + a7 b6 + a8 b5 + a9 b4 + a10 b3 + a11 b2) - 4 * (a8 b11 + a9 b10 + a10 b9 + a11 b8) + d1 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A1}, {&a.A1, &b.A0}, {mone, &a.A2, &b.A11}, {mone, &a.A3, &b.A10}, {mone, &a.A4, &b.A9}, {mone, &a.A5, &b.A8}, {mone, &a.A6, &b.A7}, {mone, &a.A7, &b.A6}, {mone, &a.A8, &b.A5}, {mone, &a.A9, &b.A4}, {mone, &a.A10, &b.A3}, {mone, &a.A11, &b.A2}, {mone, &a.A8, &b.A11}, {mone, &a.A9, &b.A10}, {mone, &a.A10, &b.A9}, {mone, &a.A11, &b.A8}}, []int{1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4}) + + // d2 = c2 - 2 * c14 - 4 * c20 + // = a0 b2 + a1 b1 + a2 b0 - 2 * (a3 b11 + a4 b10 + a5 b9 + a6 b8 + a7 b7 + a8 b6 + a9 b5 + a10 b4 + a11 b3) - 4 * (a9 b11 + a10 b10 + a11 b9) + d2 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A2}, {&a.A1, &b.A1}, {&a.A2, &b.A0}, {mone, &a.A3, &b.A11}, {mone, &a.A4, &b.A10}, {mone, &a.A5, &b.A9}, {mone, &a.A6, &b.A8}, {mone, &a.A7, &b.A7}, {mone, &a.A8, &b.A6}, {mone, &a.A9, &b.A5}, {mone, &a.A10, &b.A4}, {mone, &a.A11, &b.A3}, {mone, &a.A9, &b.A11}, {mone, &a.A10, &b.A10}, {mone, &a.A11, &b.A9}}, []int{1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4}) + + // d3 = c3 - 2 * c15 - 4 * c21 + // = a0 b3 + a1 b2 + a2 b1 + a3 b0 - 2 * (a4 b11 + a5 b10 + a6 b9 + a7 b8 + a8 b7 + a9 b6 + a10 b5 + a11 b4) - 4 * (a10 b11 + a11 b10) + d3 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A3}, {&a.A1, &b.A2}, {&a.A2, &b.A1}, {&a.A3, &b.A0}, {mone, &a.A4, &b.A11}, {mone, &a.A5, &b.A10}, {mone, &a.A6, &b.A9}, {mone, &a.A7, &b.A8}, {mone, &a.A8, &b.A7}, {mone, &a.A9, &b.A6}, {mone, &a.A10, &b.A5}, {mone, &a.A11, &b.A4}, {mone, &a.A10, &b.A11}, {mone, &a.A11, &b.A10}}, []int{1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4}) + + // d4 = c4 - 2 * c16 - 4 * c22 + // = a0 b4 + a1 b3 + a2 b2 + a3 b1 + a4 b0 - 2 * (a5 b11 + a6 b10 + a7 b9 + a8 b8 + a9 b7 + a10 b6 + a11 b5) - 4 * a11 b11 + d4 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A4}, {&a.A1, &b.A3}, {&a.A2, &b.A2}, {&a.A3, &b.A1}, {&a.A4, &b.A0}, {mone, &a.A5, &b.A11}, {mone, &a.A6, &b.A10}, {mone, &a.A7, &b.A9}, {mone, &a.A8, &b.A8}, {mone, &a.A9, &b.A7}, {mone, &a.A10, &b.A6}, {mone, &a.A11, &b.A5}, {mone, &a.A11, &b.A11}}, []int{1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 4}) + + // d5 = c5 - 2 * c17 + // = a0 b5 + a1 b4 + a2 b3 + a3 b2 + a4 b1 + a5 b0 - 2 * (a6 b11 + a7 b10 + a8 b9 + a9 b8 + a10 b7 + a11 b6) + d5 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A5}, {&a.A1, &b.A4}, {&a.A2, &b.A3}, {&a.A3, &b.A2}, {&a.A4, &b.A1}, {&a.A5, &b.A0}, {mone, &a.A6, &b.A11}, {mone, &a.A7, &b.A10}, {mone, &a.A8, &b.A9}, {mone, &a.A9, &b.A8}, {mone, &a.A10, &b.A7}, {mone, &a.A11, &b.A6}}, []int{1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2}) + + // d6 = c6 + 2 * c12 + 2 * c2 + // = a0 b6 + a1 b5 + a2 b4 + a3 b3 + a4 b2 + a5 b1 + a6 b0 + 2 * (a1 b11 + a2 b10 + a3 b9 + a4 b8 + a5 b7 + a6 b6 + a7 b5 + a8 b4 + a9 b3 + a10 b2 + a11 b1) + 2 * (a7 b11 + a8 b10 + a9 b9 + a10 b8 + a11 b7) + d6 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A6}, {&a.A1, &b.A5}, {&a.A2, &b.A4}, {&a.A3, &b.A3}, {&a.A4, &b.A2}, {&a.A5, &b.A1}, {&a.A6, &b.A0}, {&a.A1, &b.A11}, {&a.A2, &b.A10}, {&a.A3, &b.A9}, {&a.A4, &b.A8}, {&a.A5, &b.A7}, {&a.A6, &b.A6}, {&a.A7, &b.A5}, {&a.A8, &b.A4}, {&a.A9, &b.A3}, {&a.A10, &b.A2}, {&a.A11, &b.A1}, {&a.A7, &b.A11}, {&a.A8, &b.A10}, {&a.A9, &b.A9}, {&a.A10, &b.A8}, {&a.A11, &b.A7}}, []int{1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}) + + // d7 = c7 + 2 * c13 + 2 * c19 + // = a0 b7 + a1 b6 + a2 b5 + a3 b4 + a4 b3 + a5 b2 + a6 b1 + a7 b0 + 2 * (a2 b11 + a3 b10 + a4 b9 + a5 b8 + a6 b7 + a7 b6 + a8 b5 + a9 b4 + a10 b3 + a11 b2) + 2 * (a8 b11 + a9 b10 + a10 b9 + a11 b8) + d7 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A7}, {&a.A1, &b.A6}, {&a.A2, &b.A5}, {&a.A3, &b.A4}, {&a.A4, &b.A3}, {&a.A5, &b.A2}, {&a.A6, &b.A1}, {&a.A7, &b.A0}, {&a.A2, &b.A11}, {&a.A3, &b.A10}, {&a.A4, &b.A9}, {&a.A5, &b.A8}, {&a.A6, &b.A7}, {&a.A7, &b.A6}, {&a.A8, &b.A5}, {&a.A9, &b.A4}, {&a.A10, &b.A3}, {&a.A11, &b.A2}, {&a.A8, &b.A11}, {&a.A9, &b.A10}, {&a.A10, &b.A9}, {&a.A11, &b.A8}}, []int{1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}) + + // d8 = c8 + 2 * c14 + 2 * c20 + // = a0 b8 + a1 b7 + a2 b6 + a3 b5 + a4 b4 + a5 b3 + a6 b2 + a7 b1 + a8 b0 + 2 * (a3 b11 + a4 b10 + a5 b9 + a6 b8 + a7 b7 + a8 b6 + a9 b5 + a10 b4 + a11 b3) + 2 * (a9 b11 + a10 b10 + a11 b9) + d8 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A8}, {&a.A1, &b.A7}, {&a.A2, &b.A6}, {&a.A3, &b.A5}, {&a.A4, &b.A4}, {&a.A5, &b.A3}, {&a.A6, &b.A2}, {&a.A7, &b.A1}, {&a.A8, &b.A0}, {&a.A3, &b.A11}, {&a.A4, &b.A10}, {&a.A5, &b.A9}, {&a.A6, &b.A8}, {&a.A7, &b.A7}, {&a.A8, &b.A6}, {&a.A9, &b.A5}, {&a.A10, &b.A4}, {&a.A11, &b.A3}, {&a.A9, &b.A11}, {&a.A10, &b.A10}, {&a.A11, &b.A9}}, []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}) + + // d9 = c9 + 2 * c15 + 2 * c21 + // = a0 b9 + a1 b8 + a2 b7 + a3 b6 + a4 b5 + a5 b4 + a6 b3 + a7 b2 + a8 b1 + a9 b0 + 2 * (a4 b11 + a5 b10 + a6 b9 + a7 b8 + a8 b7 + a9 b6 + a10 b5 + a11 b4) + 2 * (a10 b11 + a11 b10) + d9 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A9}, {&a.A1, &b.A8}, {&a.A2, &b.A7}, {&a.A3, &b.A6}, {&a.A4, &b.A5}, {&a.A5, &b.A4}, {&a.A6, &b.A3}, {&a.A7, &b.A2}, {&a.A8, &b.A1}, {&a.A9, &b.A0}, {&a.A4, &b.A11}, {&a.A5, &b.A10}, {&a.A6, &b.A9}, {&a.A7, &b.A8}, {&a.A8, &b.A7}, {&a.A9, &b.A6}, {&a.A10, &b.A5}, {&a.A11, &b.A4}, {&a.A10, &b.A11}, {&a.A11, &b.A10}}, []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}) + + // d10 = c10 + 2 * c16 + 2 * c22 + // = a0 b10 + a1 b9 + a2 b8 + a3 b7 + a4 b6 + a5 b5 + a6 b4 + a7 b3 + a8 b2 + a9 b1 + a10 b0 + 2 * (a5 b11 + a6 b10 + a7 b9 + a8 b8 + a9 b7 + a10 b6 + a11 b5) + 2 * (a11 b11) + d10 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A10}, {&a.A1, &b.A9}, {&a.A2, &b.A8}, {&a.A3, &b.A7}, {&a.A4, &b.A6}, {&a.A5, &b.A5}, {&a.A6, &b.A4}, {&a.A7, &b.A3}, {&a.A8, &b.A2}, {&a.A9, &b.A1}, {&a.A10, &b.A0}, {&a.A5, &b.A11}, {&a.A6, &b.A10}, {&a.A7, &b.A9}, {&a.A8, &b.A8}, {&a.A9, &b.A7}, {&a.A10, &b.A6}, {&a.A11, &b.A5}, {&a.A11, &b.A11}}, []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2}) + + // d11 = c11 + 2 * c17 + // = a0 b11 + a1 b10 + a2 b9 + a3 b8 + a4 b7 + a5 b6 + a6 b5 + a7 b4 + a8 b3 + a9 b2 + a10 b1 + a11 b0 + 2 * (a6 b11 + a7 b10 + a8 b9 + a9 b8 + a10 b7 + a11 b6) + d11 := e.fp.Eval([][]*baseEl{{&a.A0, &b.A11}, {&a.A1, &b.A10}, {&a.A2, &b.A9}, {&a.A3, &b.A8}, {&a.A4, &b.A7}, {&a.A5, &b.A6}, {&a.A6, &b.A5}, {&a.A7, &b.A4}, {&a.A8, &b.A3}, {&a.A9, &b.A2}, {&a.A10, &b.A1}, {&a.A11, &b.A0}, {&a.A6, &b.A11}, {&a.A7, &b.A10}, {&a.A8, &b.A9}, {&a.A9, &b.A8}, {&a.A10, &b.A7}, {&a.A11, &b.A6}}, []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2}) + return &E12{ - C0: E6{ - B0: *z00, - B1: *z01, - B2: *z02, - }, - C1: E6{ - B0: *z10, - B1: *z11, - B2: *z12, - }, + A0: *d0, + A1: *d1, + A2: *d2, + A3: *d3, + A4: *d4, + A5: *d5, + A6: *d6, + A7: *d7, + A8: *d8, + A9: *d9, + A10: *d10, + A11: *d11, } } -func (e Ext12) AssertIsEqual(x, y *E12) { - e.Ext6.AssertIsEqual(&x.C0, &y.C0) - e.Ext6.AssertIsEqual(&x.C1, &y.C1) +func (e Ext12) Square(x *E12) *E12 { + return e.squareDirect(x) } -func FromE12(y *bls12381.E12) E12 { - return E12{ - C0: FromE6(&y.C0), - C1: FromE6(&y.C1), - } +func (e Ext12) squareDirect(a *E12) *E12 { + + mone := e.fp.NewElement(-1) + // d0 = a0 a0 - 2 * (2 a1 a11 + 2 a2 a10 + 2 a3 a9 + 2 a4 a8 + 2 a5 a7 + a6 a6) - 4 * (2 a7 a11 + 2 a8 a10 + a9 a9) + d0 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A0}, {mone, &a.A1, &a.A11}, {mone, &a.A2, &a.A10}, {mone, &a.A3, &a.A9}, {mone, &a.A4, &a.A8}, {mone, &a.A5, &a.A7}, {mone, &a.A6, &a.A6}, {mone, &a.A7, &a.A11}, {mone, &a.A8, &a.A10}, {mone, &a.A9, &a.A9}}, []int{1, 4, 4, 4, 4, 4, 2, 8, 8, 4}) + + // d1 = 2 a0 a1 - 4 * (2 a2 a11 + a3 a10 + a4 a9 + a5 a8 + a6 a7) - 8 * (a8 a11 + a9 a10) + d1 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A1}, {mone, &a.A2, &a.A11}, {mone, &a.A3, &a.A10}, {mone, &a.A4, &a.A9}, {mone, &a.A5, &a.A8}, {mone, &a.A6, &a.A7}, {mone, &a.A8, &a.A11}, {mone, &a.A9, &a.A10}}, []int{2, 4, 4, 4, 4, 4, 8, 8}) + + // d2 = 2 a0 a2 + a1 a1 - 2 * (2 a3 a11 + 2 a4 a10 + 2 a5 a9 + 2 a6 a8 + a7 a7) - 4 * (2 a9 a11 + a10 a10) + d2 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A2}, {&a.A1, &a.A1}, {mone, &a.A3, &a.A11}, {mone, &a.A4, &a.A10}, {mone, &a.A5, &a.A9}, {mone, &a.A6, &a.A8}, {mone, &a.A7, &a.A7}, {mone, &a.A9, &a.A11}, {mone, &a.A10, &a.A10}}, []int{2, 1, 4, 4, 4, 4, 2, 8, 4}) + + // d3 = 2 a0 a3 + 2 a1 a2 - 4 * (a4 a11 + a5 a10 + a6 a9 + a7 a8) - 8 * a10 a11 + d3 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A3}, {&a.A1, &a.A2}, {mone, &a.A4, &a.A11}, {mone, &a.A5, &a.A10}, {mone, &a.A6, &a.A9}, {mone, &a.A7, &a.A8}, {mone, &a.A10, &a.A11}}, []int{2, 2, 4, 4, 4, 4, 8}) + + // d4 = 2 a0 a4 + 2 a1 a3 + a2 a2 - 2 * (2 a5 a11 + 2 a6 a10 + 2 a7 a9 + a8 a8) - 4 * a11 a11 + d4 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A4}, {&a.A1, &a.A3}, {&a.A2, &a.A2}, {mone, &a.A5, &a.A11}, {mone, &a.A6, &a.A10}, {mone, &a.A7, &a.A9}, {mone, &a.A8, &a.A8}, {mone, &a.A11, &a.A11}}, []int{2, 2, 1, 4, 4, 4, 2, 4}) + + // d5 = 2 (a0 a5 + a1 a4 + a2 a3) - 4 * (a6 a11 + a7 a10 + a8 a9) + d5 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A5}, {&a.A1, &a.A4}, {&a.A2, &a.A3}, {mone, &a.A6, &a.A11}, {mone, &a.A7, &a.A10}, {mone, &a.A8, &a.A9}}, []int{2, 2, 2, 4, 4, 4}) + // d6 = 2 a0 a6 + 2 a1 a5 + 2 a2 a4 + a3 a3 + 2 * (2 a1 a11 + 2 a2 a10 + 2 a3 a9 + 2 a4 a8 + 2 a5 a7 + a6 a6) + 2 * (2 a7 a11 + 2 a8 a10 + a9 a9) + d6 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A6}, {&a.A1, &a.A5}, {&a.A2, &a.A4}, {&a.A3, &a.A3}, {&a.A1, &a.A11}, {&a.A2, &a.A10}, {&a.A3, &a.A9}, {&a.A4, &a.A8}, {&a.A5, &a.A7}, {&a.A6, &a.A6}, {&a.A7, &a.A11}, {&a.A8, &a.A10}, {&a.A9, &a.A9}}, []int{2, 2, 2, 1, 4, 4, 4, 4, 4, 2, 4, 4, 2}) + + // d7 = 2(a0 a7 + a1 a6 + a2 a5 + a3 a4) + 4 * (a2 a11 + a3 a10 + a4 a9 + a5 a8 + a6 a7) + 4 * (a8 a11 + a9 a10) + d7 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A7}, {&a.A1, &a.A6}, {&a.A2, &a.A5}, {&a.A3, &a.A4}, {&a.A2, &a.A11}, {&a.A3, &a.A10}, {&a.A4, &a.A9}, {&a.A5, &a.A8}, {&a.A6, &a.A7}, {&a.A8, &a.A11}, {&a.A9, &a.A10}}, []int{2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4}) + + // d8 = 2(a0 a8 + a1 a7 + a2 a6 + a3 a5) + a4 a4 + 2 * (2 a3 a11 + 2 a4 a10 + 2 a5 a9 + 2 a6 a8 + a7 a7) + 2 * (2 a9 a11 + a10 a10) + d8 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A8}, {&a.A1, &a.A7}, {&a.A2, &a.A6}, {&a.A3, &a.A5}, {&a.A4, &a.A4}, {&a.A3, &a.A11}, {&a.A4, &a.A10}, {&a.A5, &a.A9}, {&a.A6, &a.A8}, {&a.A7, &a.A7}, {&a.A9, &a.A11}, {&a.A10, &a.A10}}, []int{2, 2, 2, 2, 1, 4, 4, 4, 4, 2, 4, 2}) + + // d9 = 2(a0 a9 + a1 a8 + a2 a7 + a3 a6 + a4 a5) + 4 * (a4 a11 + a5 a10 + a6 a9 + a7 a8) + 4 * a10 a11 + d9 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A9}, {&a.A1, &a.A8}, {&a.A2, &a.A7}, {&a.A3, &a.A6}, {&a.A4, &a.A5}, {&a.A4, &a.A11}, {&a.A5, &a.A10}, {&a.A6, &a.A9}, {&a.A7, &a.A8}, {&a.A10, &a.A11}}, []int{2, 2, 2, 2, 2, 4, 4, 4, 4, 4}) + + // d10 = 2(a0 a10 + a1 a9 + a2 a8 + a3 a7 + a4 a6) + a5 a5 + 2 * (2 a5 a11 + 2 a6 a10 + 2 a7 a9 + a8 a8) + 2 * a11 a11 + d10 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A10}, {&a.A1, &a.A9}, {&a.A2, &a.A8}, {&a.A3, &a.A7}, {&a.A4, &a.A6}, {&a.A5, &a.A5}, {&a.A5, &a.A11}, {&a.A6, &a.A10}, {&a.A7, &a.A9}, {&a.A8, &a.A8}, {&a.A11, &a.A11}}, []int{2, 2, 2, 2, 2, 1, 4, 4, 4, 2, 2}) + + // d11 = 2(a0 a11 + a1 a10 + a2 a9 + a3 a8 + a4 a7 + a5 a6) + 4 * (a6 a11 + a7 a10 + a8 a9) + d11 := e.fp.Eval([][]*baseEl{{&a.A0, &a.A11}, {&a.A1, &a.A10}, {&a.A2, &a.A9}, {&a.A3, &a.A8}, {&a.A4, &a.A7}, {&a.A5, &a.A6}, {&a.A6, &a.A11}, {&a.A7, &a.A10}, {&a.A8, &a.A9}}, []int{2, 2, 2, 2, 2, 2, 4, 4, 4}) + + return &E12{ + A0: *d0, + A1: *d1, + A2: *d2, + A3: *d3, + A4: *d4, + A5: *d5, + A6: *d6, + A7: *d7, + A8: *d8, + A9: *d9, + A10: *d10, + A11: *d11, + } } func (e Ext12) Inverse(x *E12) *E12 { - res, err := e.fp.NewHint(inverseE12Hint, 12, &x.C0.B0.A0, &x.C0.B0.A1, &x.C0.B1.A0, &x.C0.B1.A1, &x.C0.B2.A0, &x.C0.B2.A1, &x.C1.B0.A0, &x.C1.B0.A1, &x.C1.B1.A0, &x.C1.B1.A1, &x.C1.B2.A0, &x.C1.B2.A1) + res, err := e.fp.NewHint(inverseE12Hint, 12, &x.A0, &x.A1, &x.A2, &x.A3, &x.A4, &x.A5, &x.A6, &x.A7, &x.A8, &x.A9, &x.A10, &x.A11) if err != nil { // err is non-nil only for invalid number of inputs panic(err) } - inv := E12{ - C0: E6{ - B0: E2{A0: *res[0], A1: *res[1]}, - B1: E2{A0: *res[2], A1: *res[3]}, - B2: E2{A0: *res[4], A1: *res[5]}, - }, - C1: E6{ - B0: E2{A0: *res[6], A1: *res[7]}, - B1: E2{A0: *res[8], A1: *res[9]}, - B2: E2{A0: *res[10], A1: *res[11]}, - }, - } - + inv := E12{A0: *res[0], A1: *res[1], A2: *res[2], A3: *res[3], A4: *res[4], A5: *res[5], A6: *res[6], A7: *res[7], A8: *res[8], A9: *res[9], A10: *res[10], A11: *res[11]} one := e.One() // 1 == inv * x @@ -217,41 +403,302 @@ func (e Ext12) Inverse(x *E12) *E12 { } func (e Ext12) DivUnchecked(x, y *E12) *E12 { - res, err := e.fp.NewHint(divE12Hint, 12, &x.C0.B0.A0, &x.C0.B0.A1, &x.C0.B1.A0, &x.C0.B1.A1, &x.C0.B2.A0, &x.C0.B2.A1, &x.C1.B0.A0, &x.C1.B0.A1, &x.C1.B1.A0, &x.C1.B1.A1, &x.C1.B2.A0, &x.C1.B2.A1, &y.C0.B0.A0, &y.C0.B0.A1, &y.C0.B1.A0, &y.C0.B1.A1, &y.C0.B2.A0, &y.C0.B2.A1, &y.C1.B0.A0, &y.C1.B0.A1, &y.C1.B1.A0, &y.C1.B1.A1, &y.C1.B2.A0, &y.C1.B2.A1) - + res, err := e.fp.NewHint(divE12Hint, 12, &x.A0, &x.A1, &x.A2, &x.A3, &x.A4, &x.A5, &x.A6, &x.A7, &x.A8, &x.A9, &x.A10, &x.A11, &y.A0, &y.A1, &y.A2, &y.A3, &y.A4, &y.A5, &y.A6, &y.A7, &y.A8, &y.A9, &y.A10, &y.A11) if err != nil { // err is non-nil only for invalid number of inputs panic(err) } - div := E12{ - C0: E6{ - B0: E2{A0: *res[0], A1: *res[1]}, - B1: E2{A0: *res[2], A1: *res[3]}, - B2: E2{A0: *res[4], A1: *res[5]}, - }, - C1: E6{ - B0: E2{A0: *res[6], A1: *res[7]}, - B1: E2{A0: *res[8], A1: *res[9]}, - B2: E2{A0: *res[10], A1: *res[11]}, - }, - } + div := E12{A0: *res[0], A1: *res[1], A2: *res[2], A3: *res[3], A4: *res[4], A5: *res[5], A6: *res[6], A7: *res[7], A8: *res[8], A9: *res[9], A10: *res[10], A11: *res[11]} - // x == div * y + // x = div * y _x := e.Mul(&div, y) e.AssertIsEqual(x, _x) return &div + +} + +func (e Ext12) AssertIsEqual(a, b *E12) { + e.fp.AssertIsEqual(&a.A0, &b.A0) + e.fp.AssertIsEqual(&a.A1, &b.A1) + e.fp.AssertIsEqual(&a.A2, &b.A2) + e.fp.AssertIsEqual(&a.A3, &b.A3) + e.fp.AssertIsEqual(&a.A4, &b.A4) + e.fp.AssertIsEqual(&a.A5, &b.A5) + e.fp.AssertIsEqual(&a.A6, &b.A6) + e.fp.AssertIsEqual(&a.A7, &b.A7) + e.fp.AssertIsEqual(&a.A8, &b.A8) + e.fp.AssertIsEqual(&a.A9, &b.A9) + e.fp.AssertIsEqual(&a.A10, &b.A10) + e.fp.AssertIsEqual(&a.A11, &b.A11) +} + +func (e Ext12) IsEqual(x, y *E12) 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) + diff6 := e.fp.Sub(&x.A6, &y.A6) + diff7 := e.fp.Sub(&x.A7, &y.A7) + diff8 := e.fp.Sub(&x.A8, &y.A8) + diff9 := e.fp.Sub(&x.A9, &y.A9) + diff10 := e.fp.Sub(&x.A10, &y.A10) + diff11 := e.fp.Sub(&x.A11, &y.A11) + 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) + isZero6 := e.fp.IsZero(diff6) + isZero7 := e.fp.IsZero(diff7) + isZero8 := e.fp.IsZero(diff8) + isZero9 := e.fp.IsZero(diff9) + isZero10 := e.fp.IsZero(diff10) + isZero11 := e.fp.IsZero(diff11) + + return e.api.And( + e.api.And( + e.api.And(e.api.And(isZero0, isZero1), e.api.And(isZero2, isZero3)), + e.api.And(e.api.And(isZero4, isZero5), e.api.And(isZero6, isZero7)), + ), + e.api.And(e.api.And(isZero8, isZero9), e.api.And(isZero10, isZero11)), + ) +} + +func (e Ext12) Copy(x *E12) *E12 { + return &E12{ + A0: x.A0, + A1: x.A1, + A2: x.A2, + A3: x.A3, + A4: x.A4, + A5: x.A5, + A6: x.A6, + A7: x.A7, + A8: x.A8, + A9: x.A9, + A10: x.A10, + A11: x.A11, + } +} + +// tower to direct extension conversion +func FromE12(a *bls12381.E12) E12 { + // gnark-crypto uses a quadratic over cubic over quadratic 12th extension of Fp. + // The two towers are isomorphic and the coefficients are permuted as follows: + // a000 a001 a010 a011 a020 a021 a100 a101 a110 a111 a120 a121 + // a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 + // + // A0 = a000 - a001 + // A1 = a100 - a101 + // A2 = a010 - a011 + // A3 = a110 - a111 + // A4 = a020 - a021 + // A5 = a120 - a121 + // A6 = a001 + // A7 = a101 + // A8 = a011 + // A9 = a111 + // A10 = a021 + // A11 = a121 + + var c0, c1, c2, c3, c4, c5 fp.Element + c0.Sub(&a.C0.B0.A0, &a.C0.B0.A1) + c1.Sub(&a.C1.B0.A0, &a.C1.B0.A1) + c2.Sub(&a.C0.B1.A0, &a.C0.B1.A1) + c3.Sub(&a.C1.B1.A0, &a.C1.B1.A1) + c4.Sub(&a.C0.B2.A0, &a.C0.B2.A1) + c5.Sub(&a.C1.B2.A0, &a.C1.B2.A1) + + return E12{ + A0: emulated.ValueOf[emulated.BLS12381Fp](c0), + A1: emulated.ValueOf[emulated.BLS12381Fp](c1), + A2: emulated.ValueOf[emulated.BLS12381Fp](c2), + A3: emulated.ValueOf[emulated.BLS12381Fp](c3), + A4: emulated.ValueOf[emulated.BLS12381Fp](c4), + A5: emulated.ValueOf[emulated.BLS12381Fp](c5), + A6: emulated.ValueOf[emulated.BLS12381Fp](a.C0.B0.A1), + A7: emulated.ValueOf[emulated.BLS12381Fp](a.C1.B0.A1), + A8: emulated.ValueOf[emulated.BLS12381Fp](a.C0.B1.A1), + A9: emulated.ValueOf[emulated.BLS12381Fp](a.C1.B1.A1), + A10: emulated.ValueOf[emulated.BLS12381Fp](a.C0.B2.A1), + A11: emulated.ValueOf[emulated.BLS12381Fp](a.C1.B2.A1), + } +} + +func (e Ext12) ToTower(a *E12) [12]*baseEl { + // gnark-crypto uses a quadratic over cubic over quadratic 12th extension of Fp. + // The two towers are isomorphic and the coefficients are permuted as follows: + // + // tower = a000 a001 a010 a011 a020 a021 a100 a101 a110 a111 a120 a121 + // direct = a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 + // + // a000 = A0 + A6 + // a001 = A6 + // a010 = A2 + A8 + // a011 = A8 + // a020 = A4 + A10 + // a021 = A10 + // a100 = A1 + A7 + // a101 = A7 + // a110 = A3 + A9 + // a111 = A9 + // a120 = A5 + A11 + // a121 = A11 + a000 := e.fp.Add(&a.A0, &a.A6) + a001 := &a.A6 + a010 := e.fp.Add(&a.A2, &a.A8) + a011 := &a.A8 + a020 := e.fp.Add(&a.A4, &a.A10) + a021 := &a.A10 + a100 := e.fp.Add(&a.A1, &a.A7) + a101 := &a.A7 + a110 := e.fp.Add(&a.A3, &a.A9) + a111 := &a.A9 + a120 := e.fp.Add(&a.A5, &a.A11) + a121 := &a.A11 + + tower := [12]*baseEl{a000, a001, a010, a011, a020, a021, a100, a101, a110, a111, a120, a121} + return tower +} + +func (e Ext12) FromTower(tower [12]*baseEl) *E12 { + // gnark-crypto uses a quadratic over cubic over quadratic 12th extension of Fp. + // The two towers are isomorphic and the coefficients are permuted as follows: + // + // tower = a000 a001 a010 a011 a020 a021 a100 a101 a110 a111 a120 a121 + // direct = a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 + // + // A0 = a000 - a001 + // A1 = a100 - a101 + // A2 = a010 - a011 + // A3 = a110 - a111 + // A4 = a020 - a021 + // A5 = a120 - a121 + // A6 = a001 + // A7 = a101 + // A8 = a011 + // A9 = a111 + // A10 = a021 + // A11 = a121 + A0 := e.fp.Sub(tower[0], tower[1]) + A1 := e.fp.Sub(tower[6], tower[7]) + A2 := e.fp.Sub(tower[2], tower[3]) + A3 := e.fp.Sub(tower[8], tower[9]) + A4 := e.fp.Sub(tower[4], tower[5]) + A5 := e.fp.Sub(tower[10], tower[11]) + + return &E12{ + A0: *A0, + A1: *A1, + A2: *A2, + A3: *A3, + A4: *A4, + A5: *A5, + A6: *tower[1], + A7: *tower[7], + A8: *tower[3], + A9: *tower[9], + A10: *tower[5], + A11: *tower[11], + } +} + +// Granger-Scott's cyclotomic square +// https://eprint.iacr.org/2009/565.pdf, 3.2 +func (e Ext12) CyclotomicSquareGS(x *E12) *E12 { + tower := e.ToTower(x) + + mone := e.fp.NewElement(-1) + z000 := e.fp.Eval([][]*baseEl{{tower[8], tower[8]}, {mone, tower[9], tower[9]}, {mone, tower[8], tower[9]}, {tower[0], tower[0]}, {mone, tower[1], tower[1]}, {mone, tower[0]}}, []int{3, 3, 6, 3, 3, 2}) + z001 := e.fp.Eval([][]*baseEl{{tower[8], tower[8]}, {mone, tower[9], tower[9]}, {tower[8], tower[9]}, {tower[0], tower[1]}, {mone, tower[1]}}, []int{3, 3, 6, 6, 2}) + z010 := e.fp.Eval([][]*baseEl{{tower[4], tower[4]}, {mone, tower[5], tower[5]}, {mone, tower[4], tower[5]}, {tower[6], tower[6]}, {mone, tower[7], tower[7]}, {mone, tower[2]}}, []int{3, 3, 6, 3, 3, 2}) + z011 := e.fp.Eval([][]*baseEl{{tower[4], tower[4]}, {mone, tower[5], tower[5]}, {tower[4], tower[5]}, {tower[6], tower[7]}, {mone, tower[3]}}, []int{3, 3, 6, 6, 2}) + z020 := e.fp.Eval([][]*baseEl{{tower[10], tower[10]}, {mone, tower[11], tower[11]}, {mone, tower[10], tower[11]}, {tower[2], tower[2]}, {mone, tower[3], tower[3]}, {mone, tower[4]}}, []int{3, 3, 6, 3, 3, 2}) + z021 := e.fp.Eval([][]*baseEl{{tower[10], tower[10]}, {mone, tower[11], tower[11]}, {tower[10], tower[11]}, {tower[2], tower[3]}, {mone, tower[5]}}, []int{3, 3, 6, 6, 2}) + z100 := e.fp.Eval([][]*baseEl{{tower[2], tower[10]}, {mone, tower[3], tower[11]}, {mone, tower[2], tower[11]}, {mone, tower[3], tower[10]}, {tower[6]}}, []int{6, 6, 6, 6, 2}) + z101 := e.fp.Eval([][]*baseEl{{tower[2], tower[10]}, {mone, tower[3], tower[11]}, {tower[2], tower[11]}, {tower[3], tower[10]}, {tower[7]}}, []int{6, 6, 6, 6, 2}) + z110 := e.fp.Eval([][]*baseEl{{tower[0], tower[8]}, {mone, tower[1], tower[9]}, {tower[8]}}, []int{6, 6, 2}) + z111 := e.fp.Eval([][]*baseEl{{tower[0], tower[9]}, {tower[1], tower[8]}, {tower[9]}}, []int{6, 6, 2}) + z120 := e.fp.Eval([][]*baseEl{{tower[4], tower[6]}, {mone, tower[5], tower[7]}, {tower[10]}}, []int{6, 6, 2}) + z121 := e.fp.Eval([][]*baseEl{{tower[4], tower[7]}, {tower[5], tower[6]}, {tower[11]}}, []int{6, 6, 2}) + + direct := e.FromTower([12]*baseEl{z000, z001, z010, z011, z020, z021, z100, z101, z110, z111, z120, z121}) + + return direct } -func (e Ext12) Select(selector frontend.Variable, z1, z0 *E12) *E12 { - c0 := e.Ext6.Select(selector, &z1.C0, &z0.C0) - c1 := e.Ext6.Select(selector, &z1.C1, &z0.C1) - return &E12{C0: *c0, C1: *c1} +func (e Ext12) Frobenius(a *E12) *E12 { + tower := e.ToTower(a) + + tower[1] = e.fp.Neg(tower[1]) + tower[3] = e.fp.Neg(tower[3]) + tower[5] = e.fp.Neg(tower[5]) + tower[7] = e.fp.Neg(tower[7]) + tower[9] = e.fp.Neg(tower[9]) + tower[11] = e.fp.Neg(tower[11]) + + t1 := e.Ext2.MulByNonResidue1Power2(&E2{A0: *tower[2], A1: *tower[3]}) + t2 := e.Ext2.MulByNonResidue1Power4(&E2{A0: *tower[4], A1: *tower[5]}) + t3 := e.Ext2.MulByNonResidue1Power1(&E2{A0: *tower[6], A1: *tower[7]}) + t4 := e.Ext2.MulByNonResidue1Power3(&E2{A0: *tower[8], A1: *tower[9]}) + t5 := e.Ext2.MulByNonResidue1Power5(&E2{A0: *tower[10], A1: *tower[11]}) + + A0 := e.fp.Sub(tower[0], tower[1]) + A1 := e.fp.Sub(&t3.A0, &t3.A1) + A2 := e.fp.Sub(&t1.A0, &t1.A1) + A3 := e.fp.Sub(&t4.A0, &t4.A1) + A4 := e.fp.Sub(&t2.A0, &t2.A1) + A5 := e.fp.Sub(&t5.A0, &t5.A1) + + return &E12{ + A0: *A0, + A1: *A1, + A2: *A2, + A3: *A3, + A4: *A4, + A5: *A5, + A6: *tower[1], + A7: t3.A1, + A8: t1.A1, + A9: t4.A1, + A10: t2.A1, + A11: t5.A1, + } } -func (e Ext12) Lookup2(s1, s2 frontend.Variable, a, b, c, d *E12) *E12 { - c0 := e.Ext6.Lookup2(s1, s2, &a.C0, &b.C0, &c.C0, &d.C0) - c1 := e.Ext6.Lookup2(s1, s2, &a.C1, &b.C1, &c.C1, &d.C1) - return &E12{C0: *c0, C1: *c1} +func (e Ext12) FrobeniusSquare(a *E12) *E12 { + tower := e.ToTower(a) + + t1 := e.Ext2.MulByNonResidue2Power2(&E2{A0: *tower[2], A1: *tower[3]}) + t2 := e.Ext2.MulByNonResidue2Power4(&E2{A0: *tower[4], A1: *tower[5]}) + t3 := e.Ext2.MulByNonResidue2Power1(&E2{A0: *tower[6], A1: *tower[7]}) + t4 := e.Ext2.MulByNonResidue2Power3(&E2{A0: *tower[8], A1: *tower[9]}) + t5 := e.Ext2.MulByNonResidue2Power5(&E2{A0: *tower[10], A1: *tower[11]}) + + A0 := e.fp.Sub(tower[0], tower[1]) + A1 := e.fp.Sub(&t3.A0, &t3.A1) + A2 := e.fp.Sub(&t1.A0, &t1.A1) + A3 := e.fp.Sub(&t4.A0, &t4.A1) + A4 := e.fp.Sub(&t2.A0, &t2.A1) + A5 := e.fp.Sub(&t5.A0, &t5.A1) + + return &E12{ + A0: *A0, + A1: *A1, + A2: *A2, + A3: *A3, + A4: *A4, + A5: *A5, + A6: *tower[1], + A7: t3.A1, + A8: t1.A1, + A9: t4.A1, + A10: t2.A1, + A11: t5.A1, + } } diff --git a/std/algebra/emulated/fields_bls12381/e12_pairing.go b/std/algebra/emulated/fields_bls12381/e12_pairing.go index 7e4f26d9b..1ae94d3df 100644 --- a/std/algebra/emulated/fields_bls12381/e12_pairing.go +++ b/std/algebra/emulated/fields_bls12381/e12_pairing.go @@ -1,17 +1,22 @@ package fields_bls12381 -import "github.com/consensys/gnark/std/math/emulated" +func (e Ext12) nSquare(z *E12, n int) *E12 { + for i := 0; i < n; i++ { + z = e.Square(z) + } + return z +} func (e Ext12) nSquareGS(z *E12, n int) *E12 { for i := 0; i < n; i++ { - z = e.CyclotomicSquare(z) + z = e.CyclotomicSquareGS(z) } return z } -// Expt sets z to x^t in E12 and return z +// ExptNeg sets z to x^t in E12 and return z // where t = -u = 15132376222941642752 -func (e Ext12) Expt(x *E12) *E12 { +func (e Ext12) ExptNeg(x *E12) *E12 { // Expt computation is derived from the addition chain: // // _10 = 2*1 @@ -26,108 +31,26 @@ func (e Ext12) Expt(x *E12) *E12 { // // Generated by github.com/mmcloughlin/addchain v0.4.0. - z := e.CyclotomicSquare(x) + z := e.Square(x) z = e.Mul(x, z) - z = e.nSquareGS(z, 2) + z = e.nSquare(z, 2) z = e.Mul(x, z) - z = e.nSquareGS(z, 3) + z = e.nSquare(z, 3) z = e.Mul(x, z) - z = e.nSquareGS(z, 9) + z = e.nSquare(z, 9) z = e.Mul(x, z) - z = e.nSquareGS(z, 32) - z = e.Mul(x, z) - z = e.nSquareGS(z, 15) - z = e.CyclotomicSquare(z) - - return z -} - -// ExpByU sets z to x^U in E12 and return z -// where U = (u-1)^2/3 = 76329603384216526031706109802092473003 -func (e Ext12) ExpByU(x *E12) *E12 { - // ExpByU computation is derived from the addition chain: - // - // _10 = 2*1 - // _11 = 1 + _10 - // _110 = 2*_11 - // _111 = 1 + _110 - // _1000 = 1 + _111 - // _100000 = _1000 << 2 - // _100011 = _11 + _100000 - // _101010 = _111 + _100011 - // _1010100 = 2*_101010 - // _1010101 = 1 + _1010100 - // _1010110 = 1 + _1010101 - // _1011001 = _11 + _1010110 - // _1100001 = _1000 + _1011001 - // _10101011 = _1010101 + _1010110 - // _11000010 = 2*_1100001 - // _11100101 = _100011 + _11000010 - // i49 = ((_11100101 << 7 + _1011001) << 5 + _11) << 18 - // i68 = ((_1010101 + i49) << 9 + _10101011) << 7 + _1100001 - // i85 = (i68 << 9 + _10 + _10101011) << 5 + _11 - // i136 = ((i85 << 17 + _1010101) << 9 + _10101011) << 23 - // return (_1010101 + i136) << 9 + _10101011 - // - // Operations: 124 squares 23 multiplies - // - // Generated by github.com/mmcloughlin/addchain v0.4.0. - - t2 := e.CyclotomicSquare(x) - t1 := e.Mul(x, t2) - z := e.CyclotomicSquare(t1) + z = e.nSquare(z, 32) z = e.Mul(x, z) - t3 := e.Mul(x, z) - t0 := e.CyclotomicSquare(t3) - t0 = e.CyclotomicSquare(t0) - t5 := e.Mul(t1, t0) - z = e.Mul(z, t5) - z = e.CyclotomicSquare(z) - t0 = e.Mul(x, z) - z = e.Mul(x, t0) - t4 := e.Mul(t1, z) - t3 = e.Mul(t3, t4) - z = e.Mul(t0, z) - t6 := e.CyclotomicSquare(t3) - t5 = e.Mul(t5, t6) - t5 = e.nSquareGS(t5, 7) - t4 = e.Mul(t4, t5) - t4 = e.nSquareGS(t4, 5) - t4 = e.Mul(t1, t4) - t4 = e.nSquareGS(t4, 18) - t4 = e.Mul(t0, t4) - t4 = e.nSquareGS(t4, 9) - t4 = e.Mul(z, t4) - t4 = e.nSquareGS(t4, 7) - t3 = e.Mul(t3, t4) - t3 = e.nSquareGS(t3, 9) - t2 = e.Mul(t2, t3) - t2 = e.Mul(z, t2) - t2 = e.nSquareGS(t2, 5) - t1 = e.Mul(t1, t2) - t1 = e.nSquareGS(t1, 17) - t1 = e.Mul(t0, t1) - t1 = e.nSquareGS(t1, 9) - t1 = e.Mul(z, t1) - t1 = e.nSquareGS(t1, 23) - t0 = e.Mul(t0, t1) - t0 = e.nSquareGS(t0, 9) - z = e.Mul(z, t0) + z = e.nSquare(z, 15) + z = e.Square(z) return z } -func (e Ext12) nSquareTorus(z *E6, n int) *E6 { - for i := 0; i < n; i++ { - z = e.SquareTorus(z) - } - return z -} - -// ExptHalfTorus set z to x^(t/2) in E6 and return z -// const t/2 uint64 = 7566188111470821376 // negative -func (e Ext12) ExptHalfTorus(x *E6) *E6 { - // FixedExp computation is derived from the addition chain: +// ExptGS sets z to x^t in E12 and return z +// where t = u = -15132376222941642752 +func (e Ext12) ExptGS(x *E12) *E12 { + // ExptGS computation is derived from the addition chain: // // _10 = 2*1 // _11 = 1 + _10 @@ -140,334 +63,97 @@ func (e Ext12) ExptHalfTorus(x *E6) *E6 { // Operations: 62 squares 5 multiplies // // Generated by github.com/mmcloughlin/addchain v0.4.0. - - // Step 1: z = x^0x2 - z := e.SquareTorus(x) - - // Step 2: z = x^0x3 - z = e.MulTorus(x, z) - - z = e.SquareTorus(z) - z = e.SquareTorus(z) - - // Step 5: z = x^0xd - z = e.MulTorus(x, z) - - // Step 8: z = x^0x68 - z = e.nSquareTorus(z, 3) - - // Step 9: z = x^0x69 - z = e.MulTorus(x, z) - - // Step 18: z = x^0xd200 - z = e.nSquareTorus(z, 9) - - // Step 19: z = x^0xd201 - z = e.MulTorus(x, z) - - // Step 51: z = x^0xd20100000000 - z = e.nSquareTorus(z, 32) - - // Step 52: z = x^0xd20100000001 - z = e.MulTorus(x, z) - - // Step 67: z = x^0x6900800000008000 - z = e.nSquareTorus(z, 15) - - z = e.InverseTorus(z) // because tAbsVal is negative - + z := e.ExptHalfGS(x) + z = e.CyclotomicSquareGS(z) return z } -// ExptTorus set z to xᵗ in E6 and return z -// const t uint64 = 15132376222941642752 // negative -func (e Ext12) ExptTorus(x *E6) *E6 { - z := e.ExptHalfTorus(x) - z = e.SquareTorus(z) - return z -} - -// MulBy014 multiplies z by an E12 sparse element of the form -// -// E12{ -// C0: E6{B0: c0, B1: c1, B2: 0}, -// C1: E6{B0: 0, B1: 1, B2: 0}, -// } -func (e *Ext12) MulBy014(z *E12, c0, c1 *E2) *E12 { - - a := e.MulBy01(&z.C0, c0, c1) - - var b E6 - // Mul by E6{0, 1, 0} - b.B0 = *e.Ext2.MulByNonResidue(&z.C1.B2) - b.B2 = z.C1.B1 - b.B1 = z.C1.B0 - - one := e.Ext2.One() - d := e.Ext2.Add(c1, one) - - zC1 := e.Ext6.Add(&z.C1, &z.C0) - zC1 = e.Ext6.MulBy01(zC1, c0, d) - tmp := e.Ext6.Add(&b, a) - zC1 = e.Ext6.Sub(zC1, tmp) - zC0 := e.Ext6.MulByNonResidue(&b) - zC0 = e.Ext6.Add(zC0, a) - - return &E12{ - C0: *zC0, - C1: *zC1, - } -} - -// multiplies two E12 sparse element of the form: -// -// E12{ -// C0: E6{B0: c0, B1: c1, B2: 0}, -// C1: E6{B0: 0, B1: 1, B2: 0}, -// } -// -// and -// -// E12{ -// C0: E6{B0: d0, B1: d1, B2: 0}, -// C1: E6{B0: 0, B1: 1, B2: 0}, -// } -func (e Ext12) Mul014By014(d0, d1, c0, c1 *E2) [5]*E2 { - x0 := e.Ext2.Mul(c0, d0) - x1 := e.Ext2.Mul(c1, d1) - x04 := e.Ext2.Add(c0, d0) - tmp := e.Ext2.Add(c0, c1) - x01 := e.Ext2.Add(d0, d1) - x01 = e.Ext2.Mul(x01, tmp) - tmp = e.Ext2.Add(x1, x0) - x01 = e.Ext2.Sub(x01, tmp) - x14 := e.Ext2.Add(c1, d1) - - zC0B0 := e.Ext2.NonResidue() - zC0B0 = e.Ext2.Add(zC0B0, x0) - - return [5]*E2{zC0B0, x01, x1, x04, x14} -} - -// MulBy01245 multiplies z by an E12 sparse element of the form -// -// E12{ -// C0: E6{B0: c0, B1: c1, B2: c2}, -// C1: E6{B0: 0, B1: c4, B2: c5}, -// } -func (e *Ext12) MulBy01245(z *E12, x [5]*E2) *E12 { - c0 := &E6{B0: *x[0], B1: *x[1], B2: *x[2]} - c1 := &E6{B0: *e.Ext2.Zero(), B1: *x[3], B2: *x[4]} - a := e.Ext6.Add(&z.C0, &z.C1) - b := e.Ext6.Add(c0, c1) - a = e.Ext6.Mul(a, b) - b = e.Ext6.Mul(&z.C0, c0) - c := e.Ext6.MulBy12(&z.C1, x[3], x[4]) - d := e.Ext6.Add(c, b) - z1 := e.Ext6.Sub(a, d) - z0 := e.Ext6.MulByNonResidue(c) - z0 = e.Ext6.Add(z0, b) - return &E12{ - C0: *z0, - C1: *z1, - } -} - -// Torus-based arithmetic: -// -// After the easy part of the final exponentiation the elements are in a proper -// subgroup of Fpk (E12) that coincides with some algebraic tori. The elements -// are in the torus Tk(Fp) and thus in each torus Tk/d(Fp^d) for d|k, d≠k. We -// take d=6. So the elements are in T2(Fp6). -// Let G_{q,2} = {m ∈ Fq^2 | m^(q+1) = 1} where q = p^6. -// When m.C1 = 0, then m.C0 must be 1 or −1. -// -// We recall the tower construction: -// -// 𝔽p²[u] = 𝔽p/u²+1 -// 𝔽p⁶[v] = 𝔽p²/v³-1-u -// 𝔽p¹²[w] = 𝔽p⁶/w²-v - -// CompressTorus compresses x ∈ E12 to (x.C0 + 1)/x.C1 ∈ E6 -func (e Ext12) CompressTorus(x *E12) *E6 { - // x ∈ G_{q,2} \ {-1,1} - y := e.Ext6.Add(&x.C0, e.Ext6.One()) - y = e.Ext6.DivUnchecked(y, &x.C1) - return y -} - -// DecompressTorus decompresses y ∈ E6 to (y+w)/(y-w) ∈ E12 -func (e Ext12) DecompressTorus(y *E6) *E12 { - var n, d E12 - one := e.Ext6.One() - n.C0 = *y - n.C1 = *one - d.C0 = *y - d.C1 = *e.Ext6.Neg(one) - - x := e.DivUnchecked(&n, &d) - return x -} - -// MulTorus multiplies two compressed elements y1, y2 ∈ E6 -// and returns (y1 * y2 + v)/(y1 + y2) -// N.B.: we use MulTorus in the final exponentiation throughout y1 ≠ -y2 always. -func (e Ext12) MulTorus(y1, y2 *E6) *E6 { - n := e.Ext6.Mul(y1, y2) - n.B1 = *e.Ext2.Add(&n.B1, e.Ext2.One()) - d := e.Ext6.Add(y1, y2) - y3 := e.Ext6.DivUnchecked(n, d) - return y3 -} +func (e Ext12) ExptHalfGS(x *E12) *E12 { + z := e.CyclotomicSquareGS(x) + z = e.Mul(x, z) + z = e.nSquareGS(z, 2) + z = e.Mul(x, z) + z = e.nSquareGS(z, 3) + z = e.Mul(x, z) + z = e.nSquareGS(z, 9) + z = e.Mul(x, z) + z = e.nSquareGS(z, 32) + z = e.Mul(x, z) + z = e.nSquareGS(z, 15) + z = e.Conjugate(z) -// InverseTorus inverses a compressed elements y ∈ E6 -// and returns -y -func (e Ext12) InverseTorus(y *E6) *E6 { - return e.Ext6.Neg(y) + return z } -// SquareTorus squares a compressed elements y ∈ E6 -// and returns (y + v/y)/2 +// MulBy02368 multiplies a by an E12 sparse element b of the form // -// It uses a hint to verify that (2x-y)y = v saving one E6 AssertIsEqual. -func (e Ext12) SquareTorus(y *E6) *E6 { - res, err := e.fp.NewHint(squareTorusHint, 6, &y.B0.A0, &y.B0.A1, &y.B1.A0, &y.B1.A1, &y.B2.A0, &y.B2.A1) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } +// b.A0 = c00 - c01 +// b.A1 = 0 +// b.A2 = c10 - c11 +// b.A3 = 1 +// b.A4 = 0 +// b.A5 = 0 +// b.A6 = c01 +// b.A7 = 0 +// b.A8 = c11 +// b.A9 = 0 +// b.A10 = 0 +// b.A11 = 0 +func (e *Ext12) MulBy02368(a *E12, c0, c1 *E2) *E12 { + b0 := e.fp.Sub(&c0.A0, &c0.A1) + b2 := e.fp.Sub(&c1.A0, &c1.A1) + b6 := &c0.A1 + b8 := &c1.A1 + mone := e.fp.NewElement(-1) - sq := E6{ - B0: E2{A0: *res[0], A1: *res[1]}, - B1: E2{A0: *res[2], A1: *res[3]}, - B2: E2{A0: *res[4], A1: *res[5]}, - } + // d0 = a0 b0 - 2 * (a4 b8 + a6 b6 + a10 b2 + a9) - 4 * a10 b8 + d0 := e.fp.Eval([][]*baseEl{{&a.A0, b0}, {mone, &a.A4, b8}, {mone, &a.A6, b6}, {mone, &a.A9}, {mone, &a.A10, b2}, {mone, &a.A10, b8}}, []int{1, 2, 2, 2, 2, 4}) - // v = (2x-y)y - v := e.Ext6.Double(&sq) - v = e.Ext6.Sub(v, y) - v = e.Ext6.Mul(v, y) + // d1 = a1 b0 - 2 * (a5 b8 + a7 b6 + a11 b2 + a10) - 4 * a11 b8 + d1 := e.fp.Eval([][]*baseEl{{&a.A1, b0}, {mone, &a.A5, b8}, {mone, &a.A7, b6}, {mone, &a.A10}, {mone, &a.A11, b2}, {mone, &a.A11, b8}}, []int{1, 2, 2, 2, 2, 4}) - _v := E6{B0: *e.Ext2.Zero(), B1: *e.Ext2.One(), B2: *e.Ext2.Zero()} - e.Ext6.AssertIsEqual(v, &_v) + // d2 = a0 b2 + a2 b0 - 2 * (a6 b8 + a8 b6 + a11) + d2 := e.fp.Eval([][]*baseEl{{&a.A0, b2}, {&a.A2, b0}, {mone, &a.A6, b8}, {mone, &a.A8, b6}, {mone, &a.A11}}, []int{1, 1, 2, 2, 2}) - return &sq + // d3 = a0 + a1 b2 + a3 b0 - 2 * (a7 b8 + a9 b6) + d3 := e.fp.Eval([][]*baseEl{{&a.A0}, {&a.A1, b2}, {&a.A3, b0}, {mone, &a.A7, b8}, {mone, &a.A9, b6}}, []int{1, 1, 1, 2, 2}) -} + // d4 = a1 + a2 b2 + a4 b0 - 2 * (a8 b8 + a10 b6) + d4 := e.fp.Eval([][]*baseEl{{&a.A1}, {&a.A2, b2}, {&a.A4, b0}, {mone, &a.A8, b8}, {mone, &a.A10, b6}}, []int{1, 1, 1, 2, 2}) -// FrobeniusTorus raises a compressed elements y ∈ E6 to the modulus p -// and returns y^p / v^((p-1)/2) -func (e Ext12) FrobeniusTorus(y *E6) *E6 { - t0 := e.Ext2.Conjugate(&y.B0) - t1 := e.Ext2.Conjugate(&y.B1) - t2 := e.Ext2.Conjugate(&y.B2) - t1 = e.Ext2.MulByNonResidue1Power2(t1) - t2 = e.Ext2.MulByNonResidue1Power4(t2) + // d5 = a2 + a3 b2 + a5 b0 - 2 * (a9 b8 + a11 b6) + d5 := e.fp.Eval([][]*baseEl{{&a.A2}, {&a.A3, b2}, {&a.A5, b0}, {mone, &a.A9, b8}, {mone, &a.A11, b6}}, []int{1, 1, 1, 2, 2}) - v0 := E2{emulated.ValueOf[emulated.BLS12381Fp]("877076961050607968509681729531255177986764537961432449499635504522207616027455086505066378536590128544573588734230"), emulated.ValueOf[emulated.BLS12381Fp]("877076961050607968509681729531255177986764537961432449499635504522207616027455086505066378536590128544573588734230")} - res := &E6{B0: *t0, B1: *t1, B2: *t2} - res = e.Ext6.MulBy0(res, &v0) + // d6 = a0 b6 + a3 + a4 b2 + a6 b0 + 2 * (a4 b8 + a6 b6 + a9 + a10 b2 + a10 b8) + d6 := e.fp.Eval([][]*baseEl{{&a.A0, b6}, {&a.A3}, {&a.A4, b2}, {&a.A6, b0}, {&a.A4, b8}, {&a.A6, b6}, {&a.A9}, {&a.A10, b2}, {&a.A10, b8}}, []int{1, 1, 1, 1, 2, 2, 2, 2, 2}) - return res -} + // d7 = a1 b6 + a4 + a5 b2 + a7 b0 + 2 * (a5 b8 + a7 b6 + a10 + a11 b2 + a11 b8) + d7 := e.fp.Eval([][]*baseEl{{&a.A1, b6}, {&a.A4}, {&a.A5, b2}, {&a.A7, b0}, {&a.A5, b8}, {&a.A7, b6}, {&a.A10}, {&a.A11, b2}, {&a.A11, b8}}, []int{1, 1, 1, 1, 2, 2, 2, 2, 2}) -// FrobeniusSquareTorus raises a compressed elements y ∈ E6 to the square modulus p^2 -// and returns y^(p^2) / v^((p^2-1)/2) -func (e Ext12) FrobeniusSquareTorus(y *E6) *E6 { - v0 := emulated.ValueOf[emulated.BLS12381Fp]("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939437") - t0 := e.Ext2.MulByElement(&y.B0, &v0) - t1 := e.Ext2.MulByNonResidue2Power2(&y.B1) - t1 = e.Ext2.MulByElement(t1, &v0) - t2 := e.Ext2.MulByNonResidue2Power4(&y.B2) - t2 = e.Ext2.MulByElement(t2, &v0) + // d8 = a0 b8 + a2 b6 + a5 + a6 b2 + a8 b0 + 2 * (a6 b8 + a8 b6 + a11) + d8 := e.fp.Eval([][]*baseEl{{&a.A0, b8}, {&a.A2, b6}, {&a.A5}, {&a.A6, b2}, {&a.A8, b0}, {&a.A6, b8}, {&a.A8, b6}, {&a.A11}}, []int{1, 1, 1, 1, 1, 2, 2, 2}) - return &E6{B0: *t0, B1: *t1, B2: *t2} -} + // d9 = a1 b8 + a3 b6 + a6 + a7 b2 + a9 b0 + 2 * (a7 b8 + a9 b6) + d9 := e.fp.Eval([][]*baseEl{{&a.A1, b8}, {&a.A3, b6}, {&a.A6}, {&a.A7, b2}, {&a.A9, b0}, {&a.A7, b8}, {&a.A9, b6}}, []int{1, 1, 1, 1, 1, 2, 2}) -// AssertFinalExponentiationIsOne checks that a Miller function output x lies in the -// same equivalence class as the reduced pairing. This replaces the final -// exponentiation step in-circuit. -// The method is inspired from [On Proving Pairings] paper by A. Novakovic and -// L. Eagen, and is based on a personal communication with A. Novakovic. -// -// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf -func (e Ext12) AssertFinalExponentiationIsOne(x *E12) { - res, err := e.fp.NewHint(finalExpHint, 18, &x.C0.B0.A0, &x.C0.B0.A1, &x.C0.B1.A0, &x.C0.B1.A1, &x.C0.B2.A0, &x.C0.B2.A1, &x.C1.B0.A0, &x.C1.B0.A1, &x.C1.B1.A0, &x.C1.B1.A1, &x.C1.B2.A0, &x.C1.B2.A1) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - - residueWitness := E12{ - C0: E6{ - B0: E2{A0: *res[0], A1: *res[1]}, - B1: E2{A0: *res[2], A1: *res[3]}, - B2: E2{A0: *res[4], A1: *res[5]}, - }, - C1: E6{ - B0: E2{A0: *res[6], A1: *res[7]}, - B1: E2{A0: *res[8], A1: *res[9]}, - B2: E2{A0: *res[10], A1: *res[11]}, - }, - } - // constrain cubicNonResiduePower to be in Fp6 - scalingFactor := E6{ - B0: E2{A0: *res[12], A1: *res[13]}, - B1: E2{A0: *res[14], A1: *res[15]}, - B2: E2{A0: *res[16], A1: *res[17]}, - } - - // Check that x * scalingFactor == residueWitness^(q-u) - // where u=-0xd201000000010000 is the BLS12-381 seed, - // and residueWitness, scalingFactor from the hint. - t0 := e.Frobenius(&residueWitness) - // exponentiation by -u - t1 := e.Expt(&residueWitness) - t0 = e.Mul(t0, t1) - - t1 = &E12{ - C0: *e.Ext6.Mul(&x.C0, &scalingFactor), - C1: *e.Ext6.Mul(&x.C1, &scalingFactor), - } - - e.AssertIsEqual(t0, t1) -} + // d10 = a2 b8 + a4 b6 + a7 + a8 b2 + a10 b0 + 2 * (a8 b8 + a10 b6) + d10 := e.fp.Eval([][]*baseEl{{&a.A2, b8}, {&a.A4, b6}, {&a.A7}, {&a.A8, b2}, {&a.A10, b0}, {&a.A8, b8}, {&a.A10, b6}}, []int{1, 1, 1, 1, 1, 2, 2}) -func (e Ext12) Frobenius(x *E12) *E12 { - t0 := e.Ext2.Conjugate(&x.C0.B0) - t1 := e.Ext2.Conjugate(&x.C0.B1) - t2 := e.Ext2.Conjugate(&x.C0.B2) - t3 := e.Ext2.Conjugate(&x.C1.B0) - t4 := e.Ext2.Conjugate(&x.C1.B1) - t5 := e.Ext2.Conjugate(&x.C1.B2) - t1 = e.Ext2.MulByNonResidue1Power2(t1) - t2 = e.Ext2.MulByNonResidue1Power4(t2) - t3 = e.Ext2.MulByNonResidue1Power1(t3) - t4 = e.Ext2.MulByNonResidue1Power3(t4) - t5 = e.Ext2.MulByNonResidue1Power5(t5) - return &E12{ - C0: E6{ - B0: *t0, - B1: *t1, - B2: *t2, - }, - C1: E6{ - B0: *t3, - B1: *t4, - B2: *t5, - }, - } -} + // d11 = a3 b8 + a5 b6 + a8 + a9 b2 + a11 b0 + 2 * (a9 b8 + a11 b6) + d11 := e.fp.Eval([][]*baseEl{{&a.A3, b8}, {&a.A5, b6}, {&a.A8}, {&a.A9, b2}, {&a.A11, b0}, {&a.A9, b8}, {&a.A11, b6}}, []int{1, 1, 1, 1, 1, 2, 2}) -func (e Ext12) FrobeniusSquare(x *E12) *E12 { - z00 := &x.C0.B0 - z01 := e.Ext2.MulByNonResidue2Power2(&x.C0.B1) - z02 := e.Ext2.MulByNonResidue2Power4(&x.C0.B2) - z10 := e.Ext2.MulByNonResidue2Power1(&x.C1.B0) - z11 := e.Ext2.MulByNonResidue2Power3(&x.C1.B1) - z12 := e.Ext2.MulByNonResidue2Power5(&x.C1.B2) return &E12{ - C0: E6{B0: *z00, B1: *z01, B2: *z02}, - C1: E6{B0: *z10, B1: *z11, B2: *z12}, + A0: *d0, + A1: *d1, + A2: *d2, + A3: *d3, + A4: *d4, + A5: *d5, + A6: *d6, + A7: *d7, + A8: *d8, + A9: *d9, + A10: *d10, + A11: *d11, } } diff --git a/std/algebra/emulated/fields_bls12381/e12_test.go b/std/algebra/emulated/fields_bls12381/e12_test.go index cba62ef2b..5a75b1f80 100644 --- a/std/algebra/emulated/fields_bls12381/e12_test.go +++ b/std/algebra/emulated/fields_bls12381/e12_test.go @@ -1,6 +1,7 @@ package fields_bls12381 import ( + "math/big" "testing" "github.com/consensys/gnark-crypto/ecc" @@ -9,6 +10,34 @@ import ( "github.com/consensys/gnark/test" ) +type e12Convert struct { + A E12 +} + +func (circuit *e12Convert) Define(api frontend.API) error { + e := NewExt12(api) + tower := e.ToTower(&circuit.A) + expected := e.FromTower(tower) + e.AssertIsEqual(expected, &circuit.A) + return nil +} + +func TestConvertFp12(t *testing.T) { + + assert := test.NewAssert(t) + // witness values + var a bls12381.E12 + _, _ = a.SetRandom() + + witness := e12Convert{ + A: FromE12(&a), + } + + err := test.IsSolved(&e12Convert{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + +} + type e12Add struct { A, B, C E12 } @@ -102,37 +131,6 @@ func TestMulFp12(t *testing.T) { } -type e12Div struct { - A, B, C E12 -} - -func (circuit *e12Div) Define(api frontend.API) error { - e := NewExt12(api) - expected := e.DivUnchecked(&circuit.A, &circuit.B) - e.AssertIsEqual(expected, &circuit.C) - return nil -} - -func TestDivFp12(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, b, c bls12381.E12 - _, _ = a.SetRandom() - _, _ = b.SetRandom() - c.Div(&a, &b) - - witness := e12Div{ - A: FromE12(&a), - B: FromE12(&b), - C: FromE12(&c), - } - - err := test.IsSolved(&e12Div{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - type e12Square struct { A, C E12 } @@ -162,81 +160,18 @@ func TestSquareFp12(t *testing.T) { } -type e12Conjugate struct { - A E12 - C E12 `gnark:",public"` -} - -func (circuit *e12Conjugate) Define(api frontend.API) error { - e := NewExt12(api) - expected := e.Conjugate(&circuit.A) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestConjugateFp12(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bls12381.E12 - _, _ = a.SetRandom() - c.Conjugate(&a) - - witness := e12Conjugate{ - A: FromE12(&a), - C: FromE12(&c), - } - - err := test.IsSolved(&e12Conjugate{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) -} - -type e12Inverse struct { - A E12 - C E12 `gnark:",public"` -} - -func (circuit *e12Inverse) Define(api frontend.API) error { - e := NewExt12(api) - expected := e.Inverse(&circuit.A) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestInverseFp12(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bls12381.E12 - _, _ = a.SetRandom() - c.Inverse(&a) - - witness := e12Inverse{ - A: FromE12(&a), - C: FromE12(&c), - } - - err := test.IsSolved(&e12Inverse{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) -} - -type e12ExptTorus struct { - A E6 - C E12 `gnark:",public"` +type e12SquareGS struct { + A, C E12 } -func (circuit *e12ExptTorus) Define(api frontend.API) error { +func (circuit *e12SquareGS) Define(api frontend.API) error { e := NewExt12(api) - z := e.ExptTorus(&circuit.A) - expected := e.DecompressTorus(z) + expected := e.CyclotomicSquareGS(&circuit.A) e.AssertIsEqual(expected, &circuit.C) - return nil } -func TestFp12ExptTorus(t *testing.T) { +func TestSquareGSFp12(t *testing.T) { assert := test.NewAssert(t) // witness values @@ -250,332 +185,272 @@ func TestFp12ExptTorus(t *testing.T) { tmp.Mul(&tmp, &a) a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - c.Expt(&a) - _a, _ := a.CompressTorus() - witness := e12ExptTorus{ - A: FromE6(&_a), + c.CyclotomicSquare(&a) + + witness := e12SquareGS{ + A: FromE12(&a), C: FromE12(&c), } - err := test.IsSolved(&e12ExptTorus{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&e12SquareGS{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) + } -type e12MulBy014 struct { - A E12 `gnark:",public"` - W E12 - B, C E2 +type e12Div struct { + A, B, C E12 } -func (circuit *e12MulBy014) Define(api frontend.API) error { +func (circuit *e12Div) Define(api frontend.API) error { e := NewExt12(api) - res := e.MulBy014(&circuit.A, &circuit.B, &circuit.C) - e.AssertIsEqual(res, &circuit.W) + expected := e.DivUnchecked(&circuit.A, &circuit.B) + e.AssertIsEqual(expected, &circuit.C) return nil } -func TestFp12MulBy014(t *testing.T) { +func TestDivFp12(t *testing.T) { assert := test.NewAssert(t) // witness values - var a, w bls12381.E12 + var a, b, c bls12381.E12 _, _ = a.SetRandom() - var one, b, c bls12381.E2 - one.SetOne() _, _ = b.SetRandom() - _, _ = c.SetRandom() - w.Set(&a) - w.MulBy014(&b, &c, &one) + c.Div(&a, &b) - witness := e12MulBy014{ + witness := e12Div{ A: FromE12(&a), - B: FromE2(&b), - C: FromE2(&c), - W: FromE12(&w), + B: FromE12(&b), + C: FromE12(&c), } - err := test.IsSolved(&e12MulBy014{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&e12Div{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } -// Torus-based arithmetic -type torusCompress struct { +type e12Conjugate struct { A E12 - C E6 `gnark:",public"` + C E12 `gnark:",public"` } -func (circuit *torusCompress) Define(api frontend.API) error { +func (circuit *e12Conjugate) Define(api frontend.API) error { e := NewExt12(api) - expected := e.CompressTorus(&circuit.A) - e.Ext6.AssertIsEqual(expected, &circuit.C) + expected := e.Conjugate(&circuit.A) + e.AssertIsEqual(expected, &circuit.C) + return nil } -func TestTorusCompress(t *testing.T) { +func TestConjugateFp12(t *testing.T) { assert := test.NewAssert(t) // witness values - var a bls12381.E12 + var a, c bls12381.E12 _, _ = a.SetRandom() + c.Conjugate(&a) - // put a in the cyclotomic subgroup - var tmp bls12381.E12 - tmp.Conjugate(&a) - a.Inverse(&a) - tmp.Mul(&tmp, &a) - a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - - c, _ := a.CompressTorus() - - witness := torusCompress{ + witness := e12Conjugate{ A: FromE12(&a), - C: FromE6(&c), + C: FromE12(&c), } - err := test.IsSolved(&torusCompress{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&e12Conjugate{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } -type torusDecompress struct { +type e12Inverse struct { A E12 C E12 `gnark:",public"` } -func (circuit *torusDecompress) Define(api frontend.API) error { +func (circuit *e12Inverse) Define(api frontend.API) error { e := NewExt12(api) - compressed := e.CompressTorus(&circuit.A) - expected := e.DecompressTorus(compressed) + expected := e.Inverse(&circuit.A) e.AssertIsEqual(expected, &circuit.C) + return nil } -func TestTorusDecompress(t *testing.T) { +func TestInverseFp12(t *testing.T) { assert := test.NewAssert(t) // witness values - var a bls12381.E12 + var a, c bls12381.E12 _, _ = a.SetRandom() + c.Inverse(&a) - // put a in the cyclotomic subgroup - var tmp bls12381.E12 - tmp.Conjugate(&a) - a.Inverse(&a) - tmp.Mul(&tmp, &a) - a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - - d, _ := a.CompressTorus() - c := d.DecompressTorus() - - witness := torusDecompress{ + witness := e12Inverse{ A: FromE12(&a), C: FromE12(&c), } - err := test.IsSolved(&torusDecompress{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&e12Inverse{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } -type torusMul struct { +type Frobenius struct { A E12 - B E12 C E12 `gnark:",public"` } -func (circuit *torusMul) Define(api frontend.API) error { +func (circuit *Frobenius) Define(api frontend.API) error { e := NewExt12(api) - compressedA := e.CompressTorus(&circuit.A) - compressedB := e.CompressTorus(&circuit.B) - compressedAB := e.MulTorus(compressedA, compressedB) - expected := e.DecompressTorus(compressedAB) + expected := e.Frobenius(&circuit.A) e.AssertIsEqual(expected, &circuit.C) return nil } -func TestTorusMul(t *testing.T) { +func TestFrobenius(t *testing.T) { assert := test.NewAssert(t) // witness values - var a, b, c, tmp bls12381.E12 + var a, c bls12381.E12 _, _ = a.SetRandom() - _, _ = b.SetRandom() - // put a in the cyclotomic subgroup - tmp.Conjugate(&a) - a.Inverse(&a) - tmp.Mul(&tmp, &a) - a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - // put b in the cyclotomic subgroup - tmp.Conjugate(&b) - b.Inverse(&b) - tmp.Mul(&tmp, &b) - b.FrobeniusSquare(&tmp).Mul(&b, &tmp) - - // uncompressed mul - c.Mul(&a, &b) + c.Frobenius(&a) - witness := torusMul{ + witness := Frobenius{ A: FromE12(&a), - B: FromE12(&b), C: FromE12(&c), } - err := test.IsSolved(&torusMul{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&Frobenius{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } -type torusInverse struct { +type FrobeniusSquare struct { A E12 C E12 `gnark:",public"` } -func (circuit *torusInverse) Define(api frontend.API) error { +func (circuit *FrobeniusSquare) Define(api frontend.API) error { e := NewExt12(api) - compressed := e.CompressTorus(&circuit.A) - compressed = e.InverseTorus(compressed) - expected := e.DecompressTorus(compressed) + expected := e.FrobeniusSquare(&circuit.A) e.AssertIsEqual(expected, &circuit.C) return nil } -func TestTorusInverse(t *testing.T) { +func TestFrobeniusSquare(t *testing.T) { assert := test.NewAssert(t) // witness values - var a, c, tmp bls12381.E12 + var a, c bls12381.E12 _, _ = a.SetRandom() - // put a in the cyclotomic subgroup - tmp.Conjugate(&a) - a.Inverse(&a) - tmp.Mul(&tmp, &a) - a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - - // uncompressed inverse - c.Inverse(&a) + c.FrobeniusSquare(&a) - witness := torusInverse{ + witness := FrobeniusSquare{ A: FromE12(&a), C: FromE12(&c), } - err := test.IsSolved(&torusInverse{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&FrobeniusSquare{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } -type torusFrobenius struct { +type e12Expt struct { A E12 C E12 `gnark:",public"` } -func (circuit *torusFrobenius) Define(api frontend.API) error { +func (circuit *e12Expt) Define(api frontend.API) error { e := NewExt12(api) - compressed := e.CompressTorus(&circuit.A) - compressed = e.FrobeniusTorus(compressed) - expected := e.DecompressTorus(compressed) + expected := e.ExptNeg(&circuit.A) e.AssertIsEqual(expected, &circuit.C) + return nil } -func TestTorusFrobenius(t *testing.T) { +func TestFp12Expt(t *testing.T) { assert := test.NewAssert(t) // witness values - var a, c, tmp bls12381.E12 + var a, c bls12381.E12 _, _ = a.SetRandom() - // put a in the cyclotomic subgroup - tmp.Conjugate(&a) - a.Inverse(&a) - tmp.Mul(&tmp, &a) - a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - - // uncompressed frobenius - c.Frobenius(&a) - - witness := torusFrobenius{ + var xGen big.Int + xGen.SetString("15132376222941642752", 10) + c.Exp(a, &xGen) + witness := e12Expt{ A: FromE12(&a), C: FromE12(&c), } - err := test.IsSolved(&torusFrobenius{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&e12Expt{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } -type torusFrobeniusSquare struct { +type e12ExptGS struct { A E12 C E12 `gnark:",public"` } -func (circuit *torusFrobeniusSquare) Define(api frontend.API) error { +func (circuit *e12ExptGS) Define(api frontend.API) error { e := NewExt12(api) - compressed := e.CompressTorus(&circuit.A) - compressed = e.FrobeniusSquareTorus(compressed) - expected := e.DecompressTorus(compressed) + expected := e.ExptGS(&circuit.A) e.AssertIsEqual(expected, &circuit.C) + return nil } -func TestTorusFrobeniusSquare(t *testing.T) { +func TestFp12ExptGS(t *testing.T) { assert := test.NewAssert(t) // witness values - var a, c, tmp bls12381.E12 + var a, c bls12381.E12 _, _ = a.SetRandom() // put a in the cyclotomic subgroup + var tmp bls12381.E12 tmp.Conjugate(&a) a.Inverse(&a) tmp.Mul(&tmp, &a) a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - // uncompressed frobeniusSquare - c.FrobeniusSquare(&a) - - witness := torusFrobeniusSquare{ + c.Expt(&a) + witness := e12ExptGS{ A: FromE12(&a), C: FromE12(&c), } - err := test.IsSolved(&torusFrobeniusSquare{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&e12ExptGS{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) } -type torusSquare struct { - A E12 - C E12 `gnark:",public"` +type e12MulBy02368 struct { + A E12 `gnark:",public"` + W E12 + B, C E2 } -func (circuit *torusSquare) Define(api frontend.API) error { +func (circuit *e12MulBy02368) Define(api frontend.API) error { e := NewExt12(api) - compressed := e.CompressTorus(&circuit.A) - compressed = e.SquareTorus(compressed) - expected := e.DecompressTorus(compressed) - e.AssertIsEqual(expected, &circuit.C) + res := e.MulBy02368(&circuit.A, &circuit.B, &circuit.C) + e.AssertIsEqual(res, &circuit.W) return nil } -func TestTorusSquare(t *testing.T) { +func TestFp12MulBy02368(t *testing.T) { assert := test.NewAssert(t) // witness values - var a, c, tmp bls12381.E12 + var a, w bls12381.E12 _, _ = a.SetRandom() + var one, b, c bls12381.E2 + one.SetOne() + _, _ = b.SetRandom() + _, _ = c.SetRandom() + w.Set(&a) + w.MulBy014(&b, &c, &one) - // put a in the cyclotomic subgroup - tmp.Conjugate(&a) - a.Inverse(&a) - tmp.Mul(&tmp, &a) - a.FrobeniusSquare(&tmp).Mul(&a, &tmp) - - // uncompressed square - c.Square(&a) - - witness := torusSquare{ + witness := e12MulBy02368{ A: FromE12(&a), - C: FromE12(&c), + B: FromE2(&b), + C: FromE2(&c), + W: FromE12(&w), } - err := test.IsSolved(&torusSquare{}, &witness, ecc.BN254.ScalarField()) + err := test.IsSolved(&e12MulBy02368{}, &witness, ecc.BN254.ScalarField()) assert.NoError(err) + } diff --git a/std/algebra/emulated/fields_bls12381/e2.go b/std/algebra/emulated/fields_bls12381/e2.go index 30b5ebda4..ba378f679 100644 --- a/std/algebra/emulated/fields_bls12381/e2.go +++ b/std/algebra/emulated/fields_bls12381/e2.go @@ -184,16 +184,10 @@ func (e Ext2) MulByNonResidue2Power5(x *E2) *E2 { } func (e Ext2) Mul(x, y *E2) *E2 { - - v0 := e.fp.Mul(&x.A0, &y.A0) - v1 := e.fp.Mul(&x.A1, &y.A1) - - b0 := e.fp.Sub(v0, v1) - b1 := e.fp.Add(&x.A0, &x.A1) - tmp := e.fp.Add(&y.A0, &y.A1) - b1 = e.fp.Mul(b1, tmp) - tmp = e.fp.Add(v0, v1) - b1 = e.fp.Sub(b1, tmp) + // b0 = x0*y0 - x1*y1 + b0 := e.fp.Eval([][]*baseEl{{&x.A0, &y.A0}, {e.fp.NewElement(-1), &x.A1, &y.A1}}, []int{1, 1}) + // b1 = x0*y1 + x1*y0 + b1 := e.fp.Eval([][]*baseEl{{&x.A0, &y.A1}, {&x.A1, &y.A0}}, []int{1, 1}) return &E2{ A0: *b0, @@ -262,17 +256,27 @@ func (e Ext2) NonResidue() *E2 { } func (e Ext2) Square(x *E2) *E2 { - a := e.fp.Add(&x.A0, &x.A1) - b := e.fp.Sub(&x.A0, &x.A1) - a = e.fp.Mul(a, b) - b = e.fp.Mul(&x.A0, &x.A1) - b = e.fp.MulConst(b, big.NewInt(2)) + // a = (x0+x1)(x0-x1) = x0^2 - x1^2 + a := e.fp.Eval([][]*baseEl{{&x.A0, &x.A0}, {e.fp.NewElement(-1), &x.A1, &x.A1}}, []int{1, 1}) + // b = 2*x0*x1 + b := e.fp.Eval([][]*baseEl{{&x.A0, &x.A1}}, []int{2}) return &E2{ A0: *a, A1: *b, } } +func (e Ext2) Cube(x *E2) *E2 { + mone := e.fp.NewElement(-1) + // a = x0^3 - 3*x0*x1^2 + a := e.fp.Eval([][]*baseEl{{&x.A0, &x.A0, &x.A0}, {mone, &x.A0, &x.A1, &x.A1}}, []int{1, 3}) + // b = 3*x1*x0^2 - x1^3 + b := e.fp.Eval([][]*baseEl{{&x.A1, &x.A0, &x.A0}, {mone, &x.A1, &x.A1, &x.A1}}, []int{3, 1}) + return &E2{ + A0: *a, + A1: *b, + } +} func (e Ext2) Double(x *E2) *E2 { two := big.NewInt(2) z0 := e.fp.MulConst(&x.A0, two) diff --git a/std/algebra/emulated/fields_bls12381/e6.go b/std/algebra/emulated/fields_bls12381/e6.go deleted file mode 100644 index 22a359632..000000000 --- a/std/algebra/emulated/fields_bls12381/e6.go +++ /dev/null @@ -1,465 +0,0 @@ -package fields_bls12381 - -import ( - "math/big" - - bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/internal/frontendtype" -) - -type E6 struct { - B0, B1, B2 E2 -} - -type Ext6 struct { - *Ext2 -} - -func NewExt6(api frontend.API) *Ext6 { - return &Ext6{Ext2: NewExt2(api)} -} - -func (e Ext6) One() *E6 { - z0 := e.Ext2.One() - z1 := e.Ext2.Zero() - z2 := e.Ext2.Zero() - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) Zero() *E6 { - z0 := e.Ext2.Zero() - z1 := e.Ext2.Zero() - z2 := e.Ext2.Zero() - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) IsZero(z *E6) frontend.Variable { - b0 := e.Ext2.IsZero(&z.B0) - b1 := e.Ext2.IsZero(&z.B1) - b2 := e.Ext2.IsZero(&z.B2) - return e.api.And(e.api.And(b0, b1), b2) -} - -func (e Ext6) Add(x, y *E6) *E6 { - z0 := e.Ext2.Add(&x.B0, &y.B0) - z1 := e.Ext2.Add(&x.B1, &y.B1) - z2 := e.Ext2.Add(&x.B2, &y.B2) - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) Neg(x *E6) *E6 { - z0 := e.Ext2.Neg(&x.B0) - z1 := e.Ext2.Neg(&x.B1) - z2 := e.Ext2.Neg(&x.B2) - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) Sub(x, y *E6) *E6 { - z0 := e.Ext2.Sub(&x.B0, &y.B0) - z1 := e.Ext2.Sub(&x.B1, &y.B1) - z2 := e.Ext2.Sub(&x.B2, &y.B2) - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -// Mul multiplies two E6 elmts -func (e Ext6) Mul(x, y *E6) *E6 { - if ft, ok := e.api.(frontendtype.FrontendTyper); ok { - switch ft.FrontendType() { - case frontendtype.R1CS: - return e.mulToom3OverKaratsuba(x, y) - case frontendtype.SCS: - return e.mulKaratsubaOverKaratsuba(x, y) - } - } - return e.mulKaratsubaOverKaratsuba(x, y) -} - -func (e Ext6) mulToom3OverKaratsuba(x, y *E6) *E6 { - // Toom-Cook-3x over Karatsuba: - // We start by computing five interpolation points – these are evaluations of - // the product x(u)y(u) with u ∈ {0, ±1, 2, ∞}: - // - // v0 = x(0)y(0) = x.A0 * y.A0 - // v1 = x(1)y(1) = (x.A0 + x.A1 + x.A2)(y.A0 + y.A1 + y.A2) - // v2 = x(−1)y(−1) = (x.A0 − x.A1 + x.A2)(y.A0 − y.A1 + y.A2) - // v3 = x(2)y(2) = (x.A0 + 2x.A1 + 4x.A2)(y.A0 + 2y.A1 + 4y.A2) - // v4 = x(∞)y(∞) = x.A2 * y.A2 - - v0 := e.Ext2.Mul(&x.B0, &y.B0) - - t1 := e.Ext2.Add(&x.B0, &x.B2) - t2 := e.Ext2.Add(&y.B0, &y.B2) - t3 := e.Ext2.Add(t2, &y.B1) - v1 := e.Ext2.Add(t1, &x.B1) - v1 = e.Ext2.Mul(v1, t3) - - t3 = e.Ext2.Sub(t2, &y.B1) - v2 := e.Ext2.Sub(t1, &x.B1) - v2 = e.Ext2.Mul(v2, t3) - - t1 = e.Ext2.MulByConstElement(&x.B1, big.NewInt(2)) - t2 = e.Ext2.MulByConstElement(&x.B2, big.NewInt(4)) - v3 := e.Ext2.Add(t1, t2) - v3 = e.Ext2.Add(v3, &x.B0) - t1 = e.Ext2.MulByConstElement(&y.B1, big.NewInt(2)) - t2 = e.Ext2.MulByConstElement(&y.B2, big.NewInt(4)) - t3 = e.Ext2.Add(t1, t2) - t3 = e.Ext2.Add(t3, &y.B0) - v3 = e.Ext2.Mul(v3, t3) - - v4 := e.Ext2.Mul(&x.B2, &y.B2) - - // Then the interpolation is performed as: - // - // a0 = v0 + β((1/2)v0 − (1/2)v1 − (1/6)v2 + (1/6)v3 − 2v4) - // a1 = −(1/2)v0 + v1 − (1/3)v2 − (1/6)v3 + 2v4 + βv4 - // a2 = −v0 + (1/2)v1 + (1/2)v2 − v4 - // - // where β is the cubic non-residue. - // - // In-circuit, we compute 6*x*y as - // c0 = 6v0 + β(3v0 − 3v1 − v2 + v3 − 12v4) - // a1 = -(3v0 + 2v2 + v3) + 6(v1 + 2v4 + βv4) - // a2 = 3(v1 + v2 - 2(v0 + v4)) - // - // and then divide a0, a1 and a2 by 6 using a hint. - - a0 := e.Ext2.MulByConstElement(v0, big.NewInt(6)) - t1 = e.Ext2.Sub(v0, v1) - t1 = e.Ext2.MulByConstElement(t1, big.NewInt(3)) - t1 = e.Ext2.Sub(t1, v2) - t1 = e.Ext2.Add(t1, v3) - t2 = e.Ext2.MulByConstElement(v4, big.NewInt(12)) - t1 = e.Ext2.Sub(t1, t2) - t1 = e.Ext2.MulByNonResidue(t1) - a0 = e.Ext2.Add(a0, t1) - - a1 := e.Ext2.MulByConstElement(v0, big.NewInt(3)) - t1 = e.Ext2.MulByConstElement(v2, big.NewInt(2)) - a1 = e.Ext2.Add(a1, t1) - a1 = e.Ext2.Add(a1, v3) - t1 = e.Ext2.MulByConstElement(v4, big.NewInt(2)) - t1 = e.Ext2.Add(t1, v1) - t2 = e.Ext2.MulByNonResidue(v4) - t1 = e.Ext2.Add(t1, t2) - t1 = e.Ext2.MulByConstElement(t1, big.NewInt(6)) - a1 = e.Ext2.Sub(t1, a1) - - a2 := e.Ext2.Add(v1, v2) - a2 = e.Ext2.MulByConstElement(a2, big.NewInt(3)) - t1 = e.Ext2.Add(v0, v4) - t1 = e.Ext2.MulByConstElement(t1, big.NewInt(6)) - a2 = e.Ext2.Sub(a2, t1) - - res := e.divE6By6([6]*baseEl{&a0.A0, &a0.A1, &a1.A0, &a1.A1, &a2.A0, &a2.A1}) - return &E6{ - B0: E2{ - A0: *res[0], - A1: *res[1], - }, - B1: E2{ - A0: *res[2], - A1: *res[3], - }, - B2: E2{ - A0: *res[4], - A1: *res[5], - }, - } -} - -func (e Ext6) mulKaratsubaOverKaratsuba(x, y *E6) *E6 { - // Karatsuba over Karatsuba: - // Algorithm 13 from https://eprint.iacr.org/2010/354.pdf - t0 := e.Ext2.Mul(&x.B0, &y.B0) - t1 := e.Ext2.Mul(&x.B1, &y.B1) - t2 := e.Ext2.Mul(&x.B2, &y.B2) - c0 := e.Ext2.Add(&x.B1, &x.B2) - tmp := e.Ext2.Add(&y.B1, &y.B2) - c0 = e.Ext2.Mul(c0, tmp) - tmp = e.Ext2.Add(t2, t1) - c0 = e.Ext2.Sub(c0, tmp) - c0 = e.Ext2.MulByNonResidue(c0) - c0 = e.Ext2.Add(c0, t0) - c1 := e.Ext2.Add(&x.B0, &x.B1) - tmp = e.Ext2.Add(&y.B0, &y.B1) - c1 = e.Ext2.Mul(c1, tmp) - tmp = e.Ext2.Add(t0, t1) - c1 = e.Ext2.Sub(c1, tmp) - tmp = e.Ext2.MulByNonResidue(t2) - c1 = e.Ext2.Add(c1, tmp) - tmp = e.Ext2.Add(&x.B0, &x.B2) - c2 := e.Ext2.Add(&y.B0, &y.B2) - c2 = e.Ext2.Mul(c2, tmp) - tmp = e.Ext2.Add(t0, t2) - c2 = e.Ext2.Sub(c2, tmp) - c2 = e.Ext2.Add(c2, t1) - return &E6{ - B0: *c0, - B1: *c1, - B2: *c2, - } -} - -func (e Ext6) Double(x *E6) *E6 { - z0 := e.Ext2.Double(&x.B0) - z1 := e.Ext2.Double(&x.B1) - z2 := e.Ext2.Double(&x.B2) - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) Square(x *E6) *E6 { - c4 := e.Ext2.Mul(&x.B0, &x.B1) - c4 = e.Ext2.Double(c4) - c5 := e.Ext2.Square(&x.B2) - c1 := e.Ext2.MulByNonResidue(c5) - c1 = e.Ext2.Add(c1, c4) - c2 := e.Ext2.Sub(c4, c5) - c3 := e.Ext2.Square(&x.B0) - c4 = e.Ext2.Sub(&x.B0, &x.B1) - c4 = e.Ext2.Add(c4, &x.B2) - c5 = e.Ext2.Mul(&x.B1, &x.B2) - c5 = e.Ext2.Double(c5) - c4 = e.Ext2.Square(c4) - c0 := e.Ext2.MulByNonResidue(c5) - c0 = e.Ext2.Add(c0, c3) - z2 := e.Ext2.Add(c2, c4) - z2 = e.Ext2.Add(z2, c5) - z2 = e.Ext2.Sub(z2, c3) - z0 := c0 - z1 := c1 - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) MulByE2(x *E6, y *E2) *E6 { - z0 := e.Ext2.Mul(&x.B0, y) - z1 := e.Ext2.Mul(&x.B1, y) - z2 := e.Ext2.Mul(&x.B2, y) - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -// MulBy12 multiplication by sparse element (0,b1,b2) -func (e Ext6) MulBy12(x *E6, b1, b2 *E2) *E6 { - t1 := e.Ext2.Mul(&x.B1, b1) - t2 := e.Ext2.Mul(&x.B2, b2) - c0 := e.Ext2.Add(&x.B1, &x.B2) - tmp := e.Ext2.Add(b1, b2) - c0 = e.Ext2.Mul(c0, tmp) - tmp = e.Ext2.Add(t1, t2) - c0 = e.Ext2.Sub(c0, tmp) - c0 = e.Ext2.MulByNonResidue(c0) - c1 := e.Ext2.Add(&x.B0, &x.B1) - c1 = e.Ext2.Mul(c1, b1) - c1 = e.Ext2.Sub(c1, t1) - tmp = e.Ext2.MulByNonResidue(t2) - c1 = e.Ext2.Add(c1, tmp) - tmp = e.Ext2.Add(&x.B0, &x.B2) - c2 := e.Ext2.Mul(b2, tmp) - c2 = e.Ext2.Sub(c2, t2) - c2 = e.Ext2.Add(c2, t1) - return &E6{ - B0: *c0, - B1: *c1, - B2: *c2, - } -} - -// MulBy0 multiplies z by an E6 sparse element of the form -// -// E6{ -// B0: c0, -// B1: 0, -// B2: 0, -// } -func (e Ext6) MulBy0(z *E6, c0 *E2) *E6 { - a := e.Ext2.Mul(&z.B0, c0) - tmp := e.Ext2.Add(&z.B0, &z.B2) - t2 := e.Ext2.Mul(c0, tmp) - t2 = e.Ext2.Sub(t2, a) - tmp = e.Ext2.Add(&z.B0, &z.B1) - t1 := e.Ext2.Mul(c0, tmp) - t1 = e.Ext2.Sub(t1, a) - return &E6{ - B0: *a, - B1: *t1, - B2: *t2, - } -} - -// MulBy01 multiplies z by an E6 sparse element of the form -// -// E6{ -// B0: c0, -// B1: c1, -// B2: 0, -// } -func (e Ext6) MulBy01(z *E6, c0, c1 *E2) *E6 { - a := e.Ext2.Mul(&z.B0, c0) - b := e.Ext2.Mul(&z.B1, c1) - tmp := e.Ext2.Add(&z.B1, &z.B2) - t0 := e.Ext2.Mul(c1, tmp) - t0 = e.Ext2.Sub(t0, b) - t0 = e.Ext2.MulByNonResidue(t0) - t0 = e.Ext2.Add(t0, a) - // for t2, schoolbook is faster than karatsuba - // c2 = a0b2 + a1b1 + a2b0, - // c2 = a2b0 + b ∵ b2 = 0, b = a1b1 - t2 := e.Ext2.Mul(&z.B2, c0) - t2 = e.Ext2.Add(t2, b) - t1 := e.Ext2.Add(c0, c1) - tmp = e.Ext2.Add(&z.B0, &z.B1) - t1 = e.Ext2.Mul(t1, tmp) - tmp = e.Ext2.Add(a, b) - t1 = e.Ext2.Sub(t1, tmp) - return &E6{ - B0: *t0, - B1: *t1, - B2: *t2, - } -} - -func (e Ext6) MulByNonResidue(x *E6) *E6 { - z2, z1, z0 := &x.B1, &x.B0, &x.B2 - z0 = e.Ext2.MulByNonResidue(z0) - return &E6{ - B0: *z0, - B1: *z1, - B2: *z2, - } -} - -func (e Ext6) AssertIsEqual(x, y *E6) { - e.Ext2.AssertIsEqual(&x.B0, &y.B0) - e.Ext2.AssertIsEqual(&x.B1, &y.B1) - e.Ext2.AssertIsEqual(&x.B2, &y.B2) -} - -func FromE6(y *bls12381.E6) E6 { - return E6{ - B0: FromE2(&y.B0), - B1: FromE2(&y.B1), - B2: FromE2(&y.B2), - } - -} - -func (e Ext6) Inverse(x *E6) *E6 { - res, err := e.fp.NewHint(inverseE6Hint, 6, &x.B0.A0, &x.B0.A1, &x.B1.A0, &x.B1.A1, &x.B2.A0, &x.B2.A1) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - - inv := E6{ - B0: E2{A0: *res[0], A1: *res[1]}, - B1: E2{A0: *res[2], A1: *res[3]}, - B2: E2{A0: *res[4], A1: *res[5]}, - } - - one := e.One() - - // 1 == inv * x - _one := e.Mul(&inv, x) - e.AssertIsEqual(one, _one) - - return &inv - -} - -func (e Ext6) DivUnchecked(x, y *E6) *E6 { - res, err := e.fp.NewHint(divE6Hint, 6, &x.B0.A0, &x.B0.A1, &x.B1.A0, &x.B1.A1, &x.B2.A0, &x.B2.A1, &y.B0.A0, &y.B0.A1, &y.B1.A0, &y.B1.A1, &y.B2.A0, &y.B2.A1) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - - div := E6{ - B0: E2{A0: *res[0], A1: *res[1]}, - B1: E2{A0: *res[2], A1: *res[3]}, - B2: E2{A0: *res[4], A1: *res[5]}, - } - - // x == div * y - _x := e.Mul(&div, y) - e.AssertIsEqual(x, _x) - - return &div -} - -func (e Ext6) divE6By6(x [6]*baseEl) [6]*baseEl { - res, err := e.fp.NewHint(divE6By6Hint, 6, x[0], x[1], x[2], x[3], x[4], x[5]) - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - - y0 := *res[0] - y1 := *res[1] - y2 := *res[2] - y3 := *res[3] - y4 := *res[4] - y5 := *res[5] - - // xi == 6 * yi - x0 := e.fp.MulConst(&y0, big.NewInt(6)) - x1 := e.fp.MulConst(&y1, big.NewInt(6)) - x2 := e.fp.MulConst(&y2, big.NewInt(6)) - x3 := e.fp.MulConst(&y3, big.NewInt(6)) - x4 := e.fp.MulConst(&y4, big.NewInt(6)) - x5 := e.fp.MulConst(&y5, big.NewInt(6)) - e.fp.AssertIsEqual(x[0], x0) - e.fp.AssertIsEqual(x[1], x1) - e.fp.AssertIsEqual(x[2], x2) - e.fp.AssertIsEqual(x[3], x3) - e.fp.AssertIsEqual(x[4], x4) - e.fp.AssertIsEqual(x[5], x5) - - return [6]*baseEl{&y0, &y1, &y2, &y3, &y4, &y5} -} - -func (e Ext6) Select(selector frontend.Variable, z1, z0 *E6) *E6 { - b0 := e.Ext2.Select(selector, &z1.B0, &z0.B0) - b1 := e.Ext2.Select(selector, &z1.B1, &z0.B1) - b2 := e.Ext2.Select(selector, &z1.B2, &z0.B2) - return &E6{B0: *b0, B1: *b1, B2: *b2} -} - -func (e Ext6) Lookup2(s1, s2 frontend.Variable, a, b, c, d *E6) *E6 { - b0 := e.Ext2.Lookup2(s1, s2, &a.B0, &b.B0, &c.B0, &d.B0) - b1 := e.Ext2.Lookup2(s1, s2, &a.B1, &b.B1, &c.B1, &d.B1) - b2 := e.Ext2.Lookup2(s1, s2, &a.B2, &b.B2, &c.B2, &d.B2) - return &E6{B0: *b0, B1: *b1, B2: *b2} -} diff --git a/std/algebra/emulated/fields_bls12381/e6_test.go b/std/algebra/emulated/fields_bls12381/e6_test.go deleted file mode 100644 index 9ed08d3b5..000000000 --- a/std/algebra/emulated/fields_bls12381/e6_test.go +++ /dev/null @@ -1,360 +0,0 @@ -package fields_bls12381 - -import ( - "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" -) - -type e6Add struct { - A, B, C E6 -} - -func (circuit *e6Add) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.Add(&circuit.A, &circuit.B) - e.AssertIsEqual(expected, &circuit.C) - return nil -} - -func TestAddFp6(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, b, c bls12381.E6 - _, _ = a.SetRandom() - _, _ = b.SetRandom() - c.Add(&a, &b) - - witness := e6Add{ - A: FromE6(&a), - B: FromE6(&b), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Add{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6Sub struct { - A, B, C E6 -} - -func (circuit *e6Sub) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.Sub(&circuit.A, &circuit.B) - e.AssertIsEqual(expected, &circuit.C) - return nil -} - -func TestSubFp6(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, b, c bls12381.E6 - _, _ = a.SetRandom() - _, _ = b.SetRandom() - c.Sub(&a, &b) - - witness := e6Sub{ - A: FromE6(&a), - B: FromE6(&b), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Sub{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6Mul struct { - A, B, C E6 -} - -func (circuit *e6Mul) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.Mul(&circuit.A, &circuit.B) - e.AssertIsEqual(expected, &circuit.C) - return nil -} - -func TestMulFp6(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, b, c bls12381.E6 - _, _ = a.SetRandom() - _, _ = b.SetRandom() - c.Mul(&a, &b) - - witness := e6Mul{ - A: FromE6(&a), - B: FromE6(&b), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Mul{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6MulVariant struct { - A, B, C E6 -} - -func (circuit *e6MulVariant) Define(api frontend.API) error { - e := NewExt6(api) - expected1 := e.mulKaratsubaOverKaratsuba(&circuit.A, &circuit.B) - expected2 := e.mulToom3OverKaratsuba(&circuit.A, &circuit.B) - e.AssertIsEqual(expected1, &circuit.C) - e.AssertIsEqual(expected2, &circuit.C) - return nil -} - -func TestMulFp6Variants(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, b, c bls12381.E6 - _, _ = a.SetRandom() - _, _ = b.SetRandom() - c.Mul(&a, &b) - - witness := e6Mul{ - A: FromE6(&a), - B: FromE6(&b), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Mul{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6Square struct { - A, C E6 -} - -func (circuit *e6Square) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.Square(&circuit.A) - e.AssertIsEqual(expected, &circuit.C) - return nil -} - -func TestSquareFp6(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bls12381.E6 - _, _ = a.SetRandom() - c.Square(&a) - - witness := e6Square{ - A: FromE6(&a), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Square{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6Div struct { - A, B, C E6 -} - -func (circuit *e6Div) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.DivUnchecked(&circuit.A, &circuit.B) - e.AssertIsEqual(expected, &circuit.C) - return nil -} - -func TestDivFp6(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, b, c bls12381.E6 - _, _ = a.SetRandom() - _, _ = b.SetRandom() - c.Div(&a, &b) - - witness := e6Div{ - A: FromE6(&a), - B: FromE6(&b), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Div{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6MulByNonResidue struct { - A E6 - C E6 `gnark:",public"` -} - -func (circuit *e6MulByNonResidue) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.MulByNonResidue(&circuit.A) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestMulFp6ByNonResidue(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bls12381.E6 - _, _ = a.SetRandom() - c.MulByNonResidue(&a) - - witness := e6MulByNonResidue{ - A: FromE6(&a), - C: FromE6(&c), - } - - err := test.IsSolved(&e6MulByNonResidue{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6MulByE2 struct { - A E6 - B E2 - C E6 `gnark:",public"` -} - -func (circuit *e6MulByE2) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.MulByE2(&circuit.A, &circuit.B) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestMulFp6ByE2(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bls12381.E6 - var b bls12381.E2 - _, _ = a.SetRandom() - _, _ = b.SetRandom() - c.MulByE2(&a, &b) - - witness := e6MulByE2{ - A: FromE6(&a), - B: FromE2(&b), - C: FromE6(&c), - } - - err := test.IsSolved(&e6MulByE2{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6MulBy01 struct { - A E6 - C0, C1 E2 - C E6 `gnark:",public"` -} - -func (circuit *e6MulBy01) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.MulBy01(&circuit.A, &circuit.C0, &circuit.C1) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestMulFp6By01(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bls12381.E6 - var C0, C1 bls12381.E2 - _, _ = a.SetRandom() - _, _ = C0.SetRandom() - _, _ = C1.SetRandom() - c.Set(&a) - c.MulBy01(&C0, &C1) - - witness := e6MulBy01{ - A: FromE6(&a), - C0: FromE2(&C0), - C1: FromE2(&C1), - C: FromE6(&c), - } - - err := test.IsSolved(&e6MulBy01{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) - -} - -type e6Neg struct { - A E6 - C E6 `gnark:",public"` -} - -func (circuit *e6Neg) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.Neg(&circuit.A) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestNegFp6(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bls12381.E6 - _, _ = a.SetRandom() - c.Neg(&a) - - witness := e6Neg{ - A: FromE6(&a), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Neg{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) -} - -type e6Inverse struct { - A E6 - C E6 `gnark:",public"` -} - -func (circuit *e6Inverse) Define(api frontend.API) error { - e := NewExt6(api) - expected := e.Inverse(&circuit.A) - e.AssertIsEqual(expected, &circuit.C) - - return nil -} - -func TestInverseFp6(t *testing.T) { - - assert := test.NewAssert(t) - // witness values - var a, c bls12381.E6 - _, _ = a.SetRandom() - c.Inverse(&a) - - witness := e6Inverse{ - A: FromE6(&a), - C: FromE6(&c), - } - - err := test.IsSolved(&e6Inverse{}, &witness, ecc.BN254.ScalarField()) - assert.NoError(err) -} diff --git a/std/algebra/emulated/fields_bls12381/hints.go b/std/algebra/emulated/fields_bls12381/hints.go index 77863d3ff..24473129f 100644 --- a/std/algebra/emulated/fields_bls12381/hints.go +++ b/std/algebra/emulated/fields_bls12381/hints.go @@ -19,15 +19,9 @@ func GetHints() []solver.Hint { // E2 divE2Hint, inverseE2Hint, - // E6 - divE6Hint, - inverseE6Hint, - squareTorusHint, - divE6By6Hint, // E12 divE12Hint, inverseE12Hint, - finalExpHint, } } @@ -67,153 +61,59 @@ func divE2Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error }) } -// E6 hints -func inverseE6Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { - return emulated.UnwrapHint(nativeInputs, nativeOutputs, - func(mod *big.Int, inputs, outputs []*big.Int) error { - var a, c bls12381.E6 - - a.B0.A0.SetBigInt(inputs[0]) - a.B0.A1.SetBigInt(inputs[1]) - a.B1.A0.SetBigInt(inputs[2]) - a.B1.A1.SetBigInt(inputs[3]) - a.B2.A0.SetBigInt(inputs[4]) - a.B2.A1.SetBigInt(inputs[5]) - - c.Inverse(&a) - - c.B0.A0.BigInt(outputs[0]) - c.B0.A1.BigInt(outputs[1]) - c.B1.A0.BigInt(outputs[2]) - c.B1.A1.BigInt(outputs[3]) - c.B2.A0.BigInt(outputs[4]) - c.B2.A1.BigInt(outputs[5]) - - return nil - }) -} - -func divE6Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { - return emulated.UnwrapHint(nativeInputs, nativeOutputs, - func(mod *big.Int, inputs, outputs []*big.Int) error { - var a, b, c bls12381.E6 - - a.B0.A0.SetBigInt(inputs[0]) - a.B0.A1.SetBigInt(inputs[1]) - a.B1.A0.SetBigInt(inputs[2]) - a.B1.A1.SetBigInt(inputs[3]) - a.B2.A0.SetBigInt(inputs[4]) - a.B2.A1.SetBigInt(inputs[5]) - - b.B0.A0.SetBigInt(inputs[6]) - b.B0.A1.SetBigInt(inputs[7]) - b.B1.A0.SetBigInt(inputs[8]) - b.B1.A1.SetBigInt(inputs[9]) - b.B2.A0.SetBigInt(inputs[10]) - b.B2.A1.SetBigInt(inputs[11]) - - c.Inverse(&b).Mul(&c, &a) - - c.B0.A0.BigInt(outputs[0]) - c.B0.A1.BigInt(outputs[1]) - c.B1.A0.BigInt(outputs[2]) - c.B1.A1.BigInt(outputs[3]) - c.B2.A0.BigInt(outputs[4]) - c.B2.A1.BigInt(outputs[5]) - - return nil - }) -} - -func squareTorusHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { - return emulated.UnwrapHint(nativeInputs, nativeOutputs, - func(mod *big.Int, inputs, outputs []*big.Int) error { - var a, c bls12381.E6 - - a.B0.A0.SetBigInt(inputs[0]) - a.B0.A1.SetBigInt(inputs[1]) - a.B1.A0.SetBigInt(inputs[2]) - a.B1.A1.SetBigInt(inputs[3]) - a.B2.A0.SetBigInt(inputs[4]) - a.B2.A1.SetBigInt(inputs[5]) - - _c := a.DecompressTorus() - _c.CyclotomicSquare(&_c) - c, _ = _c.CompressTorus() - - c.B0.A0.BigInt(outputs[0]) - c.B0.A1.BigInt(outputs[1]) - c.B1.A0.BigInt(outputs[2]) - c.B1.A1.BigInt(outputs[3]) - c.B2.A0.BigInt(outputs[4]) - c.B2.A1.BigInt(outputs[5]) - - return nil - }) -} - -func divE6By6Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { - return emulated.UnwrapHint(nativeInputs, nativeOutputs, - func(mod *big.Int, inputs, outputs []*big.Int) error { - var a, c bls12381.E6 - - a.B0.A0.SetBigInt(inputs[0]) - a.B0.A1.SetBigInt(inputs[1]) - a.B1.A0.SetBigInt(inputs[2]) - a.B1.A1.SetBigInt(inputs[3]) - a.B2.A0.SetBigInt(inputs[4]) - a.B2.A1.SetBigInt(inputs[5]) - - var sixInv fp.Element - sixInv.SetString("6") - sixInv.Inverse(&sixInv) - c.B0.MulByElement(&a.B0, &sixInv) - c.B1.MulByElement(&a.B1, &sixInv) - c.B2.MulByElement(&a.B2, &sixInv) - - c.B0.A0.BigInt(outputs[0]) - c.B0.A1.BigInt(outputs[1]) - c.B1.A0.BigInt(outputs[2]) - c.B1.A1.BigInt(outputs[3]) - c.B2.A0.BigInt(outputs[4]) - c.B2.A1.BigInt(outputs[5]) - - return nil - }) -} - // E12 hints func inverseE12Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { return emulated.UnwrapHint(nativeInputs, nativeOutputs, func(mod *big.Int, inputs, outputs []*big.Int) error { var a, c bls12381.E12 - a.C0.B0.A0.SetBigInt(inputs[0]) - a.C0.B0.A1.SetBigInt(inputs[1]) - a.C0.B1.A0.SetBigInt(inputs[2]) - a.C0.B1.A1.SetBigInt(inputs[3]) - a.C0.B2.A0.SetBigInt(inputs[4]) - a.C0.B2.A1.SetBigInt(inputs[5]) - a.C1.B0.A0.SetBigInt(inputs[6]) - a.C1.B0.A1.SetBigInt(inputs[7]) - a.C1.B1.A0.SetBigInt(inputs[8]) - a.C1.B1.A1.SetBigInt(inputs[9]) - a.C1.B2.A0.SetBigInt(inputs[10]) - a.C1.B2.A1.SetBigInt(inputs[11]) + var d [12]big.Int + d[0].Add(inputs[0], inputs[6]) + d[1].Set(inputs[6]) + d[2].Add(inputs[2], inputs[8]) + d[3].Set(inputs[8]) + d[4].Add(inputs[4], inputs[10]) + d[5].Set(inputs[10]) + d[6].Add(inputs[1], inputs[7]) + d[7].Set(inputs[7]) + d[8].Add(inputs[3], inputs[9]) + d[9].Set(inputs[9]) + d[10].Add(inputs[5], inputs[11]) + d[11].Set(inputs[11]) + a.C0.B0.A0.SetBigInt(&d[0]) + a.C0.B0.A1.SetBigInt(&d[1]) + a.C0.B1.A0.SetBigInt(&d[2]) + a.C0.B1.A1.SetBigInt(&d[3]) + a.C0.B2.A0.SetBigInt(&d[4]) + a.C0.B2.A1.SetBigInt(&d[5]) + a.C1.B0.A0.SetBigInt(&d[6]) + a.C1.B0.A1.SetBigInt(&d[7]) + a.C1.B1.A0.SetBigInt(&d[8]) + a.C1.B1.A1.SetBigInt(&d[9]) + a.C1.B2.A0.SetBigInt(&d[10]) + a.C1.B2.A1.SetBigInt(&d[11]) c.Inverse(&a) - c.C0.B0.A0.BigInt(outputs[0]) - c.C0.B0.A1.BigInt(outputs[1]) - c.C0.B1.A0.BigInt(outputs[2]) - c.C0.B1.A1.BigInt(outputs[3]) - c.C0.B2.A0.BigInt(outputs[4]) - c.C0.B2.A1.BigInt(outputs[5]) - c.C1.B0.A0.BigInt(outputs[6]) + var c0, c1, c2, c3, c4, c5 fp.Element + c0.Sub(&c.C0.B0.A0, &c.C0.B0.A1) + c1.Sub(&c.C1.B0.A0, &c.C1.B0.A1) + c2.Sub(&c.C0.B1.A0, &c.C0.B1.A1) + c3.Sub(&c.C1.B1.A0, &c.C1.B1.A1) + c4.Sub(&c.C0.B2.A0, &c.C0.B2.A1) + c5.Sub(&c.C1.B2.A0, &c.C1.B2.A1) + + c0.BigInt(outputs[0]) + c1.BigInt(outputs[1]) + c2.BigInt(outputs[2]) + c3.BigInt(outputs[3]) + c4.BigInt(outputs[4]) + c5.BigInt(outputs[5]) + c.C0.B0.A1.BigInt(outputs[6]) c.C1.B0.A1.BigInt(outputs[7]) - c.C1.B1.A0.BigInt(outputs[8]) + c.C0.B1.A1.BigInt(outputs[8]) c.C1.B1.A1.BigInt(outputs[9]) - c.C1.B2.A0.BigInt(outputs[10]) + c.C0.B2.A1.BigInt(outputs[10]) c.C1.B2.A1.BigInt(outputs[11]) return nil @@ -225,157 +125,80 @@ func divE12Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) erro func(mod *big.Int, inputs, outputs []*big.Int) error { var a, b, c bls12381.E12 - a.C0.B0.A0.SetBigInt(inputs[0]) - a.C0.B0.A1.SetBigInt(inputs[1]) - a.C0.B1.A0.SetBigInt(inputs[2]) - a.C0.B1.A1.SetBigInt(inputs[3]) - a.C0.B2.A0.SetBigInt(inputs[4]) - a.C0.B2.A1.SetBigInt(inputs[5]) - a.C1.B0.A0.SetBigInt(inputs[6]) - a.C1.B0.A1.SetBigInt(inputs[7]) - a.C1.B1.A0.SetBigInt(inputs[8]) - a.C1.B1.A1.SetBigInt(inputs[9]) - a.C1.B2.A0.SetBigInt(inputs[10]) - a.C1.B2.A1.SetBigInt(inputs[11]) - - b.C0.B0.A0.SetBigInt(inputs[12]) - b.C0.B0.A1.SetBigInt(inputs[13]) - b.C0.B1.A0.SetBigInt(inputs[14]) - b.C0.B1.A1.SetBigInt(inputs[15]) - b.C0.B2.A0.SetBigInt(inputs[16]) - b.C0.B2.A1.SetBigInt(inputs[17]) - b.C1.B0.A0.SetBigInt(inputs[18]) - b.C1.B0.A1.SetBigInt(inputs[19]) - b.C1.B1.A0.SetBigInt(inputs[20]) - b.C1.B1.A1.SetBigInt(inputs[21]) - b.C1.B2.A0.SetBigInt(inputs[22]) - b.C1.B2.A1.SetBigInt(inputs[23]) + var d [12]big.Int + d[0].Add(inputs[0], inputs[6]) + d[1].Set(inputs[6]) + d[2].Add(inputs[2], inputs[8]) + d[3].Set(inputs[8]) + d[4].Add(inputs[4], inputs[10]) + d[5].Set(inputs[10]) + d[6].Add(inputs[1], inputs[7]) + d[7].Set(inputs[7]) + d[8].Add(inputs[3], inputs[9]) + d[9].Set(inputs[9]) + d[10].Add(inputs[5], inputs[11]) + d[11].Set(inputs[11]) + a.C0.B0.A0.SetBigInt(&d[0]) + a.C0.B0.A1.SetBigInt(&d[1]) + a.C0.B1.A0.SetBigInt(&d[2]) + a.C0.B1.A1.SetBigInt(&d[3]) + a.C0.B2.A0.SetBigInt(&d[4]) + a.C0.B2.A1.SetBigInt(&d[5]) + a.C1.B0.A0.SetBigInt(&d[6]) + a.C1.B0.A1.SetBigInt(&d[7]) + a.C1.B1.A0.SetBigInt(&d[8]) + a.C1.B1.A1.SetBigInt(&d[9]) + a.C1.B2.A0.SetBigInt(&d[10]) + a.C1.B2.A1.SetBigInt(&d[11]) + + d[0].Add(inputs[12], inputs[18]) + d[1].Set(inputs[18]) + d[2].Add(inputs[14], inputs[20]) + d[3].Set(inputs[20]) + d[4].Add(inputs[16], inputs[22]) + d[5].Set(inputs[22]) + d[6].Add(inputs[13], inputs[19]) + d[7].Set(inputs[19]) + d[8].Add(inputs[15], inputs[21]) + d[9].Set(inputs[21]) + d[10].Add(inputs[17], inputs[23]) + d[11].Set(inputs[23]) + b.C0.B0.A0.SetBigInt(&d[0]) + b.C0.B0.A1.SetBigInt(&d[1]) + b.C0.B1.A0.SetBigInt(&d[2]) + b.C0.B1.A1.SetBigInt(&d[3]) + b.C0.B2.A0.SetBigInt(&d[4]) + b.C0.B2.A1.SetBigInt(&d[5]) + b.C1.B0.A0.SetBigInt(&d[6]) + b.C1.B0.A1.SetBigInt(&d[7]) + b.C1.B1.A0.SetBigInt(&d[8]) + b.C1.B1.A1.SetBigInt(&d[9]) + b.C1.B2.A0.SetBigInt(&d[10]) + b.C1.B2.A1.SetBigInt(&d[11]) c.Inverse(&b).Mul(&c, &a) - c.C0.B0.A0.BigInt(outputs[0]) - c.C0.B0.A1.BigInt(outputs[1]) - c.C0.B1.A0.BigInt(outputs[2]) - c.C0.B1.A1.BigInt(outputs[3]) - c.C0.B2.A0.BigInt(outputs[4]) - c.C0.B2.A1.BigInt(outputs[5]) - c.C1.B0.A0.BigInt(outputs[6]) + var c0, c1, c2, c3, c4, c5 fp.Element + c0.Sub(&c.C0.B0.A0, &c.C0.B0.A1) + c1.Sub(&c.C1.B0.A0, &c.C1.B0.A1) + c2.Sub(&c.C0.B1.A0, &c.C0.B1.A1) + c3.Sub(&c.C1.B1.A0, &c.C1.B1.A1) + c4.Sub(&c.C0.B2.A0, &c.C0.B2.A1) + c5.Sub(&c.C1.B2.A0, &c.C1.B2.A1) + + c0.BigInt(outputs[0]) + c1.BigInt(outputs[1]) + c2.BigInt(outputs[2]) + c3.BigInt(outputs[3]) + c4.BigInt(outputs[4]) + c5.BigInt(outputs[5]) + c.C0.B0.A1.BigInt(outputs[6]) c.C1.B0.A1.BigInt(outputs[7]) - c.C1.B1.A0.BigInt(outputs[8]) + c.C0.B1.A1.BigInt(outputs[8]) c.C1.B1.A1.BigInt(outputs[9]) - c.C1.B2.A0.BigInt(outputs[10]) + c.C0.B2.A1.BigInt(outputs[10]) c.C1.B2.A1.BigInt(outputs[11]) 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. - return emulated.UnwrapHint(nativeInputs, nativeOutputs, - func(mod *big.Int, inputs, outputs []*big.Int) error { - var millerLoop bls12381.E12 - - millerLoop.C0.B0.A0.SetBigInt(inputs[0]) - millerLoop.C0.B0.A1.SetBigInt(inputs[1]) - millerLoop.C0.B1.A0.SetBigInt(inputs[2]) - millerLoop.C0.B1.A1.SetBigInt(inputs[3]) - millerLoop.C0.B2.A0.SetBigInt(inputs[4]) - millerLoop.C0.B2.A1.SetBigInt(inputs[5]) - millerLoop.C1.B0.A0.SetBigInt(inputs[6]) - millerLoop.C1.B0.A1.SetBigInt(inputs[7]) - millerLoop.C1.B1.A0.SetBigInt(inputs[8]) - millerLoop.C1.B1.A1.SetBigInt(inputs[9]) - millerLoop.C1.B2.A0.SetBigInt(inputs[10]) - millerLoop.C1.B2.A1.SetBigInt(inputs[11]) - - var root, rootPthInverse, root27thInverse, residueWitness, scalingFactor bls12381.E12 - var order3rd, order3rdPower, exponent, exponentInv, finalExpFactor, polyFactor big.Int - // polyFactor = (1-x)/3 - polyFactor.SetString("5044125407647214251", 10) - // finalExpFactor = ((q^12 - 1) / r) / (27 * polyFactor) - finalExpFactor.SetString("2366356426548243601069753987687709088104621721678962410379583120840019275952471579477684846670499039076873213559162845121989217658133790336552276567078487633052653005423051750848782286407340332979263075575489766963251914185767058009683318020965829271737924625612375201545022326908440428522712877494557944965298566001441468676802477524234094954960009227631543471415676620753242466901942121887152806837594306028649150255258504417829961387165043999299071444887652375514277477719817175923289019181393803729926249507024121957184340179467502106891835144220611408665090353102353194448552304429530104218473070114105759487413726485729058069746063140422361472585604626055492939586602274983146215294625774144156395553405525711143696689756441298365274341189385646499074862712688473936093315628166094221735056483459332831845007196600723053356837526749543765815988577005929923802636375670820616189737737304893769679803809426304143627363860243558537831172903494450556755190448279875942974830469855835666815454271389438587399739607656399812689280234103023464545891697941661992848552456326290792224091557256350095392859243101357349751064730561345062266850238821755009430903520645523345000326783803935359711318798844368754833295302563158150573540616830138810935344206231367357992991289265295323280", 10) - - // 1. get pth-root inverse - exponent.Mul(&finalExpFactor, big.NewInt(27)) - root.Exp(millerLoop, &exponent) - if root.IsOne() { - rootPthInverse.SetOne() - } else { - exponentInv.ModInverse(&exponent, &polyFactor) - exponent.Neg(&exponentInv).Mod(&exponent, &polyFactor) - rootPthInverse.Exp(root, &exponent) - } - - // 2.1. get order of 3rd primitive root - var three big.Int - three.SetUint64(3) - exponent.Mul(&polyFactor, &finalExpFactor) - root.Exp(millerLoop, &exponent) - if root.IsOne() { - order3rdPower.SetUint64(0) - } - root.Exp(root, &three) - if root.IsOne() { - order3rdPower.SetUint64(1) - } - root.Exp(root, &three) - if root.IsOne() { - order3rdPower.SetUint64(2) - } - root.Exp(root, &three) - if root.IsOne() { - order3rdPower.SetUint64(3) - } - - // 2.2. get 27th root inverse - if order3rdPower.Uint64() == 0 { - root27thInverse.SetOne() - } else { - order3rd.Exp(&three, &order3rdPower, nil) - exponent.Mul(&polyFactor, &finalExpFactor) - root.Exp(millerLoop, &exponent) - exponentInv.ModInverse(&exponent, &order3rd) - exponent.Neg(&exponentInv).Mod(&exponent, &order3rd) - root27thInverse.Exp(root, &exponent) - } - - // 2.3. shift the Miller loop result so that millerLoop * scalingFactor - // is of order finalExpFactor - scalingFactor.Mul(&rootPthInverse, &root27thInverse) - millerLoop.Mul(&millerLoop, &scalingFactor) - - // 3. get the witness residue - // - // lambda = q - u, the optimal exponent - var lambda big.Int - lambda.SetString("4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129030796414117214202539", 10) - exponent.ModInverse(&lambda, &finalExpFactor) - residueWitness.Exp(millerLoop, &exponent) - - // return the witness residue - residueWitness.C0.B0.A0.BigInt(outputs[0]) - residueWitness.C0.B0.A1.BigInt(outputs[1]) - residueWitness.C0.B1.A0.BigInt(outputs[2]) - residueWitness.C0.B1.A1.BigInt(outputs[3]) - residueWitness.C0.B2.A0.BigInt(outputs[4]) - residueWitness.C0.B2.A1.BigInt(outputs[5]) - residueWitness.C1.B0.A0.BigInt(outputs[6]) - residueWitness.C1.B0.A1.BigInt(outputs[7]) - residueWitness.C1.B1.A0.BigInt(outputs[8]) - residueWitness.C1.B1.A1.BigInt(outputs[9]) - residueWitness.C1.B2.A0.BigInt(outputs[10]) - residueWitness.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/hints.go b/std/algebra/emulated/sw_bls12381/hints.go new file mode 100644 index 000000000..676ed3c22 --- /dev/null +++ b/std/algebra/emulated/sw_bls12381/hints.go @@ -0,0 +1,128 @@ +package sw_bls12381 + +import ( + "math/big" + + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark/constraint/solver" + "github.com/consensys/gnark/std/math/emulated" +) + +func init() { + solver.RegisterHint(GetHints()...) +} + +// GetHints returns all hint functions used in the package. +func GetHints() []solver.Hint { + return []solver.Hint{finalExpHint} +} + +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. + return emulated.UnwrapHint(nativeInputs, nativeOutputs, + func(mod *big.Int, inputs, outputs []*big.Int) error { + var millerLoop bls12381.E12 + + millerLoop.C0.B0.A0.SetBigInt(inputs[0]) + millerLoop.C0.B0.A1.SetBigInt(inputs[1]) + millerLoop.C0.B1.A0.SetBigInt(inputs[2]) + millerLoop.C0.B1.A1.SetBigInt(inputs[3]) + millerLoop.C0.B2.A0.SetBigInt(inputs[4]) + millerLoop.C0.B2.A1.SetBigInt(inputs[5]) + millerLoop.C1.B0.A0.SetBigInt(inputs[6]) + millerLoop.C1.B0.A1.SetBigInt(inputs[7]) + millerLoop.C1.B1.A0.SetBigInt(inputs[8]) + millerLoop.C1.B1.A1.SetBigInt(inputs[9]) + millerLoop.C1.B2.A0.SetBigInt(inputs[10]) + millerLoop.C1.B2.A1.SetBigInt(inputs[11]) + + var root, rootPthInverse, root27thInverse, residueWitness, scalingFactor bls12381.E12 + var order3rd, order3rdPower, exponent, exponentInv, finalExpFactor, polyFactor big.Int + // polyFactor = (1-x)/3 + polyFactor.SetString("5044125407647214251", 10) + // finalExpFactor = ((q^12 - 1) / r) / (27 * polyFactor) + finalExpFactor.SetString("2366356426548243601069753987687709088104621721678962410379583120840019275952471579477684846670499039076873213559162845121989217658133790336552276567078487633052653005423051750848782286407340332979263075575489766963251914185767058009683318020965829271737924625612375201545022326908440428522712877494557944965298566001441468676802477524234094954960009227631543471415676620753242466901942121887152806837594306028649150255258504417829961387165043999299071444887652375514277477719817175923289019181393803729926249507024121957184340179467502106891835144220611408665090353102353194448552304429530104218473070114105759487413726485729058069746063140422361472585604626055492939586602274983146215294625774144156395553405525711143696689756441298365274341189385646499074862712688473936093315628166094221735056483459332831845007196600723053356837526749543765815988577005929923802636375670820616189737737304893769679803809426304143627363860243558537831172903494450556755190448279875942974830469855835666815454271389438587399739607656399812689280234103023464545891697941661992848552456326290792224091557256350095392859243101357349751064730561345062266850238821755009430903520645523345000326783803935359711318798844368754833295302563158150573540616830138810935344206231367357992991289265295323280", 10) + + // 1. get pth-root inverse + exponent.Mul(&finalExpFactor, big.NewInt(27)) + root.Exp(millerLoop, &exponent) + if root.IsOne() { + rootPthInverse.SetOne() + } else { + exponentInv.ModInverse(&exponent, &polyFactor) + exponent.Neg(&exponentInv).Mod(&exponent, &polyFactor) + rootPthInverse.Exp(root, &exponent) + } + + // 2.1. get order of 3rd primitive root + var three big.Int + three.SetUint64(3) + exponent.Mul(&polyFactor, &finalExpFactor) + root.Exp(millerLoop, &exponent) + if root.IsOne() { + order3rdPower.SetUint64(0) + } + root.Exp(root, &three) + if root.IsOne() { + order3rdPower.SetUint64(1) + } + root.Exp(root, &three) + if root.IsOne() { + order3rdPower.SetUint64(2) + } + root.Exp(root, &three) + if root.IsOne() { + order3rdPower.SetUint64(3) + } + + // 2.2. get 27th root inverse + if order3rdPower.Uint64() == 0 { + root27thInverse.SetOne() + } else { + order3rd.Exp(&three, &order3rdPower, nil) + exponent.Mul(&polyFactor, &finalExpFactor) + root.Exp(millerLoop, &exponent) + exponentInv.ModInverse(&exponent, &order3rd) + exponent.Neg(&exponentInv).Mod(&exponent, &order3rd) + root27thInverse.Exp(root, &exponent) + } + + // 2.3. shift the Miller loop result so that millerLoop * scalingFactor + // is of order finalExpFactor + scalingFactor.Mul(&rootPthInverse, &root27thInverse) + millerLoop.Mul(&millerLoop, &scalingFactor) + + // 3. get the witness residue + // + // lambda = q - u, the optimal exponent + var lambda big.Int + lambda.SetString("4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129030796414117214202539", 10) + exponent.ModInverse(&lambda, &finalExpFactor) + residueWitness.Exp(millerLoop, &exponent) + + // return the witness residue + residueWitness.C0.B0.A0.BigInt(outputs[0]) + residueWitness.C0.B0.A1.BigInt(outputs[1]) + residueWitness.C0.B1.A0.BigInt(outputs[2]) + residueWitness.C0.B1.A1.BigInt(outputs[3]) + residueWitness.C0.B2.A0.BigInt(outputs[4]) + residueWitness.C0.B2.A1.BigInt(outputs[5]) + residueWitness.C1.B0.A0.BigInt(outputs[6]) + residueWitness.C1.B0.A1.BigInt(outputs[7]) + residueWitness.C1.B1.A0.BigInt(outputs[8]) + residueWitness.C1.B1.A1.BigInt(outputs[9]) + residueWitness.C1.B2.A0.BigInt(outputs[10]) + residueWitness.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 b46a3d2c2..8f6018090 100644 --- a/std/algebra/emulated/sw_bls12381/pairing.go +++ b/std/algebra/emulated/sw_bls12381/pairing.go @@ -6,6 +6,7 @@ import ( "math/big" 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/algebra/emulated/fields_bls12381" "github.com/consensys/gnark/std/algebra/emulated/sw_emulated" @@ -15,46 +16,40 @@ import ( type Pairing struct { api frontend.API *fields_bls12381.Ext12 + *fields_bls12381.Ext2 curveF *emulated.Field[BaseField] curve *sw_emulated.Curve[BaseField, ScalarField] g2 *G2 g1 *G1 bTwist *fields_bls12381.E2 - g2gen *G2Affine } +type baseEl = emulated.Element[BaseField] type GTEl = fields_bls12381.E12 -func NewGTEl(v bls12381.GT) GTEl { +func NewGTEl(a bls12381.GT) GTEl { + + var c0, c1, c2, c3, c4, c5 fp.Element + c0.Sub(&a.C0.B0.A0, &a.C0.B0.A1) + c1.Sub(&a.C1.B0.A0, &a.C1.B0.A1) + c2.Sub(&a.C0.B1.A0, &a.C0.B1.A1) + c3.Sub(&a.C1.B1.A0, &a.C1.B1.A1) + c4.Sub(&a.C0.B2.A0, &a.C0.B2.A1) + c5.Sub(&a.C1.B2.A0, &a.C1.B2.A1) + return GTEl{ - C0: fields_bls12381.E6{ - B0: fields_bls12381.E2{ - A0: emulated.ValueOf[BaseField](v.C0.B0.A0), - A1: emulated.ValueOf[BaseField](v.C0.B0.A1), - }, - B1: fields_bls12381.E2{ - A0: emulated.ValueOf[BaseField](v.C0.B1.A0), - A1: emulated.ValueOf[BaseField](v.C0.B1.A1), - }, - B2: fields_bls12381.E2{ - A0: emulated.ValueOf[BaseField](v.C0.B2.A0), - A1: emulated.ValueOf[BaseField](v.C0.B2.A1), - }, - }, - C1: fields_bls12381.E6{ - B0: fields_bls12381.E2{ - A0: emulated.ValueOf[BaseField](v.C1.B0.A0), - A1: emulated.ValueOf[BaseField](v.C1.B0.A1), - }, - B1: fields_bls12381.E2{ - A0: emulated.ValueOf[BaseField](v.C1.B1.A0), - A1: emulated.ValueOf[BaseField](v.C1.B1.A1), - }, - B2: fields_bls12381.E2{ - A0: emulated.ValueOf[BaseField](v.C1.B2.A0), - A1: emulated.ValueOf[BaseField](v.C1.B2.A1), - }, - }, + A0: emulated.ValueOf[BaseField](c0), + A1: emulated.ValueOf[BaseField](c1), + A2: emulated.ValueOf[BaseField](c2), + A3: emulated.ValueOf[BaseField](c3), + A4: emulated.ValueOf[BaseField](c4), + A5: emulated.ValueOf[BaseField](c5), + A6: emulated.ValueOf[BaseField](a.C0.B0.A1), + A7: emulated.ValueOf[BaseField](a.C1.B0.A1), + A8: emulated.ValueOf[BaseField](a.C0.B1.A1), + A9: emulated.ValueOf[BaseField](a.C1.B1.A1), + A10: emulated.ValueOf[BaseField](a.C0.B2.A1), + A11: emulated.ValueOf[BaseField](a.C1.B2.A1), } } @@ -78,6 +73,7 @@ func NewPairing(api frontend.API) (*Pairing, error) { return &Pairing{ api: api, Ext12: fields_bls12381.NewExt12(api), + Ext2: fields_bls12381.NewExt2(api), curveF: ba, curve: curve, g1: g1, @@ -86,144 +82,6 @@ func NewPairing(api frontend.API) (*Pairing, error) { }, nil } -func (pr Pairing) generators() *G2Affine { - if pr.g2gen == nil { - _, _, _, g2gen := bls12381.Generators() - cg2gen := NewG2AffineFixed(g2gen) - pr.g2gen = &cg2gen - } - return pr.g2gen -} - -// FinalExponentiation computes the exponentiation (∏ᵢ zᵢ)ᵈ where -// -// d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r -// -// we use instead -// -// d=s ⋅ (p⁶-1)(p²+1)(p⁴ - p² +1)/r -// -// where s is the cofactor 3 (Hayashida et al.). -// -// FinalExponentiation returns a decompressed element in E12. -// -// This is the safe version of the method where e may be {-1,1}. If it is known -// that e ≠ {-1,1} then using the unsafe version of the method saves -// considerable amount of constraints. When called with the result of -// [MillerLoop], then current method is applicable when length of the inputs to -// Miller loop is 1. -func (pr Pairing) FinalExponentiation(e *GTEl) *GTEl { - return pr.finalExponentiation(e, false) -} - -// FinalExponentiationUnsafe computes the exponentiation (∏ᵢ zᵢ)ᵈ where -// -// d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r -// -// we use instead -// -// d=s ⋅ (p⁶-1)(p²+1)(p⁴ - p² +1)/r -// -// where s is the cofactor 3 (Hayashida et al.). -// -// FinalExponentiationUnsafe returns a decompressed element in E12. -// -// This is the unsafe version of the method where e may NOT be {-1,1}. If e ∈ -// {-1, 1}, then there exists no valid solution to the circuit. This method is -// applicable when called with the result of [MillerLoop] method when the length -// of the inputs to Miller loop is 1. -func (pr Pairing) FinalExponentiationUnsafe(e *GTEl) *GTEl { - return pr.finalExponentiation(e, true) -} - -// finalExponentiation computes the exponentiation (∏ᵢ zᵢ)ᵈ where -// -// d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r -// -// we use instead -// -// d=s ⋅ (p⁶-1)(p²+1)(p⁴ - p² +1)/r -// -// where s is the cofactor 3 (Hayashida et al.). -// -// finalExponentiation returns a decompressed element in E12 -func (pr Pairing) finalExponentiation(e *GTEl, unsafe bool) *GTEl { - - // 1. Easy part - // (p⁶-1)(p²+1) - var selector1, selector2 frontend.Variable - _dummy := pr.Ext6.One() - - if unsafe { - // The Miller loop result is ≠ {-1,1}, otherwise this means P and Q are - // linearly dependent and not from G1 and G2 respectively. - // So e ∈ G_{q,2} \ {-1,1} and hence e.C1 ≠ 0. - // Nothing to do. - - } else { - // However, for a product of Miller loops (n>=2) this might happen. If this is - // the case, the result is 1 in the torus. We assign a dummy value (1) to e.C1 - // and proceed further. - selector1 = pr.Ext6.IsZero(&e.C1) - e.C1.B0.A0 = *pr.curveF.Select(selector1, pr.curveF.One(), &e.C1.B0.A0) - } - - // Torus compression absorbed: - // Raising e to (p⁶-1) is - // e^(p⁶) / e = (e.C0 - w*e.C1) / (e.C0 + w*e.C1) - // = (-e.C0/e.C1 + w) / (-e.C0/e.C1 - w) - // So the fraction -e.C0/e.C1 is already in the torus. - // This absorbs the torus compression in the easy part. - c := pr.Ext6.DivUnchecked(&e.C0, &e.C1) - c = pr.Ext6.Neg(c) - t0 := pr.FrobeniusSquareTorus(c) - c = pr.MulTorus(t0, c) - - // 2. Hard part (up to permutation) - // 3(p⁴-p²+1)/r - // Daiki Hayashida, Kenichiro Hayasaka and Tadanori Teruya - // https://eprint.iacr.org/2020/875.pdf - // performed in torus compressed form - t0 = pr.SquareTorus(c) - t1 := pr.ExptHalfTorus(t0) - t2 := pr.InverseTorus(c) - t1 = pr.MulTorus(t1, t2) - t2 = pr.ExptTorus(t1) - t1 = pr.InverseTorus(t1) - t1 = pr.MulTorus(t1, t2) - t2 = pr.ExptTorus(t1) - t1 = pr.FrobeniusTorus(t1) - t1 = pr.MulTorus(t1, t2) - c = pr.MulTorus(c, t0) - t0 = pr.ExptTorus(t1) - t2 = pr.ExptTorus(t0) - t0 = pr.FrobeniusSquareTorus(t1) - t1 = pr.InverseTorus(t1) - t1 = pr.MulTorus(t1, t2) - t1 = pr.MulTorus(t1, t0) - - var result GTEl - // MulTorus(c, t1) requires c ≠ -t1. When c = -t1, it means the - // product is 1 in the torus. - if unsafe { - // For a single pairing, this does not happen because the pairing is non-degenerate. - result = *pr.DecompressTorus(pr.MulTorus(c, t1)) - } else { - // For a product of pairings this might happen when the result is expected to be 1. - // We assign a dummy value (1) to t1 and proceed further. - // Finally we do a select on both edge cases: - // - Only if seletor1=0 and selector2=0, we return MulTorus(c, t1) decompressed. - // - Otherwise, we return 1. - _sum := pr.Ext6.Add(c, t1) - selector2 = pr.Ext6.IsZero(_sum) - t1 = pr.Ext6.Select(selector2, _dummy, t1) - selector := pr.api.Mul(pr.api.Sub(1, selector1), pr.api.Sub(1, selector2)) - result = *pr.Select(selector, pr.DecompressTorus(pr.MulTorus(c, t1)), pr.One()) - } - - return &result -} - // Pair calculates the reduced pairing for a set of points // ∏ᵢ e(Pᵢ, Qᵢ). // @@ -233,7 +91,7 @@ func (pr Pairing) Pair(P []*G1Affine, Q []*G2Affine) (*GTEl, error) { if err != nil { return nil, fmt.Errorf("miller loop: %w", err) } - res = pr.finalExponentiation(res, len(P) == 1) + res = pr.FinalExponentiation(res) return res, nil } @@ -247,15 +105,6 @@ func (pr Pairing) PairingCheck(P []*G1Affine, Q []*G2Affine) error { return err } - // We perform the easy part of the final exp to push f to the cyclotomic - // subgroup so that AssertFinalExponentiationIsOne is carried with optimized - // cyclotomic squaring (e.g. Karabina12345). - // - // f = f^(p⁶-1)(p²+1) - buf := pr.Conjugate(f) - buf = pr.DivUnchecked(buf, f) - f = pr.FrobeniusSquare(buf) - f = pr.Mul(f, buf) pr.AssertFinalExponentiationIsOne(f) @@ -356,8 +205,8 @@ func (pr Pairing) millerLoopLines(P []*G1Affine, lines []lineEvaluations) (*GTEl } // precomputations - yInv := make([]*emulated.Element[BaseField], n) - xNegOverY := make([]*emulated.Element[BaseField], n) + yInv := make([]*baseEl, n) + xNegOverY := make([]*baseEl, n) for k := 0; k < n; k++ { // P are supposed to be on G1 respectively of prime order r. @@ -371,39 +220,14 @@ func (pr Pairing) millerLoopLines(P []*G1Affine, lines []lineEvaluations) (*GTEl res := pr.Ext12.One() // Compute ∏ᵢ { fᵢ_{x₀,Q}(P) } - // i = 62, separately to avoid an E12 Square // (Square(res) = 1² = 1) - // k = 0, separately to avoid MulBy014 (res × ℓ) - res.C0.B0 = *pr.MulByElement(&lines[0][0][62].R1, yInv[0]) - res.C0.B1 = *pr.MulByElement(&lines[0][0][62].R0, xNegOverY[0]) - res.C1.B1 = *pr.Ext2.One() - - prodLines := pr.Mul014By014( - pr.MulByElement(&lines[0][1][62].R1, yInv[0]), - pr.MulByElement(&lines[0][1][62].R0, xNegOverY[0]), - &res.C0.B0, - &res.C0.B1, - ) - res = &fields_bls12381.E12{ - C0: fields_bls12381.E6{ - B0: *prodLines[0], - B1: *prodLines[1], - B2: *prodLines[2], - }, - C1: fields_bls12381.E6{ - B0: res.C1.B0, - B1: *prodLines[3], - B2: *prodLines[4], - }, - } - - for k := 1; k < n; k++ { - res = pr.MulBy014(res, + for k := 0; k < n; k++ { + res = pr.MulBy02368(res, pr.MulByElement(&lines[k][0][62].R1, yInv[k]), pr.MulByElement(&lines[k][0][62].R0, xNegOverY[k]), ) - res = pr.MulBy014(res, + res = pr.MulBy02368(res, pr.MulByElement(&lines[k][1][62].R1, yInv[k]), pr.MulByElement(&lines[k][1][62].R0, xNegOverY[k]), ) @@ -412,20 +236,20 @@ func (pr Pairing) millerLoopLines(P []*G1Affine, lines []lineEvaluations) (*GTEl for i := 61; i >= 0; i-- { // mutualize the square among n Miller loops // (∏ᵢfᵢ)² - res = pr.Square(res) + res = pr.Ext12.Square(res) for k := 0; k < n; k++ { if loopCounter[i] == 0 { - res = pr.MulBy014(res, + res = pr.MulBy02368(res, pr.MulByElement(&lines[k][0][i].R1, yInv[k]), pr.MulByElement(&lines[k][0][i].R0, xNegOverY[k]), ) } else { - res = pr.MulBy014(res, + res = pr.MulBy02368(res, pr.MulByElement(&lines[k][0][i].R1, yInv[k]), pr.MulByElement(&lines[k][0][i].R0, xNegOverY[k]), ) - res = pr.MulBy014(res, + res = pr.MulBy02368(res, pr.MulByElement(&lines[k][1][i].R1, yInv[k]), pr.MulByElement(&lines[k][1][i].R0, xNegOverY[k]), ) @@ -439,13 +263,118 @@ func (pr Pairing) millerLoopLines(P []*G1Affine, lines []lineEvaluations) (*GTEl return res, nil } +// FinalExponentiation computes the exponentiation (∏ᵢ zᵢ)ᵈ +// where d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r +// we use instead d=s ⋅ (p⁶-1)(p²+1)(p⁴ - p² +1)/r +// where s is the cofactor 3 (Hayashida et al.) +func (pr Pairing) FinalExponentiation(e *GTEl) *GTEl { + z := pr.Copy(e) + + // Easy part + // (p⁶-1)(p²+1) + t0 := pr.Ext12.Conjugate(z) + t0 = pr.Ext12.DivUnchecked(t0, z) + z = pr.Ext12.FrobeniusSquare(t0) + z = pr.Ext12.Mul(z, t0) + + // Hard part (up to permutation) + // Daiki Hayashida, Kenichiro Hayasaka and Tadanori Teruya + // https://eprint.iacr.org/2020/875.pdf + t0 = pr.Ext12.CyclotomicSquareGS(z) + t1 := pr.Ext12.ExptHalfGS(t0) + t2 := pr.Ext12.Conjugate(z) + t1 = pr.Ext12.Mul(t1, t2) + t2 = pr.Ext12.ExptGS(t1) + t1 = pr.Ext12.Conjugate(t1) + t1 = pr.Ext12.Mul(t1, t2) + t2 = pr.Ext12.ExptGS(t1) + t1 = pr.Ext12.Frobenius(t1) + t1 = pr.Ext12.Mul(t1, t2) + z = pr.Ext12.Mul(z, t0) + t0 = pr.Ext12.ExptGS(t1) + t2 = pr.Ext12.ExptGS(t0) + t0 = pr.Ext12.FrobeniusSquare(t1) + t1 = pr.Ext12.Conjugate(t1) + t1 = pr.Ext12.Mul(t1, t2) + t1 = pr.Ext12.Mul(t1, t0) + z = pr.Ext12.Mul(z, t1) + + return z +} + +// AssertFinalExponentiationIsOne checks that a Miller function output x lies in the +// same equivalence class as the reduced pairing. This replaces the final +// exponentiation step in-circuit. +// The method is inspired from [On Proving Pairings] paper by A. Novakovic and +// L. Eagen, and is based on a personal communication with A. Novakovic. +// +// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf +func (pr Pairing) AssertFinalExponentiationIsOne(x *GTEl) { + tower := pr.ToTower(x) + + res, err := pr.curveF.NewHint(finalExpHint, 24, 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) + } + + if err != nil { + // err is non-nil only for invalid number of inputs + panic(err) + } + + residueWitness := pr.FromTower([12]*baseEl{res[0], res[1], res[2], res[3], res[4], res[5], res[6], res[7], res[8], res[9], res[10], res[11]}) + // constrain cubicNonResiduePower 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(res[12], res[13]), + A1: *pr.curveF.Zero(), + A2: *pr.curveF.Sub(res[14], res[15]), + A3: *pr.curveF.Zero(), + A4: *pr.curveF.Sub(res[16], res[17]), + A5: *pr.curveF.Zero(), + A6: *res[13], + A7: *pr.curveF.Zero(), + A8: *res[15], + A9: *pr.curveF.Zero(), + A10: *res[17], + A11: *pr.curveF.Zero(), + } + + // Check that x * scalingFactor == residueWitness^(q-u) + // where u=-0xd201000000010000 is the BLS12-381 seed, + // and residueWitness, scalingFactor from the hint. + t0 := pr.Frobenius(residueWitness) + // exponentiation by -u + t1 := pr.ExptNeg(residueWitness) + t0 = pr.Ext12.Mul(t0, t1) + + t1 = pr.Ext12.Mul(x, &scalingFactor) + + pr.AssertIsEqual(t0, t1) +} + // doubleAndAddStep doubles p1 and adds p2 to the result in affine coordinates. -// Then evaluates the lines going through p1 and p2 (line1) and p1 and p1+p2 (line2). +// Then evaluates the lines going through p1 and p2 or -p2 (line1) and p1 and p1+p2 (line2). // https://eprint.iacr.org/2022/1162 (Section 6.1) func (pr Pairing) doubleAndAddStep(p1, p2 *g2AffP) (*g2AffP, *lineEvaluation, *lineEvaluation) { var line1, line2 lineEvaluation var p g2AffP + mone := pr.curveF.NewElement(-1) // compute λ1 = (y2-y1)/(x2-x1) n := pr.Ext2.Sub(&p1.Y, &p2.Y) @@ -453,39 +382,42 @@ func (pr Pairing) doubleAndAddStep(p1, p2 *g2AffP) (*g2AffP, *lineEvaluation, *l λ1 := pr.Ext2.DivUnchecked(n, d) // compute x3 =λ1²-x1-x2 - x3 := pr.Ext2.Square(λ1) - x3 = pr.Ext2.Sub(x3, pr.Ext2.Add(&p1.X, &p2.X)) + x30 := pr.curveF.Eval([][]*baseEl{{&λ1.A0, &λ1.A0}, {mone, &λ1.A1, &λ1.A1}, {mone, &p1.X.A0}, {mone, &p2.X.A0}}, []int{1, 1, 1, 1}) + x31 := pr.curveF.Eval([][]*baseEl{{&λ1.A0, &λ1.A1}, {mone, &p1.X.A1}, {mone, &p2.X.A1}}, []int{2, 1, 1}) + x3 := &fields_bls12381.E2{A0: *x30, A1: *x31} // omit y3 computation // compute line1 line1.R0 = *λ1 - line1.R1 = *pr.Ext2.Mul(λ1, &p1.X) - line1.R1 = *pr.Ext2.Sub(&line1.R1, &p1.Y) + line1.R1.A0 = *pr.curveF.Eval([][]*baseEl{{&λ1.A0, &p1.X.A0}, {mone, &λ1.A1, &p1.X.A1}, {mone, &p1.Y.A0}}, []int{1, 1, 1}) + line1.R1.A1 = *pr.curveF.Eval([][]*baseEl{{&λ1.A0, &p1.X.A1}, {&λ1.A1, &p1.X.A0}, {mone, &p1.Y.A1}}, []int{1, 1, 1}) // compute λ2 = -λ1-2y1/(x3-x1) - n = pr.Ext2.Double(&p1.Y) + n = pr.Ext2.MulByConstElement(&p1.Y, big.NewInt(2)) d = pr.Ext2.Sub(x3, &p1.X) λ2 := pr.Ext2.DivUnchecked(n, d) λ2 = pr.Ext2.Add(λ2, λ1) λ2 = pr.Ext2.Neg(λ2) // compute x4 = λ2²-x1-x3 - x4 := pr.Ext2.Square(λ2) - x4 = pr.Ext2.Sub(x4, pr.Ext2.Add(&p1.X, x3)) + x40 := pr.curveF.Eval([][]*baseEl{{&λ2.A0, &λ2.A0}, {mone, &λ2.A1, &λ2.A1}, {mone, &p1.X.A0}, {mone, x30}}, []int{1, 1, 1, 1}) + x41 := pr.curveF.Eval([][]*baseEl{{&λ2.A0, &λ2.A1}, {mone, &p1.X.A1}, {mone, x31}}, []int{2, 1, 1}) + x4 := &fields_bls12381.E2{A0: *x40, A1: *x41} // compute y4 = λ2(x1 - x4)-y1 y4 := pr.Ext2.Sub(&p1.X, x4) - y4 = pr.Ext2.Mul(λ2, y4) - y4 = pr.Ext2.Sub(y4, &p1.Y) + y40 := pr.curveF.Eval([][]*baseEl{{&λ2.A0, &y4.A0}, {mone, &λ2.A1, &y4.A1}, {mone, &p1.Y.A0}}, []int{1, 1, 1}) + y41 := pr.curveF.Eval([][]*baseEl{{&λ2.A0, &y4.A1}, {&λ2.A1, &y4.A0}, {mone, &p1.Y.A1}}, []int{1, 1, 1}) + y4 = &fields_bls12381.E2{A0: *y40, A1: *y41} p.X = *x4 p.Y = *y4 // compute line2 line2.R0 = *λ2 - line2.R1 = *pr.Ext2.Mul(λ2, &p1.X) - line2.R1 = *pr.Ext2.Sub(&line2.R1, &p1.Y) + line2.R1.A0 = *pr.curveF.Eval([][]*baseEl{{&λ2.A0, &p1.X.A0}, {mone, &λ2.A1, &p1.X.A1}, {mone, &p1.Y.A0}}, []int{1, 1, 1}) + line2.R1.A1 = *pr.curveF.Eval([][]*baseEl{{&λ2.A0, &p1.X.A1}, {&λ2.A1, &p1.X.A0}, {mone, &p1.Y.A1}}, []int{1, 1, 1}) return &p, &line1, &line2 } @@ -496,29 +428,31 @@ func (pr Pairing) doubleStep(p1 *g2AffP) (*g2AffP, *lineEvaluation) { var p g2AffP var line lineEvaluation + mone := pr.curveF.NewElement(-1) // λ = 3x²/2y n := pr.Ext2.Square(&p1.X) - three := big.NewInt(3) - n = pr.Ext2.MulByConstElement(n, three) - d := pr.Ext2.Double(&p1.Y) + n = pr.Ext2.MulByConstElement(n, big.NewInt(3)) + d := pr.Ext2.MulByConstElement(&p1.Y, big.NewInt(2)) λ := pr.Ext2.DivUnchecked(n, d) // xr = λ²-2x - xr := pr.Ext2.Square(λ) - xr = pr.Ext2.Sub(xr, pr.Ext2.MulByConstElement(&p1.X, big.NewInt(2))) + xr0 := pr.curveF.Eval([][]*baseEl{{&λ.A0, &λ.A0}, {mone, &λ.A1, &λ.A1}, {mone, &p1.X.A0}}, []int{1, 1, 2}) + xr1 := pr.curveF.Eval([][]*baseEl{{&λ.A0, &λ.A1}, {mone, &p1.X.A1}}, []int{2, 2}) + xr := &fields_bls12381.E2{A0: *xr0, A1: *xr1} // yr = λ(x-xr)-y yr := pr.Ext2.Sub(&p1.X, xr) - yr = pr.Ext2.Mul(λ, yr) - yr = pr.Ext2.Sub(yr, &p1.Y) + yr0 := pr.curveF.Eval([][]*baseEl{{&λ.A0, &yr.A0}, {mone, &λ.A1, &yr.A1}, {mone, &p1.Y.A0}}, []int{1, 1, 1}) + yr1 := pr.curveF.Eval([][]*baseEl{{&λ.A0, &yr.A1}, {&λ.A1, &yr.A0}, {mone, &p1.Y.A1}}, []int{1, 1, 1}) + yr = &fields_bls12381.E2{A0: *yr0, A1: *yr1} p.X = *xr p.Y = *yr line.R0 = *λ - line.R1 = *pr.Ext2.Mul(λ, &p1.X) - line.R1 = *pr.Ext2.Sub(&line.R1, &p1.Y) + 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 &p, &line @@ -529,6 +463,7 @@ func (pr Pairing) tripleStep(p1 *g2AffP) (*g2AffP, *lineEvaluation, *lineEvaluat var line1, line2 lineEvaluation var res g2AffP + mone := pr.curveF.NewElement(-1) // λ1 = 3x²/2y n := pr.Ext2.Square(&p1.X) @@ -539,12 +474,13 @@ func (pr Pairing) tripleStep(p1 *g2AffP) (*g2AffP, *lineEvaluation, *lineEvaluat // compute line1 line1.R0 = *λ1 - line1.R1 = *pr.Ext2.Mul(λ1, &p1.X) - line1.R1 = *pr.Ext2.Sub(&line1.R1, &p1.Y) + line1.R1.A0 = *pr.curveF.Eval([][]*baseEl{{&λ1.A0, &p1.X.A0}, {mone, &λ1.A1, &p1.X.A1}, {mone, &p1.Y.A0}}, []int{1, 1, 1}) + line1.R1.A1 = *pr.curveF.Eval([][]*baseEl{{&λ1.A0, &p1.X.A1}, {&λ1.A1, &p1.X.A0}, {mone, &p1.Y.A1}}, []int{1, 1, 1}) // x2 = λ1²-2x - x2 := pr.Ext2.Square(λ1) - x2 = pr.Ext2.Sub(x2, pr.Ext2.MulByConstElement(&p1.X, big.NewInt(2))) + x20 := pr.curveF.Eval([][]*baseEl{{&λ1.A0, &λ1.A0}, {mone, &λ1.A1, &λ1.A1}, {mone, &p1.X.A0}}, []int{1, 1, 2}) + x21 := pr.curveF.Eval([][]*baseEl{{&λ1.A0, &λ1.A1}, {mone, &p1.X.A1}}, []int{2, 2}) + x2 := &fields_bls12381.E2{A0: *x20, A1: *x21} // omit yr computation, and // compute λ2 = 2y/(x2 − x) − λ1. @@ -554,18 +490,19 @@ func (pr Pairing) tripleStep(p1 *g2AffP) (*g2AffP, *lineEvaluation, *lineEvaluat // compute line2 line2.R0 = *λ2 - line2.R1 = *pr.Ext2.Mul(λ2, &p1.X) - line2.R1 = *pr.Ext2.Sub(&line2.R1, &p1.Y) + line2.R1.A0 = *pr.curveF.Eval([][]*baseEl{{&λ2.A0, &p1.X.A0}, {mone, &λ2.A1, &p1.X.A1}, {mone, &p1.Y.A0}}, []int{1, 1, 1}) + line2.R1.A1 = *pr.curveF.Eval([][]*baseEl{{&λ2.A0, &p1.X.A1}, {&λ2.A1, &p1.X.A0}, {mone, &p1.Y.A1}}, []int{1, 1, 1}) - // xr = λ²-p.x-x2 - λ2λ2 := pr.Ext2.Mul(λ2, λ2) - qxrx := pr.Ext2.Add(x2, &p1.X) - xr := pr.Ext2.Sub(λ2λ2, qxrx) + // xr = λ²-x1-x2 + xr0 := pr.curveF.Eval([][]*baseEl{{&λ2.A0, &λ2.A0}, {mone, &λ2.A1, &λ2.A1}, {mone, &p1.X.A0}, {mone, x20}}, []int{1, 1, 1, 1}) + xr1 := pr.curveF.Eval([][]*baseEl{{&λ2.A0, &λ2.A1}, {mone, &p1.X.A1}, {mone, x21}}, []int{2, 1, 1}) + xr := &fields_bls12381.E2{A0: *xr0, A1: *xr1} - // yr = λ(p.x-xr) - p.y - pxrx := pr.Ext2.Sub(&p1.X, xr) - λ2pxrx := pr.Ext2.Mul(λ2, pxrx) - yr := pr.Ext2.Sub(λ2pxrx, &p1.Y) + // yr = λ(x1-xr) - y1 + yr := pr.Ext2.Sub(&p1.X, xr) + yr0 := pr.curveF.Eval([][]*baseEl{{&λ2.A0, &yr.A0}, {mone, &λ2.A1, &yr.A1}, {mone, &p1.Y.A0}}, []int{1, 1, 1}) + yr1 := pr.curveF.Eval([][]*baseEl{{&λ2.A0, &yr.A1}, {&λ2.A1, &yr.A0}, {mone, &p1.Y.A1}}, []int{1, 1, 1}) + yr = &fields_bls12381.E2{A0: *yr0, A1: *yr1} res.X = *xr res.Y = *yr @@ -584,9 +521,10 @@ func (pr Pairing) tangentCompute(p1 *g2AffP) *lineEvaluation { λ := pr.Ext2.DivUnchecked(n, d) var line lineEvaluation + mone := pr.curveF.NewElement(-1) line.R0 = *λ - line.R1 = *pr.Ext2.Mul(λ, &p1.X) - line.R1 = *pr.Ext2.Sub(&line.R1, &p1.Y) + 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 diff --git a/std/algebra/emulated/sw_bls12381/pairing_test.go b/std/algebra/emulated/sw_bls12381/pairing_test.go index 80490e280..30cbb14a0 100644 --- a/std/algebra/emulated/sw_bls12381/pairing_test.go +++ b/std/algebra/emulated/sw_bls12381/pairing_test.go @@ -43,10 +43,8 @@ func (c *FinalExponentiationCircuit) Define(api frontend.API) error { if err != nil { return fmt.Errorf("new pairing: %w", err) } - res1 := pairing.FinalExponentiation(&c.InGt) - pairing.AssertIsEqual(res1, &c.Res) - res2 := pairing.FinalExponentiationUnsafe(&c.InGt) - pairing.AssertIsEqual(res2, &c.Res) + res := pairing.FinalExponentiation(&c.InGt) + pairing.AssertIsEqual(res, &c.Res) return nil } @@ -63,6 +61,40 @@ func TestFinalExponentiationTestSolve(t *testing.T) { assert.NoError(err) } +type FinalExponentiationIsOne struct { + InGt GTEl +} + +func (c *FinalExponentiationIsOne) Define(api frontend.API) error { + pairing, err := NewPairing(api) + if err != nil { + return fmt.Errorf("new pairing: %w", err) + } + pairing.AssertFinalExponentiationIsOne(&c.InGt) + return nil +} + +func TestFinalExponentiationIsOneTestSolve(t *testing.T) { + assert := test.NewAssert(t) + // e(a,2b) * e(-2a,b) == 1 + p1, q1 := randomG1G2Affines() + var p2 bls12381.G1Affine + p2.Double(&p1).Neg(&p2) + var q2 bls12381.G2Affine + q2.Set(&q1) + q1.Double(&q1) + ml, err := bls12381.MillerLoop( + []bls12381.G1Affine{p1, p2}, + []bls12381.G2Affine{q1, q2}, + ) + assert.NoError(err) + witness := FinalExponentiationIsOne{ + InGt: NewGTEl(ml), + } + err = test.IsSolved(&FinalExponentiationIsOne{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) +} + type PairCircuit struct { InG1 G1Affine InG2 G2Affine @@ -295,64 +327,3 @@ func BenchmarkPairing(b *testing.B) { } }) } - -func BenchmarkFinalExponentiation(b *testing.B) { - // e(a,2b) * e(-2a,b) == 1 - var gt bls12381.GT - gt.SetRandom() - res := bls12381.FinalExponentiation(>) - witness := FinalExponentiationCircuit{ - InGt: NewGTEl(gt), - Res: NewGTEl(res), - } - 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, &FinalExponentiationCircuit{}); 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, &FinalExponentiationCircuit{}); 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 b04a6884de8de776f2d3803ec9f81892a8ae5886 Mon Sep 17 00:00:00 2001 From: Jeremy Felder Date: Mon, 16 Dec 2024 11:55:48 +0200 Subject: [PATCH 05/12] Update ICICLE integration to use v3 ICICLE (#1318) Co-authored-by: Ivo Kubjas --- README.md | 6 +- backend/groth16/bn254/icicle/device.go | 29 + backend/groth16/bn254/icicle/doc.go | 2 +- backend/groth16/bn254/icicle/icicle.go | 677 ++++++++++++------- backend/groth16/bn254/icicle/marshal_test.go | 33 +- backend/groth16/bn254/icicle/noicicle.go | 2 +- backend/groth16/bn254/icicle/provingkey.go | 32 +- backend/groth16/groth16.go | 2 +- go.mod | 4 +- go.sum | 6 +- 10 files changed, 537 insertions(+), 256 deletions(-) create mode 100644 backend/groth16/bn254/icicle/device.go diff --git a/README.md b/README.md index 229630ff9..02c008f95 100644 --- a/README.md +++ b/README.md @@ -162,9 +162,9 @@ func main() { ### GPU Support -#### Icicle Library +#### ICICLE Library -The following schemes and curves support experimental use of Ingonyama's Icicle GPU library for low level zk-SNARK primitives such as MSM, NTT, and polynomial operations: +The following schemes and curves support experimental use of Ingonyama's ICICLE GPU library for low level zk-SNARK primitives such as MSM, NTT, and polynomial operations: - [x] [Groth16](https://eprint.iacr.org/2016/260) @@ -184,7 +184,7 @@ You can then toggle on or off icicle acceleration by providing the `WithIcicleAc proof, err := groth16.Prove(ccs, pk, secretWitness) ``` -For more information about prerequisites see the [Icicle repo](https://github.com/ingonyama-zk/icicle). +For more information about prerequisites see the [ICICLE repo](https://github.com/ingonyama-zk/icicle). **NB! ICICLE CUDA kernels are covered by a special license for now. Follow the instructions to download and set up the kernels.** ## Citing diff --git a/backend/groth16/bn254/icicle/device.go b/backend/groth16/bn254/icicle/device.go new file mode 100644 index 000000000..d6d86ea36 --- /dev/null +++ b/backend/groth16/bn254/icicle/device.go @@ -0,0 +1,29 @@ +package icicle + +import ( + "fmt" + "sync" + + "github.com/consensys/gnark/logger" + icicle_runtime "github.com/ingonyama-zk/icicle/v3/wrappers/golang/runtime" +) + +var onceWarmUpDevice sync.Once + +func warmUpDevice() { + onceWarmUpDevice.Do(func() { + log := logger.Logger() + err := icicle_runtime.LoadBackendFromEnvOrDefault() + if err != icicle_runtime.Success { + panic(fmt.Sprintf("ICICLE backend loading error: %s", err.AsString())) + } + device := icicle_runtime.CreateDevice("CUDA", 0) + log.Debug().Int32("id", device.Id).Str("type", device.GetDeviceType()).Msg("ICICLE device created") + icicle_runtime.RunOnDevice(&device, func(args ...any) { + err := icicle_runtime.WarmUpDevice() + if err != icicle_runtime.Success { + panic(fmt.Sprintf("ICICLE device warmup error: %s", err.AsString())) + } + }) + }) +} diff --git a/backend/groth16/bn254/icicle/doc.go b/backend/groth16/bn254/icicle/doc.go index a77a7fbde..3a662b35d 100644 --- a/backend/groth16/bn254/icicle/doc.go +++ b/backend/groth16/bn254/icicle/doc.go @@ -1,2 +1,2 @@ // Package icicle_bn254 implements ICICLE acceleration for BN254 Groth16 backend. -package icicle_bn254 +package icicle diff --git a/backend/groth16/bn254/icicle/icicle.go b/backend/groth16/bn254/icicle/icicle.go index 5b1b235d3..49133cd8b 100644 --- a/backend/groth16/bn254/icicle/icicle.go +++ b/backend/groth16/bn254/icicle/icicle.go @@ -1,19 +1,20 @@ //go:build icicle -package icicle_bn254 +package icicle import ( "fmt" "math/big" "math/bits" + "os" "time" - "unsafe" + "github.com/consensys/gnark-crypto/ecc" curve "github.com/consensys/gnark-crypto/ecc/bn254" "github.com/consensys/gnark-crypto/ecc/bn254/fp" "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/consensys/gnark-crypto/ecc/bn254/fr/fft" "github.com/consensys/gnark-crypto/ecc/bn254/fr/hash_to_field" - "github.com/consensys/gnark-crypto/ecc/bn254/fr/pedersen" "github.com/consensys/gnark/backend" groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" "github.com/consensys/gnark/backend/groth16/internal" @@ -23,33 +24,40 @@ import ( "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark/internal/utils" "github.com/consensys/gnark/logger" - iciclegnark "github.com/ingonyama-zk/iciclegnark/curves/bn254" + + icicle_core "github.com/ingonyama-zk/icicle/v3/wrappers/golang/core" + icicle_bn254 "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254" + icicle_g2 "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254/g2" + icicle_msm "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254/msm" + icicle_ntt "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254/ntt" + icicle_vecops "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254/vecOps" + icicle_runtime "github.com/ingonyama-zk/icicle/v3/wrappers/golang/runtime" + + fcs "github.com/consensys/gnark/frontend/cs" ) const HasIcicle = true -func (pk *ProvingKey) setupDevicePointers() error { +var isProfileMode bool + +func init() { + _, isProfileMode = os.LookupEnv("ICICLE_STEP_PROFILE") +} + +func (pk *ProvingKey) setupDevicePointers(device *icicle_runtime.Device) error { if pk.deviceInfo != nil { return nil } pk.deviceInfo = &deviceInfo{} - n := int(pk.Domain.Cardinality) - sizeBytes := n * fr.Bytes - - /************************* Start Domain Device Setup ***************************/ - copyCosetInvDone := make(chan unsafe.Pointer, 1) - copyCosetDone := make(chan unsafe.Pointer, 1) - copyDenDone := make(chan unsafe.Pointer, 1) - /************************* CosetTableInv ***************************/ - go iciclegnark.CopyToDevice(pk.Domain.CosetTableInv, sizeBytes, copyCosetInvDone) - - /************************* CosetTable ***************************/ - go iciclegnark.CopyToDevice(pk.Domain.CosetTable, sizeBytes, copyCosetDone) - + gen, err := fft.Generator(2 * pk.Domain.Cardinality) + if err != nil { + return fmt.Errorf("get fft generator: %w", err) + } /************************* Den ***************************/ + n := int(pk.Domain.Cardinality) var denI, oneI fr.Element oneI.SetOne() - denI.Exp(pk.Domain.FrMultiplicativeGen, big.NewInt(int64(pk.Domain.Cardinality))) + denI.Exp(gen, big.NewInt(int64(pk.Domain.Cardinality))) denI.Sub(&denI, &oneI).Inverse(&denI) log2SizeFloor := bits.Len(uint(n)) - 1 @@ -62,71 +70,165 @@ func (pk *ProvingKey) setupDevicePointers() error { denIcicleArr = append(denIcicleArr, denI) } - go iciclegnark.CopyToDevice(denIcicleArr, sizeBytes, copyDenDone) + copyDenDone := make(chan struct{}) + icicle_runtime.RunOnDevice(device, func(args ...any) { + denIcicleArrHost := (icicle_core.HostSlice[fr.Element])(denIcicleArr) + denIcicleArrHost.CopyToDevice(&pk.DenDevice, true) + if err := icicle_bn254.FromMontgomery(pk.DenDevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy den to device: %s", err.AsString())) + } + close(copyDenDone) + }) + + /************************* Init Domain Device ***************************/ + genBits := gen.Bits() + limbs := icicle_core.ConvertUint64ArrToUint32Arr(genBits[:]) + copy(pk.CosetGenerator[:], limbs[:fr.Limbs*2]) + var rouIcicle icicle_bn254.ScalarField + rouIcicle.FromLimbs(limbs) + + initDomain := make(chan struct{}) + icicle_runtime.RunOnDevice(device, func(args ...any) { + if e := icicle_ntt.InitDomain(rouIcicle, icicle_core.GetDefaultNTTInitDomainConfig()); e != icicle_runtime.Success { + panic(fmt.Sprintf("couldn't initialize domain: %s", e.AsString())) // TODO + } + close(initDomain) + }) - /************************* Twiddles and Twiddles Inv ***************************/ - twiddlesInv_d_gen, twddles_err := iciclegnark.GenerateTwiddleFactors(n, true) - if twddles_err != nil { - return twddles_err - } + /************************* End Init Domain Device ***************************/ + /************************* Start G1 Device Setup ***************************/ + /************************* A ***************************/ + copyADone := make(chan struct{}) + icicle_runtime.RunOnDevice(device, func(args ...any) { + g1AHost := (icicle_core.HostSlice[curve.G1Affine])(pk.G1.A) + g1AHost.CopyToDevice(&pk.G1Device.A, true) + if err := icicle_bn254.AffineFromMontgomery(pk.G1Device.A); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy A to device: %s", err.AsString())) + } + close(copyADone) + }) + /************************* B ***************************/ + copyBDone := make(chan struct{}) + icicle_runtime.RunOnDevice(device, func(args ...any) { + g1BHost := (icicle_core.HostSlice[curve.G1Affine])(pk.G1.B) + g1BHost.CopyToDevice(&pk.G1Device.B, true) + if err := icicle_bn254.AffineFromMontgomery(pk.G1Device.B); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy B to device: %s", err.AsString())) + } + close(copyBDone) + }) + /************************* K ***************************/ + copyKDone := make(chan struct{}) + icicle_runtime.RunOnDevice(device, func(args ...any) { + g1KHost := (icicle_core.HostSlice[curve.G1Affine])(pk.G1.K) + g1KHost.CopyToDevice(&pk.G1Device.K, true) + if err := icicle_bn254.AffineFromMontgomery(pk.G1Device.K); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy K to device: %s", err.AsString())) + } + close(copyKDone) + }) + /************************* Z ***************************/ + copyZDone := make(chan struct{}) + icicle_runtime.RunOnDevice(device, func(args ...any) { + g1ZHost := (icicle_core.HostSlice[curve.G1Affine])(pk.G1.Z) + g1ZHost.CopyToDevice(&pk.G1Device.Z, true) + err := icicle_bn254.AffineFromMontgomery(pk.G1Device.Z) + if err != icicle_runtime.Success { + panic(fmt.Sprintf("copy Z to device: %s", err.AsString())) + } + close(copyZDone) + }) + /************************* End G1 Device Setup ***************************/ + /************************* Start G2 Device Setup ***************************/ + copyG2BDone := make(chan struct{}) + icicle_runtime.RunOnDevice(device, func(args ...any) { + g2BHost := (icicle_core.HostSlice[curve.G2Affine])(pk.G2.B) + g2BHost.CopyToDevice(&pk.G2Device.B, true) + if err := icicle_g2.G2AffineFromMontgomery(pk.G2Device.B); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy G2 B to device: %s", err.AsString())) + } + close(copyG2BDone) + }) + /************************* End G2 Device Setup ***************************/ - twiddles_d_gen, twddles_err := iciclegnark.GenerateTwiddleFactors(n, false) - if twddles_err != nil { - return twddles_err - } + /************************* Commitment Keys Device Setup ***************************/ + + commitmentKeysDeviceDone := make(chan struct{}) + pk.CommitmentKeysDevice.Basis = make([]icicle_core.DeviceSlice, len(pk.CommitmentKeys)) + pk.CommitmentKeysDevice.BasisExpSigma = make([]icicle_core.DeviceSlice, len(pk.CommitmentKeys)) + icicle_runtime.RunOnDevice(device, func(args ...any) { + for i := range pk.CommitmentKeys { + commitmentKeyBasisHost := icicle_core.HostSliceFromElements(pk.CommitmentKeys[i].Basis) + commitmentKeyBasisExpSigmaHost := icicle_core.HostSliceFromElements(pk.CommitmentKeys[i].BasisExpSigma) + commitmentKeyBasisHost.CopyToDevice(&pk.CommitmentKeysDevice.Basis[i], true) + commitmentKeyBasisExpSigmaHost.CopyToDevice(&pk.CommitmentKeysDevice.BasisExpSigma[i], true) + } + close(commitmentKeysDeviceDone) + }) + /************************* End Commitment Keys Device Setup ***************************/ + + /************************* Wait for all data tranfsers ***************************/ + <-initDomain + <-copyDenDone + <-copyADone + <-copyBDone + <-copyKDone + <-copyZDone + <-copyG2BDone + <-commitmentKeysDeviceDone - /************************* End Domain Device Setup ***************************/ - pk.DomainDevice.Twiddles = twiddles_d_gen - pk.DomainDevice.TwiddlesInv = twiddlesInv_d_gen + return nil +} - pk.DomainDevice.CosetTableInv = <-copyCosetInvDone - pk.DomainDevice.CosetTable = <-copyCosetDone - pk.DenDevice = <-copyDenDone +func projectiveToGnarkAffine(p icicle_bn254.Projective) *curve.G1Affine { + px, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(p.X.ToBytesLittleEndian())) + py, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(p.Y.ToBytesLittleEndian())) + pz, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(p.Z.ToBytesLittleEndian())) - /************************* Start G1 Device Setup ***************************/ - /************************* A ***************************/ - pointsBytesA := len(pk.G1.A) * fp.Bytes * 2 - copyADone := make(chan unsafe.Pointer, 1) - go iciclegnark.CopyPointsToDevice(pk.G1.A, pointsBytesA, copyADone) // Make a function for points + var x, y, zInv fp.Element - /************************* B ***************************/ - pointsBytesB := len(pk.G1.B) * fp.Bytes * 2 - copyBDone := make(chan unsafe.Pointer, 1) - go iciclegnark.CopyPointsToDevice(pk.G1.B, pointsBytesB, copyBDone) // Make a function for points + zInv.Inverse(&pz) + x.Mul(&px, &zInv) + y.Mul(&py, &zInv) - /************************* K ***************************/ - var pointsNoInfinity []curve.G1Affine - for i, gnarkPoint := range pk.G1.K { - if gnarkPoint.IsInfinity() { - pk.InfinityPointIndicesK = append(pk.InfinityPointIndicesK, i) - } else { - pointsNoInfinity = append(pointsNoInfinity, gnarkPoint) - } - } + return &curve.G1Affine{X: x, Y: y} +} - pointsBytesK := len(pointsNoInfinity) * fp.Bytes * 2 - copyKDone := make(chan unsafe.Pointer, 1) - go iciclegnark.CopyPointsToDevice(pointsNoInfinity, pointsBytesK, copyKDone) // Make a function for points +func g1ProjectiveToG1Jac(p icicle_bn254.Projective) curve.G1Jac { + var p1 curve.G1Jac + p1.FromAffine(projectiveToGnarkAffine(p)) - /************************* Z ***************************/ - pointsBytesZ := len(pk.G1.Z) * fp.Bytes * 2 - copyZDone := make(chan unsafe.Pointer, 1) - go iciclegnark.CopyPointsToDevice(pk.G1.Z, pointsBytesZ, copyZDone) // Make a function for points + return p1 +} - /************************* End G1 Device Setup ***************************/ - pk.G1Device.A = <-copyADone - pk.G1Device.B = <-copyBDone - pk.G1Device.K = <-copyKDone - pk.G1Device.Z = <-copyZDone +func toGnarkE2(f icicle_g2.G2BaseField) curve.E2 { + bytes := f.ToBytesLittleEndian() + a0, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(bytes[:fp.Bytes])) + a1, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(bytes[fp.Bytes:])) + return curve.E2{ + A0: a0, + A1: a1, + } +} - /************************* Start G2 Device Setup ***************************/ - pointsBytesB2 := len(pk.G2.B) * fp.Bytes * 4 - copyG2BDone := make(chan unsafe.Pointer, 1) - go iciclegnark.CopyG2PointsToDevice(pk.G2.B, pointsBytesB2, copyG2BDone) // Make a function for points - pk.G2Device.B = <-copyG2BDone +func g2ProjectiveToG2Jac(p *icicle_g2.G2Projective) curve.G2Jac { + x := toGnarkE2(p.X) + y := toGnarkE2(p.Y) + z := toGnarkE2(p.Z) + var zSquared curve.E2 + zSquared.Mul(&z, &z) - /************************* End G2 Device Setup ***************************/ - return nil + var X curve.E2 + X.Mul(&x, &z) + + var Y curve.E2 + Y.Mul(&y, &zSquared) + + return curve.G2Jac{ + X: X, + Y: Y, + Z: z, + } } // Prove generates the proof of knowledge of a r1cs with full witness (secret + public part). @@ -142,9 +244,13 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b return groth16_bn254.Prove(r1cs, &pk.ProvingKey, fullWitness, opts...) } log := logger.Logger().With().Str("curve", r1cs.CurveID().String()).Str("acceleration", "icicle").Int("nbConstraints", r1cs.GetNbConstraints()).Str("backend", "groth16").Logger() + + device := icicle_runtime.CreateDevice("CUDA", 0) + if pk.deviceInfo == nil { log.Debug().Msg("precomputing proving key in GPU") - if err := pk.setupDevicePointers(); err != nil { + + if err := pk.setupDevicePointers(&device); err != nil { return nil, fmt.Errorf("setup device pointers: %w", err) } } @@ -156,42 +262,48 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b solverOpts := opt.SolverOpts[:len(opt.SolverOpts):len(opt.SolverOpts)] privateCommittedValues := make([][]fr.Element, len(commitmentInfo)) - for i := range commitmentInfo { - solverOpts = append(solverOpts, solver.OverrideHint(commitmentInfo[i].HintID, func(i int) solver.Hint { - return func(_ *big.Int, in []*big.Int, out []*big.Int) error { - privateCommittedValues[i] = make([]fr.Element, len(commitmentInfo[i].PrivateCommitted)) - hashed := in[:len(commitmentInfo[i].PublicAndCommitmentCommitted)] - committed := in[len(hashed):] - for j, inJ := range committed { - privateCommittedValues[i][j].SetBigInt(inJ) - } - - var err error - if proof.Commitments[i], err = pk.CommitmentKeys[i].Commit(privateCommittedValues[i]); err != nil { - return err - } + privateCommittedValuesDevice := make([]icicle_core.DeviceSlice, len(commitmentInfo)) + + // override hints + bsb22ID := solver.GetHintID(fcs.Bsb22CommitmentComputePlaceholder) + solverOpts = append(solverOpts, solver.OverrideHint(bsb22ID, func(_ *big.Int, in []*big.Int, out []*big.Int) error { + i := int(in[0].Int64()) + in = in[1:] + privateCommittedValues[i] = make([]fr.Element, len(commitmentInfo[i].PrivateCommitted)) + hashed := in[:len(commitmentInfo[i].PublicAndCommitmentCommitted)] + committed := in[+len(hashed):] + for j, inJ := range committed { + privateCommittedValues[i][j].SetBigInt(inJ) + } - opt.HashToFieldFn.Write(constraint.SerializeCommitment(proof.Commitments[i].Marshal(), hashed, (fr.Bits-1)/8+1)) - hashBts := opt.HashToFieldFn.Sum(nil) - opt.HashToFieldFn.Reset() - nbBuf := fr.Bytes - if opt.HashToFieldFn.Size() < fr.Bytes { - nbBuf = opt.HashToFieldFn.Size() - } - var res fr.Element - res.SetBytes(hashBts[:nbBuf]) - res.BigInt(out[0]) - return err + proofCommitmentIcicle := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) + ckBasisMsmDone := make(chan struct{}) + icicle_runtime.RunOnDevice(&device, func(args ...any) { + cfg := icicle_msm.GetDefaultMSMConfig() + cfg.AreBasesMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + privateCommittedValuesHost := icicle_core.HostSliceFromElements(privateCommittedValues[i]) + privateCommittedValuesHost.CopyToDevice(&privateCommittedValuesDevice[i], true) + if err := icicle_msm.Msm(privateCommittedValuesDevice[i], pk.CommitmentKeysDevice.Basis[i], &cfg, proofCommitmentIcicle); err != icicle_runtime.Success { + panic(fmt.Sprintf("commitment: %s", err.AsString())) } - }(i))) - } - - if r1cs.GkrInfo.Is() { - var gkrData cs.GkrSolvingData - solverOpts = append(solverOpts, - solver.OverrideHint(r1cs.GkrInfo.SolveHintID, cs.GkrSolveHint(r1cs.GkrInfo, &gkrData)), - solver.OverrideHint(r1cs.GkrInfo.ProveHintID, cs.GkrProveHint(r1cs.GkrInfo.HashName, &gkrData))) - } + close(ckBasisMsmDone) + }) + <-ckBasisMsmDone + proof.Commitments[i] = *projectiveToGnarkAffine(proofCommitmentIcicle[0]) + + opt.HashToFieldFn.Write(constraint.SerializeCommitment(proof.Commitments[i].Marshal(), hashed, (fr.Bits-1)/8+1)) + hashBts := opt.HashToFieldFn.Sum(nil) + opt.HashToFieldFn.Reset() + nbBuf := fr.Bytes + if opt.HashToFieldFn.Size() < fr.Bytes { + nbBuf = opt.HashToFieldFn.Size() + } + var res fr.Element + res.SetBytes(hashBts[:nbBuf]) + res.BigInt(out[0]) + return nil + })) _solution, err := r1cs.Solve(fullWitness, solverOpts...) if err != nil { @@ -202,33 +314,66 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b wireValues := []fr.Element(solution.W) start := time.Now() - + numCommitmentKeys := len(pk.CommitmentKeys) + poks := make([]curve.G1Affine, numCommitmentKeys) + + // if there are CommitmentKeys, run a batch MSM for pederson Proof of Knowledge + if numCommitmentKeys > 0 { + startPoKBatch := time.Now() + poksIcicle := make([]icicle_core.HostSlice[icicle_bn254.Projective], numCommitmentKeys) + for i := range poksIcicle { + poksIcicle[i] = make(icicle_core.HostSlice[icicle_bn254.Projective], 1) + } + ckBasisExpSigmaMsmBatchDone := make(chan struct{}) + icicle_runtime.RunOnDevice(&device, func(args ...any) { + cfg := icicle_msm.GetDefaultMSMConfig() + cfg.AreBasesMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + for i := range pk.CommitmentKeysDevice.BasisExpSigma { + if err := icicle_msm.Msm(privateCommittedValuesDevice[i], pk.CommitmentKeysDevice.BasisExpSigma[i], &cfg, poksIcicle[i]); err != icicle_runtime.Success { + panic(fmt.Sprintf("commitment POK: %s", err.AsString())) + } + } + close(ckBasisExpSigmaMsmBatchDone) + }) + <-ckBasisExpSigmaMsmBatchDone + for i := range pk.CommitmentKeys { + poks[i] = *projectiveToGnarkAffine(poksIcicle[i][0]) + } + if isProfileMode { + log.Debug().Dur("took", time.Since(startPoKBatch)).Msg("ICICLE Batch Proof of Knowledge") + } + } + // compute challenge for folding the PoKs from the commitments commitmentsSerialized := make([]byte, fr.Bytes*len(commitmentInfo)) for i := range commitmentInfo { copy(commitmentsSerialized[fr.Bytes*i:], wireValues[commitmentInfo[i].CommitmentIndex].Marshal()) } - - if proof.CommitmentPok, err = pedersen.BatchProve(pk.CommitmentKeys, privateCommittedValues, commitmentsSerialized); err != nil { + challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1) + if err != nil { + return nil, err + } + if _, err = proof.CommitmentPok.Fold(poks, challenge[0], ecc.MultiExpConfig{NbTasks: 1}); err != nil { return nil, err } - // H (witness reduction / FFT part) - var h unsafe.Pointer - chHDone := make(chan struct{}, 1) - go func() { - h = computeH(solution.A, solution.B, solution.C, pk) + var h icicle_core.DeviceSlice + chHDone := make(chan struct{}) + icicle_runtime.RunOnDevice(&device, func(args ...any) { + h = computeH(solution.A, solution.B, solution.C, pk, &device) + solution.A = nil solution.B = nil solution.C = nil - chHDone <- struct{}{} - }() + close(chHDone) + }) // we need to copy and filter the wireValues for each multi exp // as pk.G1.A, pk.G1.B and pk.G2.B may have (a significant) number of point at infinity - var wireValuesADevice, wireValuesBDevice iciclegnark.OnDeviceData - chWireValuesA, chWireValuesB := make(chan struct{}, 1), make(chan struct{}, 1) + var wireValuesADevice, wireValuesBDevice icicle_core.DeviceSlice + chWireValuesA, chWireValuesB := make(chan struct{}), make(chan struct{}) - go func() { + icicle_runtime.RunOnDevice(&device, func(args ...any) { wireValuesA := make([]fr.Element, len(wireValues)-int(pk.NbInfinityA)) for i, j := 0, 0; j < len(wireValuesA); i++ { if pk.InfinityA[i] { @@ -237,22 +382,18 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b wireValuesA[j] = wireValues[i] j++ } - wireValuesASize := len(wireValuesA) - scalarBytes := wireValuesASize * fr.Bytes // Copy scalars to the device and retain ptr to them - copyDone := make(chan unsafe.Pointer, 1) - iciclegnark.CopyToDevice(wireValuesA, scalarBytes, copyDone) - wireValuesADevicePtr := <-copyDone - - wireValuesADevice = iciclegnark.OnDeviceData{ - P: wireValuesADevicePtr, - Size: wireValuesASize, + wireValuesAHost := (icicle_core.HostSlice[fr.Element])(wireValuesA) + wireValuesAHost.CopyToDevice(&wireValuesADevice, true) + if err := icicle_bn254.FromMontgomery(wireValuesADevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy A to device: %s", err.AsString())) } close(chWireValuesA) - }() - go func() { + }) + + icicle_runtime.RunOnDevice(&device, func(args ...any) { wireValuesB := make([]fr.Element, len(wireValues)-int(pk.NbInfinityB)) for i, j := 0, 0; j < len(wireValuesB); i++ { if pk.InfinityB[i] { @@ -261,21 +402,16 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b wireValuesB[j] = wireValues[i] j++ } - wireValuesBSize := len(wireValuesB) - scalarBytes := wireValuesBSize * fr.Bytes // Copy scalars to the device and retain ptr to them - copyDone := make(chan unsafe.Pointer, 1) - iciclegnark.CopyToDevice(wireValuesB, scalarBytes, copyDone) - wireValuesBDevicePtr := <-copyDone - - wireValuesBDevice = iciclegnark.OnDeviceData{ - P: wireValuesBDevicePtr, - Size: wireValuesBSize, + wireValuesBHost := (icicle_core.HostSlice[fr.Element])(wireValuesB) + wireValuesBHost.CopyToDevice(&wireValuesBDevice, true) + if err := icicle_bn254.FromMontgomery(wireValuesBDevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy B to device: %s", err.AsString())) } close(chWireValuesB) - }() + }) // sample random r and s var r, s big.Int @@ -295,74 +431,91 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b deltas := curve.BatchScalarMultiplicationG1(&pk.G1.Delta, []fr.Element{_r, _s, _kr}) var bs1, ar curve.G1Jac + chArDone, chBs1Done := make(chan struct{}), make(chan struct{}) computeBS1 := func() error { <-chWireValuesB - if bs1, _, err = iciclegnark.MsmOnDevice(wireValuesBDevice.P, pk.G1Device.B, wireValuesBDevice.Size, true); err != nil { - return err + cfg := icicle_msm.GetDefaultMSMConfig() + res := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) + start := time.Now() + if err := icicle_msm.Msm(wireValuesBDevice, pk.G1Device.B, &cfg, res); err != icicle_runtime.Success { + panic(fmt.Sprintf("msm Bs1: %s", err.AsString())) + } + + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("MSM Bs1") } + bs1 = g1ProjectiveToG1Jac(res[0]) bs1.AddMixed(&pk.G1.Beta) bs1.AddMixed(&deltas[1]) + close(chBs1Done) return nil } computeAR1 := func() error { <-chWireValuesA - if ar, _, err = iciclegnark.MsmOnDevice(wireValuesADevice.P, pk.G1Device.A, wireValuesADevice.Size, true); err != nil { - return err + cfg := icicle_msm.GetDefaultMSMConfig() + res := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) + start := time.Now() + if err := icicle_msm.Msm(wireValuesADevice, pk.G1Device.A, &cfg, res); err != icicle_runtime.Success { + panic(fmt.Sprintf("msm Ar1: %s", err.AsString())) + } + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("MSM Ar1") } + ar = g1ProjectiveToG1Jac(res[0]) ar.AddMixed(&pk.G1.Alpha) ar.AddMixed(&deltas[0]) proof.Ar.FromJacobian(&ar) + close(chArDone) return nil } computeKRS := func() error { var krs, krs2, p1 curve.G1Jac - sizeH := int(pk.Domain.Cardinality - 1) // comes from the fact the deg(H)=(n-1)+(n-1)-n=n-2 + sizeH := int(pk.Domain.Cardinality - 1) - // check for small circuits as iciclegnark doesn't handle zero sizes well - if len(pk.G1.Z) > 0 { - if krs2, _, err = iciclegnark.MsmOnDevice(h, pk.G1Device.Z, sizeH, true); err != nil { - return err - } + cfg := icicle_msm.GetDefaultMSMConfig() + resKrs2 := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) + start := time.Now() + if err := icicle_msm.Msm(h.RangeTo(sizeH, false), pk.G1Device.Z, &cfg, resKrs2); err != icicle_runtime.Success { + panic(fmt.Sprintf("msm Krs2: %s", err.AsString())) + } + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("MSM Krs2") } + krs2 = g1ProjectiveToG1Jac(resKrs2[0]) // filter the wire values if needed // TODO Perf @Tabaie worst memory allocation offender toRemove := commitmentInfo.GetPrivateCommitted() toRemove = append(toRemove, commitmentInfo.CommitmentIndexes()) - scalars := filterHeap(wireValues[r1cs.GetNbPublicVariables():], r1cs.GetNbPublicVariables(), internal.ConcatAll(toRemove...)) - - // filter zero/infinity points since icicle doesn't handle them - // See https://github.com/ingonyama-zk/icicle/issues/169 for more info - for _, indexToRemove := range pk.InfinityPointIndicesK { - scalars = append(scalars[:indexToRemove], scalars[indexToRemove+1:]...) + _wireValues := filterHeap(wireValues[r1cs.GetNbPublicVariables():], r1cs.GetNbPublicVariables(), internal.ConcatAll(toRemove...)) + _wireValuesHost := (icicle_core.HostSlice[fr.Element])(_wireValues) + resKrs := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) + cfg.AreScalarsMontgomeryForm = true + start = time.Now() + if err := icicle_msm.Msm(_wireValuesHost, pk.G1Device.K, &cfg, resKrs); err != icicle_runtime.Success { + panic(fmt.Sprintf("msm Krs: %s", err.AsString())) } - - scalarBytes := len(scalars) * fr.Bytes - - copyDone := make(chan unsafe.Pointer, 1) - iciclegnark.CopyToDevice(scalars, scalarBytes, copyDone) - scalars_d := <-copyDone - - krs, _, err = iciclegnark.MsmOnDevice(scalars_d, pk.G1Device.K, len(scalars), true) - iciclegnark.FreeDevicePointer(scalars_d) - - if err != nil { - return err + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("MSM Krs") } + krs = g1ProjectiveToG1Jac(resKrs[0]) krs.AddMixed(&deltas[2]) krs.AddAssign(&krs2) + <-chArDone + <-chBs1Done + p1.ScalarMultiplication(&ar, &s) krs.AddAssign(&p1) @@ -379,9 +532,17 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b var Bs, deltaS curve.G2Jac <-chWireValuesB - if Bs, _, err = iciclegnark.MsmG2OnDevice(wireValuesBDevice.P, pk.G2Device.B, wireValuesBDevice.Size, true); err != nil { - return err + + cfg := icicle_g2.G2GetDefaultMSMConfig() + res := make(icicle_core.HostSlice[icicle_g2.G2Projective], 1) + start := time.Now() + if err := icicle_g2.G2Msm(wireValuesBDevice, pk.G2Device.B, &cfg, res); err != icicle_runtime.Success { + panic(fmt.Sprintf("msm Bs2: %s", err.AsString())) + } + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("MSM Bs2 G2") } + Bs = g2ProjectiveToG2Jac(&res[0]) deltaS.FromAffine(&pk.G2.Delta) deltaS.ScalarMultiplication(&deltaS, &s) @@ -392,31 +553,51 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b return nil } + // schedule our proof part computations + icicle_runtime.RunOnDevice(&device, func(args ...any) { + if err := computeAR1(); err != nil { + panic(fmt.Sprintf("compute AR1: %v", err)) + } + }) + + icicle_runtime.RunOnDevice(&device, func(args ...any) { + if err := computeBS1(); err != nil { + panic(fmt.Sprintf("compute BS1: %v", err)) + } + }) + + icicle_runtime.RunOnDevice(&device, func(args ...any) { + if err := computeBS2(); err != nil { + panic(fmt.Sprintf("compute BS2: %v", err)) + } + }) + // wait for FFT to end <-chHDone - // schedule our proof part computations - if err := computeAR1(); err != nil { - return nil, err - } - if err := computeBS1(); err != nil { - return nil, err - } - if err := computeKRS(); err != nil { - return nil, err - } - if err := computeBS2(); err != nil { - return nil, err - } + computeKrsDone := make(chan struct{}) + icicle_runtime.RunOnDevice(&device, func(args ...any) { + if err := computeKRS(); err != nil { + panic(fmt.Sprintf("compute KRS: %v", err)) + } + close(computeKrsDone) + }) + <-computeKrsDone log.Debug().Dur("took", time.Since(start)).Msg("prover done") // free device/GPU memory that is not needed for future proofs (scalars/hpoly) - go func() { - iciclegnark.FreeDevicePointer(wireValuesADevice.P) - iciclegnark.FreeDevicePointer(wireValuesBDevice.P) - iciclegnark.FreeDevicePointer(h) - }() + icicle_runtime.RunOnDevice(&device, func(args ...any) { + if err := wireValuesADevice.Free(); err != icicle_runtime.Success { + log.Error().Msgf("free wireValuesADevice failed: %s", err.AsString()) + } + if err := wireValuesBDevice.Free(); err != icicle_runtime.Success { + log.Error().Msgf("free wireValuesBDevice failed: %s", err.AsString()) + } + if err := h.Free(); err != icicle_runtime.Success { + log.Error().Msgf("free h failed: %s", err.AsString()) + } + }) return proof, nil } @@ -450,13 +631,14 @@ func filterHeap(slice []fr.Element, sliceFirstIndex int, toRemove []int) (r []fr return } -func computeH(a, b, c []fr.Element, pk *ProvingKey) unsafe.Pointer { +func computeH(a, b, c []fr.Element, pk *ProvingKey, device *icicle_runtime.Device) icicle_core.DeviceSlice { // H part of Krs // Compute H (hz=ab-c, where z=-2 on ker X^n+1 (z(x)=x^n-1)) // 1 - _a = ifft(a), _b = ifft(b), _c = ifft(c) // 2 - ca = fft_coset(_a), ba = fft_coset(_b), cc = fft_coset(_c) // 3 - h = ifft_coset(ca o cb - cc) - + log := logger.Logger() + startTotal := time.Now() n := len(a) // add padding to ensure input length is domain cardinality @@ -466,48 +648,83 @@ func computeH(a, b, c []fr.Element, pk *ProvingKey) unsafe.Pointer { c = append(c, padding...) n = len(a) - sizeBytes := n * fr.Bytes - - /*********** Copy a,b,c to Device Start ************/ - // Individual channels are necessary to know which device pointers - // point to which vector - copyADone := make(chan unsafe.Pointer, 1) - copyBDone := make(chan unsafe.Pointer, 1) - copyCDone := make(chan unsafe.Pointer, 1) - - go iciclegnark.CopyToDevice(a, sizeBytes, copyADone) - go iciclegnark.CopyToDevice(b, sizeBytes, copyBDone) - go iciclegnark.CopyToDevice(c, sizeBytes, copyCDone) - - a_device := <-copyADone - b_device := <-copyBDone - c_device := <-copyCDone - /*********** Copy a,b,c to Device End ************/ - - computeInttNttDone := make(chan error, 1) - computeInttNttOnDevice := func(devicePointer unsafe.Pointer) { - a_intt_d := iciclegnark.INttOnDevice(devicePointer, pk.DomainDevice.TwiddlesInv, nil, n, sizeBytes, false) - iciclegnark.NttOnDevice(devicePointer, a_intt_d, pk.DomainDevice.Twiddles, pk.DomainDevice.CosetTable, n, n, sizeBytes, true) - computeInttNttDone <- nil - iciclegnark.FreeDevicePointer(a_intt_d) + computeADone := make(chan icicle_core.DeviceSlice) + computeBDone := make(chan icicle_core.DeviceSlice) + computeCDone := make(chan icicle_core.DeviceSlice) + + computeInttNttOnDevice := func(args ...any) { + var scalars []fr.Element = args[0].([]fr.Element) + var channel chan icicle_core.DeviceSlice = args[1].(chan icicle_core.DeviceSlice) + + cfg := icicle_ntt.GetDefaultNttConfig() + scalarsStream, _ := icicle_runtime.CreateStream() + cfg.StreamHandle = scalarsStream + cfg.Ordering = icicle_core.KNM + cfg.IsAsync = true + scalarsHost := icicle_core.HostSliceFromElements(scalars) + var scalarsDevice icicle_core.DeviceSlice + scalarsHost.CopyToDeviceAsync(&scalarsDevice, scalarsStream, true) + start := time.Now() + icicle_ntt.Ntt(scalarsDevice, icicle_core.KInverse, &cfg, scalarsDevice) + cfg.Ordering = icicle_core.KMN + cfg.CosetGen = pk.CosetGenerator + icicle_ntt.Ntt(scalarsDevice, icicle_core.KForward, &cfg, scalarsDevice) + icicle_runtime.SynchronizeStream(scalarsStream) + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("computeH: NTT + INTT") + } + channel <- scalarsDevice + close(channel) } - go computeInttNttOnDevice(a_device) - go computeInttNttOnDevice(b_device) - go computeInttNttOnDevice(c_device) - _, _, _ = <-computeInttNttDone, <-computeInttNttDone, <-computeInttNttDone + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, a, computeADone) + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, b, computeBDone) + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, c, computeCDone) - iciclegnark.PolyOps(a_device, b_device, c_device, pk.DenDevice, n) + aDevice := <-computeADone + bDevice := <-computeBDone + cDevice := <-computeCDone - h := iciclegnark.INttOnDevice(a_device, pk.DomainDevice.TwiddlesInv, pk.DomainDevice.CosetTableInv, n, sizeBytes, true) - - go func() { - iciclegnark.FreeDevicePointer(a_device) - iciclegnark.FreeDevicePointer(b_device) - iciclegnark.FreeDevicePointer(c_device) - }() - - iciclegnark.ReverseScalars(h, n) + // The following does not need to be run in a RunOnDevice call because + // computeH is being run inside a RunOnDevice call and the following is not + // being run in a different goroutine unlike the calls above to + // computeInttNttOnDevice which are running in different goroutines + vecCfg := icicle_core.DefaultVecOpsConfig() + start := time.Now() + if err := icicle_bn254.FromMontgomery(aDevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("fromMontgomery a in computeH: %s", err.AsString())) + } + if err := icicle_vecops.VecOp(aDevice, bDevice, aDevice, vecCfg, icicle_core.Mul); err != icicle_runtime.Success { + panic(fmt.Sprintf("mul a b in computeH: %s", err.AsString())) + } + if err := icicle_vecops.VecOp(aDevice, cDevice, aDevice, vecCfg, icicle_core.Sub); err != icicle_runtime.Success { + panic(fmt.Sprintf("sub a c in computeH: %s", err.AsString())) + } + if err := icicle_vecops.VecOp(aDevice, pk.DenDevice, aDevice, vecCfg, icicle_core.Mul); err != icicle_runtime.Success { + panic(fmt.Sprintf("mul a den in computeH: %s", err.AsString())) + } + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("computeH: vecOps") + } + defer bDevice.Free() + defer cDevice.Free() + + cfg := icicle_ntt.GetDefaultNttConfig() + cfg.CosetGen = pk.CosetGenerator + cfg.Ordering = icicle_core.KNR + start = time.Now() + if err := icicle_ntt.Ntt(aDevice, icicle_core.KInverse, &cfg, aDevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("ntt a in computeH: %s", err.AsString())) + } + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("computeH: INTT final") + } + if err := icicle_bn254.FromMontgomery(aDevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("fromMontgomery a in computeH: %s", err.AsString())) + } - return h + if isProfileMode { + log.Debug().Dur("took", time.Since(startTotal)).Msg("computeH: Total") + } + return aDevice } diff --git a/backend/groth16/bn254/icicle/marshal_test.go b/backend/groth16/bn254/icicle/marshal_test.go index 75c5a2b57..114765daf 100644 --- a/backend/groth16/bn254/icicle/marshal_test.go +++ b/backend/groth16/bn254/icicle/marshal_test.go @@ -1,10 +1,13 @@ -package icicle_bn254_test +//go:build icicle + +package icicle_test import ( "bytes" "testing" "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend" "github.com/consensys/gnark/backend/groth16" groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" icicle_bn254 "github.com/consensys/gnark/backend/groth16/bn254/icicle" @@ -43,6 +46,20 @@ func TestMarshal(t *testing.T) { if pk.IsDifferent(&nativePK) { t.Error("marshal output difference") } + + assignment := circuit{A: 3, B: 5, Res: 15} + w, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) + assert.NoError(err) + pw, err := w.Public() + assert.NoError(err) + proofNative, err := groth16_bn254.Prove(tCcs, &nativePK, w) + assert.NoError(err) + proofIcicle, err := groth16.Prove(tCcs, pk, w, backend.WithIcicleAcceleration()) + assert.NoError(err) + err = groth16.Verify(proofNative, &nativeVK, pw) + assert.NoError(err) + err = groth16.Verify(proofIcicle, &nativeVK, pw) + assert.NoError(err) } func TestMarshal2(t *testing.T) { @@ -64,4 +81,18 @@ func TestMarshal2(t *testing.T) { if iciPK.IsDifferent(&nativePK) { t.Error("marshal output difference") } + + assignment := circuit{A: 3, B: 5, Res: 15} + w, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) + assert.NoError(err) + pw, err := w.Public() + assert.NoError(err) + proofNative, err := groth16_bn254.Prove(tCcs, &nativePK, w) + assert.NoError(err) + proofIcicle, err := groth16.Prove(tCcs, &iciPK, w, backend.WithIcicleAcceleration()) + assert.NoError(err) + err = groth16.Verify(proofNative, &iciVK, pw) + assert.NoError(err) + err = groth16.Verify(proofIcicle, &iciVK, pw) + assert.NoError(err) } diff --git a/backend/groth16/bn254/icicle/noicicle.go b/backend/groth16/bn254/icicle/noicicle.go index 87703339c..5925dd0b1 100644 --- a/backend/groth16/bn254/icicle/noicicle.go +++ b/backend/groth16/bn254/icicle/noicicle.go @@ -1,6 +1,6 @@ //go:build !icicle -package icicle_bn254 +package icicle import ( "fmt" diff --git a/backend/groth16/bn254/icicle/provingkey.go b/backend/groth16/bn254/icicle/provingkey.go index 146a79425..ee9d17057 100644 --- a/backend/groth16/bn254/icicle/provingkey.go +++ b/backend/groth16/bn254/icicle/provingkey.go @@ -1,25 +1,26 @@ -package icicle_bn254 +package icicle import ( - "unsafe" - + "github.com/consensys/gnark-crypto/ecc/bn254/fr" groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" cs "github.com/consensys/gnark/constraint/bn254" + icicle_core "github.com/ingonyama-zk/icicle/v3/wrappers/golang/core" ) type deviceInfo struct { - G1Device struct { - A, B, K, Z unsafe.Pointer - } - DomainDevice struct { - Twiddles, TwiddlesInv unsafe.Pointer - CosetTable, CosetTableInv unsafe.Pointer + CosetGenerator [fr.Limbs * 2]uint32 + G1Device struct { + A, B, K, Z icicle_core.DeviceSlice } G2Device struct { - B unsafe.Pointer + B icicle_core.DeviceSlice + } + DenDevice icicle_core.DeviceSlice + + CommitmentKeysDevice struct { + Basis []icicle_core.DeviceSlice + BasisExpSigma []icicle_core.DeviceSlice // we compute in batch } - DenDevice unsafe.Pointer - InfinityPointIndicesK []int } type ProvingKey struct { @@ -27,10 +28,17 @@ type ProvingKey struct { *deviceInfo } +func NewProvingKey() *ProvingKey { + warmUpDevice() + return &ProvingKey{} +} + func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *groth16_bn254.VerifyingKey) error { + warmUpDevice() return groth16_bn254.Setup(r1cs, &pk.ProvingKey, vk) } func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { + warmUpDevice() return groth16_bn254.DummySetup(r1cs, &pk.ProvingKey) } diff --git a/backend/groth16/groth16.go b/backend/groth16/groth16.go index 426621e2f..aa27ef3f7 100644 --- a/backend/groth16/groth16.go +++ b/backend/groth16/groth16.go @@ -347,7 +347,7 @@ func NewProvingKey(curveID ecc.ID) ProvingKey { case ecc.BN254: pk = &groth16_bn254.ProvingKey{} if icicle_bn254.HasIcicle { - pk = &icicle_bn254.ProvingKey{} + pk = icicle_bn254.NewProvingKey() } case ecc.BLS12_377: pk = &groth16_bls12377.ProvingKey{} diff --git a/go.mod b/go.mod index 6e0371490..a71f63c3c 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 github.com/icza/bitio v1.1.0 - github.com/ingonyama-zk/iciclegnark v0.1.0 + github.com/ingonyama-zk/icicle/v3 v3.1.1-0.20241118092657-fccdb2f0921b github.com/leanovate/gopter v0.2.11 github.com/ronanh/intcomp v1.1.0 github.com/rs/zerolog v1.33.0 @@ -26,12 +26,10 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/ingonyama-zk/icicle v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/sys v0.28.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 5a48cfc66..4676a0a14 100644 --- a/go.sum +++ b/go.sum @@ -184,10 +184,8 @@ github.com/icza/bitio v1.1.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6 h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k= github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/ingonyama-zk/icicle v1.1.0 h1:a2MUIaF+1i4JY2Lnb961ZMvaC8GFs9GqZgSnd9e95C8= -github.com/ingonyama-zk/icicle v1.1.0/go.mod h1:kAK8/EoN7fUEmakzgZIYdWy1a2rBnpCaZLqSHwZWxEk= -github.com/ingonyama-zk/iciclegnark v0.1.0 h1:88MkEghzjQBMjrYRJFxZ9oR9CTIpB8NG2zLeCJSvXKQ= -github.com/ingonyama-zk/iciclegnark v0.1.0/go.mod h1:wz6+IpyHKs6UhMMoQpNqz1VY+ddfKqC/gRwR/64W6WU= +github.com/ingonyama-zk/icicle/v3 v3.1.1-0.20241118092657-fccdb2f0921b h1:AvQTK7l0PTHODD06PVQX1Tn2o29sRIaKIDOvTJmKurY= +github.com/ingonyama-zk/icicle/v3 v3.1.1-0.20241118092657-fccdb2f0921b/go.mod h1:e0JHb27/P6WorCJS3YolbY5XffS4PGBuoW38OthLkDs= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= From 9154785015cc6b8b028416d9a9c7767188dc6327 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 16 Dec 2024 10:58:50 +0000 Subject: [PATCH 06/12] Revert "Update ICICLE integration to use v3 ICICLE (#1318)" This reverts commit b04a6884de8de776f2d3803ec9f81892a8ae5886. Build fails when dependencies not installed. --- README.md | 6 +- backend/groth16/bn254/icicle/device.go | 29 - backend/groth16/bn254/icicle/doc.go | 2 +- backend/groth16/bn254/icicle/icicle.go | 677 +++++++------------ backend/groth16/bn254/icicle/marshal_test.go | 33 +- backend/groth16/bn254/icicle/noicicle.go | 2 +- backend/groth16/bn254/icicle/provingkey.go | 32 +- backend/groth16/groth16.go | 2 +- go.mod | 4 +- go.sum | 6 +- 10 files changed, 256 insertions(+), 537 deletions(-) delete mode 100644 backend/groth16/bn254/icicle/device.go diff --git a/README.md b/README.md index 02c008f95..229630ff9 100644 --- a/README.md +++ b/README.md @@ -162,9 +162,9 @@ func main() { ### GPU Support -#### ICICLE Library +#### Icicle Library -The following schemes and curves support experimental use of Ingonyama's ICICLE GPU library for low level zk-SNARK primitives such as MSM, NTT, and polynomial operations: +The following schemes and curves support experimental use of Ingonyama's Icicle GPU library for low level zk-SNARK primitives such as MSM, NTT, and polynomial operations: - [x] [Groth16](https://eprint.iacr.org/2016/260) @@ -184,7 +184,7 @@ You can then toggle on or off icicle acceleration by providing the `WithIcicleAc proof, err := groth16.Prove(ccs, pk, secretWitness) ``` -For more information about prerequisites see the [ICICLE repo](https://github.com/ingonyama-zk/icicle). **NB! ICICLE CUDA kernels are covered by a special license for now. Follow the instructions to download and set up the kernels.** +For more information about prerequisites see the [Icicle repo](https://github.com/ingonyama-zk/icicle). ## Citing diff --git a/backend/groth16/bn254/icicle/device.go b/backend/groth16/bn254/icicle/device.go deleted file mode 100644 index d6d86ea36..000000000 --- a/backend/groth16/bn254/icicle/device.go +++ /dev/null @@ -1,29 +0,0 @@ -package icicle - -import ( - "fmt" - "sync" - - "github.com/consensys/gnark/logger" - icicle_runtime "github.com/ingonyama-zk/icicle/v3/wrappers/golang/runtime" -) - -var onceWarmUpDevice sync.Once - -func warmUpDevice() { - onceWarmUpDevice.Do(func() { - log := logger.Logger() - err := icicle_runtime.LoadBackendFromEnvOrDefault() - if err != icicle_runtime.Success { - panic(fmt.Sprintf("ICICLE backend loading error: %s", err.AsString())) - } - device := icicle_runtime.CreateDevice("CUDA", 0) - log.Debug().Int32("id", device.Id).Str("type", device.GetDeviceType()).Msg("ICICLE device created") - icicle_runtime.RunOnDevice(&device, func(args ...any) { - err := icicle_runtime.WarmUpDevice() - if err != icicle_runtime.Success { - panic(fmt.Sprintf("ICICLE device warmup error: %s", err.AsString())) - } - }) - }) -} diff --git a/backend/groth16/bn254/icicle/doc.go b/backend/groth16/bn254/icicle/doc.go index 3a662b35d..a77a7fbde 100644 --- a/backend/groth16/bn254/icicle/doc.go +++ b/backend/groth16/bn254/icicle/doc.go @@ -1,2 +1,2 @@ // Package icicle_bn254 implements ICICLE acceleration for BN254 Groth16 backend. -package icicle +package icicle_bn254 diff --git a/backend/groth16/bn254/icicle/icicle.go b/backend/groth16/bn254/icicle/icicle.go index 49133cd8b..5b1b235d3 100644 --- a/backend/groth16/bn254/icicle/icicle.go +++ b/backend/groth16/bn254/icicle/icicle.go @@ -1,20 +1,19 @@ //go:build icicle -package icicle +package icicle_bn254 import ( "fmt" "math/big" "math/bits" - "os" "time" + "unsafe" - "github.com/consensys/gnark-crypto/ecc" curve "github.com/consensys/gnark-crypto/ecc/bn254" "github.com/consensys/gnark-crypto/ecc/bn254/fp" "github.com/consensys/gnark-crypto/ecc/bn254/fr" - "github.com/consensys/gnark-crypto/ecc/bn254/fr/fft" "github.com/consensys/gnark-crypto/ecc/bn254/fr/hash_to_field" + "github.com/consensys/gnark-crypto/ecc/bn254/fr/pedersen" "github.com/consensys/gnark/backend" groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" "github.com/consensys/gnark/backend/groth16/internal" @@ -24,40 +23,33 @@ import ( "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark/internal/utils" "github.com/consensys/gnark/logger" - - icicle_core "github.com/ingonyama-zk/icicle/v3/wrappers/golang/core" - icicle_bn254 "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254" - icicle_g2 "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254/g2" - icicle_msm "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254/msm" - icicle_ntt "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254/ntt" - icicle_vecops "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254/vecOps" - icicle_runtime "github.com/ingonyama-zk/icicle/v3/wrappers/golang/runtime" - - fcs "github.com/consensys/gnark/frontend/cs" + iciclegnark "github.com/ingonyama-zk/iciclegnark/curves/bn254" ) const HasIcicle = true -var isProfileMode bool - -func init() { - _, isProfileMode = os.LookupEnv("ICICLE_STEP_PROFILE") -} - -func (pk *ProvingKey) setupDevicePointers(device *icicle_runtime.Device) error { +func (pk *ProvingKey) setupDevicePointers() error { if pk.deviceInfo != nil { return nil } pk.deviceInfo = &deviceInfo{} - gen, err := fft.Generator(2 * pk.Domain.Cardinality) - if err != nil { - return fmt.Errorf("get fft generator: %w", err) - } - /************************* Den ***************************/ n := int(pk.Domain.Cardinality) + sizeBytes := n * fr.Bytes + + /************************* Start Domain Device Setup ***************************/ + copyCosetInvDone := make(chan unsafe.Pointer, 1) + copyCosetDone := make(chan unsafe.Pointer, 1) + copyDenDone := make(chan unsafe.Pointer, 1) + /************************* CosetTableInv ***************************/ + go iciclegnark.CopyToDevice(pk.Domain.CosetTableInv, sizeBytes, copyCosetInvDone) + + /************************* CosetTable ***************************/ + go iciclegnark.CopyToDevice(pk.Domain.CosetTable, sizeBytes, copyCosetDone) + + /************************* Den ***************************/ var denI, oneI fr.Element oneI.SetOne() - denI.Exp(gen, big.NewInt(int64(pk.Domain.Cardinality))) + denI.Exp(pk.Domain.FrMultiplicativeGen, big.NewInt(int64(pk.Domain.Cardinality))) denI.Sub(&denI, &oneI).Inverse(&denI) log2SizeFloor := bits.Len(uint(n)) - 1 @@ -70,165 +62,71 @@ func (pk *ProvingKey) setupDevicePointers(device *icicle_runtime.Device) error { denIcicleArr = append(denIcicleArr, denI) } - copyDenDone := make(chan struct{}) - icicle_runtime.RunOnDevice(device, func(args ...any) { - denIcicleArrHost := (icicle_core.HostSlice[fr.Element])(denIcicleArr) - denIcicleArrHost.CopyToDevice(&pk.DenDevice, true) - if err := icicle_bn254.FromMontgomery(pk.DenDevice); err != icicle_runtime.Success { - panic(fmt.Sprintf("copy den to device: %s", err.AsString())) - } - close(copyDenDone) - }) - - /************************* Init Domain Device ***************************/ - genBits := gen.Bits() - limbs := icicle_core.ConvertUint64ArrToUint32Arr(genBits[:]) - copy(pk.CosetGenerator[:], limbs[:fr.Limbs*2]) - var rouIcicle icicle_bn254.ScalarField - rouIcicle.FromLimbs(limbs) - - initDomain := make(chan struct{}) - icicle_runtime.RunOnDevice(device, func(args ...any) { - if e := icicle_ntt.InitDomain(rouIcicle, icicle_core.GetDefaultNTTInitDomainConfig()); e != icicle_runtime.Success { - panic(fmt.Sprintf("couldn't initialize domain: %s", e.AsString())) // TODO - } - close(initDomain) - }) - - /************************* End Init Domain Device ***************************/ - /************************* Start G1 Device Setup ***************************/ - /************************* A ***************************/ - copyADone := make(chan struct{}) - icicle_runtime.RunOnDevice(device, func(args ...any) { - g1AHost := (icicle_core.HostSlice[curve.G1Affine])(pk.G1.A) - g1AHost.CopyToDevice(&pk.G1Device.A, true) - if err := icicle_bn254.AffineFromMontgomery(pk.G1Device.A); err != icicle_runtime.Success { - panic(fmt.Sprintf("copy A to device: %s", err.AsString())) - } - close(copyADone) - }) - /************************* B ***************************/ - copyBDone := make(chan struct{}) - icicle_runtime.RunOnDevice(device, func(args ...any) { - g1BHost := (icicle_core.HostSlice[curve.G1Affine])(pk.G1.B) - g1BHost.CopyToDevice(&pk.G1Device.B, true) - if err := icicle_bn254.AffineFromMontgomery(pk.G1Device.B); err != icicle_runtime.Success { - panic(fmt.Sprintf("copy B to device: %s", err.AsString())) - } - close(copyBDone) - }) - /************************* K ***************************/ - copyKDone := make(chan struct{}) - icicle_runtime.RunOnDevice(device, func(args ...any) { - g1KHost := (icicle_core.HostSlice[curve.G1Affine])(pk.G1.K) - g1KHost.CopyToDevice(&pk.G1Device.K, true) - if err := icicle_bn254.AffineFromMontgomery(pk.G1Device.K); err != icicle_runtime.Success { - panic(fmt.Sprintf("copy K to device: %s", err.AsString())) - } - close(copyKDone) - }) - /************************* Z ***************************/ - copyZDone := make(chan struct{}) - icicle_runtime.RunOnDevice(device, func(args ...any) { - g1ZHost := (icicle_core.HostSlice[curve.G1Affine])(pk.G1.Z) - g1ZHost.CopyToDevice(&pk.G1Device.Z, true) - err := icicle_bn254.AffineFromMontgomery(pk.G1Device.Z) - if err != icicle_runtime.Success { - panic(fmt.Sprintf("copy Z to device: %s", err.AsString())) - } - close(copyZDone) - }) - /************************* End G1 Device Setup ***************************/ - /************************* Start G2 Device Setup ***************************/ - copyG2BDone := make(chan struct{}) - icicle_runtime.RunOnDevice(device, func(args ...any) { - g2BHost := (icicle_core.HostSlice[curve.G2Affine])(pk.G2.B) - g2BHost.CopyToDevice(&pk.G2Device.B, true) - if err := icicle_g2.G2AffineFromMontgomery(pk.G2Device.B); err != icicle_runtime.Success { - panic(fmt.Sprintf("copy G2 B to device: %s", err.AsString())) - } - close(copyG2BDone) - }) - /************************* End G2 Device Setup ***************************/ - - /************************* Commitment Keys Device Setup ***************************/ - - commitmentKeysDeviceDone := make(chan struct{}) - pk.CommitmentKeysDevice.Basis = make([]icicle_core.DeviceSlice, len(pk.CommitmentKeys)) - pk.CommitmentKeysDevice.BasisExpSigma = make([]icicle_core.DeviceSlice, len(pk.CommitmentKeys)) - icicle_runtime.RunOnDevice(device, func(args ...any) { - for i := range pk.CommitmentKeys { - commitmentKeyBasisHost := icicle_core.HostSliceFromElements(pk.CommitmentKeys[i].Basis) - commitmentKeyBasisExpSigmaHost := icicle_core.HostSliceFromElements(pk.CommitmentKeys[i].BasisExpSigma) - commitmentKeyBasisHost.CopyToDevice(&pk.CommitmentKeysDevice.Basis[i], true) - commitmentKeyBasisExpSigmaHost.CopyToDevice(&pk.CommitmentKeysDevice.BasisExpSigma[i], true) - } - close(commitmentKeysDeviceDone) - }) - /************************* End Commitment Keys Device Setup ***************************/ - - /************************* Wait for all data tranfsers ***************************/ - <-initDomain - <-copyDenDone - <-copyADone - <-copyBDone - <-copyKDone - <-copyZDone - <-copyG2BDone - <-commitmentKeysDeviceDone - - return nil -} + go iciclegnark.CopyToDevice(denIcicleArr, sizeBytes, copyDenDone) -func projectiveToGnarkAffine(p icicle_bn254.Projective) *curve.G1Affine { - px, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(p.X.ToBytesLittleEndian())) - py, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(p.Y.ToBytesLittleEndian())) - pz, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(p.Z.ToBytesLittleEndian())) + /************************* Twiddles and Twiddles Inv ***************************/ + twiddlesInv_d_gen, twddles_err := iciclegnark.GenerateTwiddleFactors(n, true) + if twddles_err != nil { + return twddles_err + } - var x, y, zInv fp.Element + twiddles_d_gen, twddles_err := iciclegnark.GenerateTwiddleFactors(n, false) + if twddles_err != nil { + return twddles_err + } - zInv.Inverse(&pz) - x.Mul(&px, &zInv) - y.Mul(&py, &zInv) + /************************* End Domain Device Setup ***************************/ + pk.DomainDevice.Twiddles = twiddles_d_gen + pk.DomainDevice.TwiddlesInv = twiddlesInv_d_gen - return &curve.G1Affine{X: x, Y: y} -} + pk.DomainDevice.CosetTableInv = <-copyCosetInvDone + pk.DomainDevice.CosetTable = <-copyCosetDone + pk.DenDevice = <-copyDenDone -func g1ProjectiveToG1Jac(p icicle_bn254.Projective) curve.G1Jac { - var p1 curve.G1Jac - p1.FromAffine(projectiveToGnarkAffine(p)) + /************************* Start G1 Device Setup ***************************/ + /************************* A ***************************/ + pointsBytesA := len(pk.G1.A) * fp.Bytes * 2 + copyADone := make(chan unsafe.Pointer, 1) + go iciclegnark.CopyPointsToDevice(pk.G1.A, pointsBytesA, copyADone) // Make a function for points - return p1 -} + /************************* B ***************************/ + pointsBytesB := len(pk.G1.B) * fp.Bytes * 2 + copyBDone := make(chan unsafe.Pointer, 1) + go iciclegnark.CopyPointsToDevice(pk.G1.B, pointsBytesB, copyBDone) // Make a function for points -func toGnarkE2(f icicle_g2.G2BaseField) curve.E2 { - bytes := f.ToBytesLittleEndian() - a0, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(bytes[:fp.Bytes])) - a1, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(bytes[fp.Bytes:])) - return curve.E2{ - A0: a0, - A1: a1, + /************************* K ***************************/ + var pointsNoInfinity []curve.G1Affine + for i, gnarkPoint := range pk.G1.K { + if gnarkPoint.IsInfinity() { + pk.InfinityPointIndicesK = append(pk.InfinityPointIndicesK, i) + } else { + pointsNoInfinity = append(pointsNoInfinity, gnarkPoint) + } } -} -func g2ProjectiveToG2Jac(p *icicle_g2.G2Projective) curve.G2Jac { - x := toGnarkE2(p.X) - y := toGnarkE2(p.Y) - z := toGnarkE2(p.Z) - var zSquared curve.E2 - zSquared.Mul(&z, &z) + pointsBytesK := len(pointsNoInfinity) * fp.Bytes * 2 + copyKDone := make(chan unsafe.Pointer, 1) + go iciclegnark.CopyPointsToDevice(pointsNoInfinity, pointsBytesK, copyKDone) // Make a function for points - var X curve.E2 - X.Mul(&x, &z) + /************************* Z ***************************/ + pointsBytesZ := len(pk.G1.Z) * fp.Bytes * 2 + copyZDone := make(chan unsafe.Pointer, 1) + go iciclegnark.CopyPointsToDevice(pk.G1.Z, pointsBytesZ, copyZDone) // Make a function for points - var Y curve.E2 - Y.Mul(&y, &zSquared) + /************************* End G1 Device Setup ***************************/ + pk.G1Device.A = <-copyADone + pk.G1Device.B = <-copyBDone + pk.G1Device.K = <-copyKDone + pk.G1Device.Z = <-copyZDone - return curve.G2Jac{ - X: X, - Y: Y, - Z: z, - } + /************************* Start G2 Device Setup ***************************/ + pointsBytesB2 := len(pk.G2.B) * fp.Bytes * 4 + copyG2BDone := make(chan unsafe.Pointer, 1) + go iciclegnark.CopyG2PointsToDevice(pk.G2.B, pointsBytesB2, copyG2BDone) // Make a function for points + pk.G2Device.B = <-copyG2BDone + + /************************* End G2 Device Setup ***************************/ + return nil } // Prove generates the proof of knowledge of a r1cs with full witness (secret + public part). @@ -244,13 +142,9 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b return groth16_bn254.Prove(r1cs, &pk.ProvingKey, fullWitness, opts...) } log := logger.Logger().With().Str("curve", r1cs.CurveID().String()).Str("acceleration", "icicle").Int("nbConstraints", r1cs.GetNbConstraints()).Str("backend", "groth16").Logger() - - device := icicle_runtime.CreateDevice("CUDA", 0) - if pk.deviceInfo == nil { log.Debug().Msg("precomputing proving key in GPU") - - if err := pk.setupDevicePointers(&device); err != nil { + if err := pk.setupDevicePointers(); err != nil { return nil, fmt.Errorf("setup device pointers: %w", err) } } @@ -262,48 +156,42 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b solverOpts := opt.SolverOpts[:len(opt.SolverOpts):len(opt.SolverOpts)] privateCommittedValues := make([][]fr.Element, len(commitmentInfo)) - privateCommittedValuesDevice := make([]icicle_core.DeviceSlice, len(commitmentInfo)) - - // override hints - bsb22ID := solver.GetHintID(fcs.Bsb22CommitmentComputePlaceholder) - solverOpts = append(solverOpts, solver.OverrideHint(bsb22ID, func(_ *big.Int, in []*big.Int, out []*big.Int) error { - i := int(in[0].Int64()) - in = in[1:] - privateCommittedValues[i] = make([]fr.Element, len(commitmentInfo[i].PrivateCommitted)) - hashed := in[:len(commitmentInfo[i].PublicAndCommitmentCommitted)] - committed := in[+len(hashed):] - for j, inJ := range committed { - privateCommittedValues[i][j].SetBigInt(inJ) - } + for i := range commitmentInfo { + solverOpts = append(solverOpts, solver.OverrideHint(commitmentInfo[i].HintID, func(i int) solver.Hint { + return func(_ *big.Int, in []*big.Int, out []*big.Int) error { + privateCommittedValues[i] = make([]fr.Element, len(commitmentInfo[i].PrivateCommitted)) + hashed := in[:len(commitmentInfo[i].PublicAndCommitmentCommitted)] + committed := in[len(hashed):] + for j, inJ := range committed { + privateCommittedValues[i][j].SetBigInt(inJ) + } + + var err error + if proof.Commitments[i], err = pk.CommitmentKeys[i].Commit(privateCommittedValues[i]); err != nil { + return err + } - proofCommitmentIcicle := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) - ckBasisMsmDone := make(chan struct{}) - icicle_runtime.RunOnDevice(&device, func(args ...any) { - cfg := icicle_msm.GetDefaultMSMConfig() - cfg.AreBasesMontgomeryForm = true - cfg.AreScalarsMontgomeryForm = true - privateCommittedValuesHost := icicle_core.HostSliceFromElements(privateCommittedValues[i]) - privateCommittedValuesHost.CopyToDevice(&privateCommittedValuesDevice[i], true) - if err := icicle_msm.Msm(privateCommittedValuesDevice[i], pk.CommitmentKeysDevice.Basis[i], &cfg, proofCommitmentIcicle); err != icicle_runtime.Success { - panic(fmt.Sprintf("commitment: %s", err.AsString())) + opt.HashToFieldFn.Write(constraint.SerializeCommitment(proof.Commitments[i].Marshal(), hashed, (fr.Bits-1)/8+1)) + hashBts := opt.HashToFieldFn.Sum(nil) + opt.HashToFieldFn.Reset() + nbBuf := fr.Bytes + if opt.HashToFieldFn.Size() < fr.Bytes { + nbBuf = opt.HashToFieldFn.Size() + } + var res fr.Element + res.SetBytes(hashBts[:nbBuf]) + res.BigInt(out[0]) + return err } - close(ckBasisMsmDone) - }) - <-ckBasisMsmDone - proof.Commitments[i] = *projectiveToGnarkAffine(proofCommitmentIcicle[0]) - - opt.HashToFieldFn.Write(constraint.SerializeCommitment(proof.Commitments[i].Marshal(), hashed, (fr.Bits-1)/8+1)) - hashBts := opt.HashToFieldFn.Sum(nil) - opt.HashToFieldFn.Reset() - nbBuf := fr.Bytes - if opt.HashToFieldFn.Size() < fr.Bytes { - nbBuf = opt.HashToFieldFn.Size() - } - var res fr.Element - res.SetBytes(hashBts[:nbBuf]) - res.BigInt(out[0]) - return nil - })) + }(i))) + } + + if r1cs.GkrInfo.Is() { + var gkrData cs.GkrSolvingData + solverOpts = append(solverOpts, + solver.OverrideHint(r1cs.GkrInfo.SolveHintID, cs.GkrSolveHint(r1cs.GkrInfo, &gkrData)), + solver.OverrideHint(r1cs.GkrInfo.ProveHintID, cs.GkrProveHint(r1cs.GkrInfo.HashName, &gkrData))) + } _solution, err := r1cs.Solve(fullWitness, solverOpts...) if err != nil { @@ -314,66 +202,33 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b wireValues := []fr.Element(solution.W) start := time.Now() - numCommitmentKeys := len(pk.CommitmentKeys) - poks := make([]curve.G1Affine, numCommitmentKeys) - - // if there are CommitmentKeys, run a batch MSM for pederson Proof of Knowledge - if numCommitmentKeys > 0 { - startPoKBatch := time.Now() - poksIcicle := make([]icicle_core.HostSlice[icicle_bn254.Projective], numCommitmentKeys) - for i := range poksIcicle { - poksIcicle[i] = make(icicle_core.HostSlice[icicle_bn254.Projective], 1) - } - ckBasisExpSigmaMsmBatchDone := make(chan struct{}) - icicle_runtime.RunOnDevice(&device, func(args ...any) { - cfg := icicle_msm.GetDefaultMSMConfig() - cfg.AreBasesMontgomeryForm = true - cfg.AreScalarsMontgomeryForm = true - for i := range pk.CommitmentKeysDevice.BasisExpSigma { - if err := icicle_msm.Msm(privateCommittedValuesDevice[i], pk.CommitmentKeysDevice.BasisExpSigma[i], &cfg, poksIcicle[i]); err != icicle_runtime.Success { - panic(fmt.Sprintf("commitment POK: %s", err.AsString())) - } - } - close(ckBasisExpSigmaMsmBatchDone) - }) - <-ckBasisExpSigmaMsmBatchDone - for i := range pk.CommitmentKeys { - poks[i] = *projectiveToGnarkAffine(poksIcicle[i][0]) - } - if isProfileMode { - log.Debug().Dur("took", time.Since(startPoKBatch)).Msg("ICICLE Batch Proof of Knowledge") - } - } - // compute challenge for folding the PoKs from the commitments + commitmentsSerialized := make([]byte, fr.Bytes*len(commitmentInfo)) for i := range commitmentInfo { copy(commitmentsSerialized[fr.Bytes*i:], wireValues[commitmentInfo[i].CommitmentIndex].Marshal()) } - challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1) - if err != nil { - return nil, err - } - if _, err = proof.CommitmentPok.Fold(poks, challenge[0], ecc.MultiExpConfig{NbTasks: 1}); err != nil { + + if proof.CommitmentPok, err = pedersen.BatchProve(pk.CommitmentKeys, privateCommittedValues, commitmentsSerialized); err != nil { return nil, err } - // H (witness reduction / FFT part) - var h icicle_core.DeviceSlice - chHDone := make(chan struct{}) - icicle_runtime.RunOnDevice(&device, func(args ...any) { - h = computeH(solution.A, solution.B, solution.C, pk, &device) + // H (witness reduction / FFT part) + var h unsafe.Pointer + chHDone := make(chan struct{}, 1) + go func() { + h = computeH(solution.A, solution.B, solution.C, pk) solution.A = nil solution.B = nil solution.C = nil - close(chHDone) - }) + chHDone <- struct{}{} + }() // we need to copy and filter the wireValues for each multi exp // as pk.G1.A, pk.G1.B and pk.G2.B may have (a significant) number of point at infinity - var wireValuesADevice, wireValuesBDevice icicle_core.DeviceSlice - chWireValuesA, chWireValuesB := make(chan struct{}), make(chan struct{}) + var wireValuesADevice, wireValuesBDevice iciclegnark.OnDeviceData + chWireValuesA, chWireValuesB := make(chan struct{}, 1), make(chan struct{}, 1) - icicle_runtime.RunOnDevice(&device, func(args ...any) { + go func() { wireValuesA := make([]fr.Element, len(wireValues)-int(pk.NbInfinityA)) for i, j := 0, 0; j < len(wireValuesA); i++ { if pk.InfinityA[i] { @@ -382,18 +237,22 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b wireValuesA[j] = wireValues[i] j++ } + wireValuesASize := len(wireValuesA) + scalarBytes := wireValuesASize * fr.Bytes // Copy scalars to the device and retain ptr to them - wireValuesAHost := (icicle_core.HostSlice[fr.Element])(wireValuesA) - wireValuesAHost.CopyToDevice(&wireValuesADevice, true) - if err := icicle_bn254.FromMontgomery(wireValuesADevice); err != icicle_runtime.Success { - panic(fmt.Sprintf("copy A to device: %s", err.AsString())) + copyDone := make(chan unsafe.Pointer, 1) + iciclegnark.CopyToDevice(wireValuesA, scalarBytes, copyDone) + wireValuesADevicePtr := <-copyDone + + wireValuesADevice = iciclegnark.OnDeviceData{ + P: wireValuesADevicePtr, + Size: wireValuesASize, } close(chWireValuesA) - }) - - icicle_runtime.RunOnDevice(&device, func(args ...any) { + }() + go func() { wireValuesB := make([]fr.Element, len(wireValues)-int(pk.NbInfinityB)) for i, j := 0, 0; j < len(wireValuesB); i++ { if pk.InfinityB[i] { @@ -402,16 +261,21 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b wireValuesB[j] = wireValues[i] j++ } + wireValuesBSize := len(wireValuesB) + scalarBytes := wireValuesBSize * fr.Bytes // Copy scalars to the device and retain ptr to them - wireValuesBHost := (icicle_core.HostSlice[fr.Element])(wireValuesB) - wireValuesBHost.CopyToDevice(&wireValuesBDevice, true) - if err := icicle_bn254.FromMontgomery(wireValuesBDevice); err != icicle_runtime.Success { - panic(fmt.Sprintf("copy B to device: %s", err.AsString())) + copyDone := make(chan unsafe.Pointer, 1) + iciclegnark.CopyToDevice(wireValuesB, scalarBytes, copyDone) + wireValuesBDevicePtr := <-copyDone + + wireValuesBDevice = iciclegnark.OnDeviceData{ + P: wireValuesBDevicePtr, + Size: wireValuesBSize, } close(chWireValuesB) - }) + }() // sample random r and s var r, s big.Int @@ -431,91 +295,74 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b deltas := curve.BatchScalarMultiplicationG1(&pk.G1.Delta, []fr.Element{_r, _s, _kr}) var bs1, ar curve.G1Jac - chArDone, chBs1Done := make(chan struct{}), make(chan struct{}) computeBS1 := func() error { <-chWireValuesB - cfg := icicle_msm.GetDefaultMSMConfig() - res := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) - start := time.Now() - if err := icicle_msm.Msm(wireValuesBDevice, pk.G1Device.B, &cfg, res); err != icicle_runtime.Success { - panic(fmt.Sprintf("msm Bs1: %s", err.AsString())) - } - - if isProfileMode { - log.Debug().Dur("took", time.Since(start)).Msg("MSM Bs1") + if bs1, _, err = iciclegnark.MsmOnDevice(wireValuesBDevice.P, pk.G1Device.B, wireValuesBDevice.Size, true); err != nil { + return err } - bs1 = g1ProjectiveToG1Jac(res[0]) bs1.AddMixed(&pk.G1.Beta) bs1.AddMixed(&deltas[1]) - close(chBs1Done) return nil } computeAR1 := func() error { <-chWireValuesA - cfg := icicle_msm.GetDefaultMSMConfig() - res := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) - start := time.Now() - if err := icicle_msm.Msm(wireValuesADevice, pk.G1Device.A, &cfg, res); err != icicle_runtime.Success { - panic(fmt.Sprintf("msm Ar1: %s", err.AsString())) - } - if isProfileMode { - log.Debug().Dur("took", time.Since(start)).Msg("MSM Ar1") + if ar, _, err = iciclegnark.MsmOnDevice(wireValuesADevice.P, pk.G1Device.A, wireValuesADevice.Size, true); err != nil { + return err } - ar = g1ProjectiveToG1Jac(res[0]) ar.AddMixed(&pk.G1.Alpha) ar.AddMixed(&deltas[0]) proof.Ar.FromJacobian(&ar) - close(chArDone) return nil } computeKRS := func() error { var krs, krs2, p1 curve.G1Jac - sizeH := int(pk.Domain.Cardinality - 1) + sizeH := int(pk.Domain.Cardinality - 1) // comes from the fact the deg(H)=(n-1)+(n-1)-n=n-2 - cfg := icicle_msm.GetDefaultMSMConfig() - resKrs2 := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) - start := time.Now() - if err := icicle_msm.Msm(h.RangeTo(sizeH, false), pk.G1Device.Z, &cfg, resKrs2); err != icicle_runtime.Success { - panic(fmt.Sprintf("msm Krs2: %s", err.AsString())) - } - if isProfileMode { - log.Debug().Dur("took", time.Since(start)).Msg("MSM Krs2") + // check for small circuits as iciclegnark doesn't handle zero sizes well + if len(pk.G1.Z) > 0 { + if krs2, _, err = iciclegnark.MsmOnDevice(h, pk.G1Device.Z, sizeH, true); err != nil { + return err + } } - krs2 = g1ProjectiveToG1Jac(resKrs2[0]) // filter the wire values if needed // TODO Perf @Tabaie worst memory allocation offender toRemove := commitmentInfo.GetPrivateCommitted() toRemove = append(toRemove, commitmentInfo.CommitmentIndexes()) - _wireValues := filterHeap(wireValues[r1cs.GetNbPublicVariables():], r1cs.GetNbPublicVariables(), internal.ConcatAll(toRemove...)) - _wireValuesHost := (icicle_core.HostSlice[fr.Element])(_wireValues) - resKrs := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) - cfg.AreScalarsMontgomeryForm = true - start = time.Now() - if err := icicle_msm.Msm(_wireValuesHost, pk.G1Device.K, &cfg, resKrs); err != icicle_runtime.Success { - panic(fmt.Sprintf("msm Krs: %s", err.AsString())) + scalars := filterHeap(wireValues[r1cs.GetNbPublicVariables():], r1cs.GetNbPublicVariables(), internal.ConcatAll(toRemove...)) + + // filter zero/infinity points since icicle doesn't handle them + // See https://github.com/ingonyama-zk/icicle/issues/169 for more info + for _, indexToRemove := range pk.InfinityPointIndicesK { + scalars = append(scalars[:indexToRemove], scalars[indexToRemove+1:]...) } - if isProfileMode { - log.Debug().Dur("took", time.Since(start)).Msg("MSM Krs") + + scalarBytes := len(scalars) * fr.Bytes + + copyDone := make(chan unsafe.Pointer, 1) + iciclegnark.CopyToDevice(scalars, scalarBytes, copyDone) + scalars_d := <-copyDone + + krs, _, err = iciclegnark.MsmOnDevice(scalars_d, pk.G1Device.K, len(scalars), true) + iciclegnark.FreeDevicePointer(scalars_d) + + if err != nil { + return err } - krs = g1ProjectiveToG1Jac(resKrs[0]) krs.AddMixed(&deltas[2]) krs.AddAssign(&krs2) - <-chArDone - <-chBs1Done - p1.ScalarMultiplication(&ar, &s) krs.AddAssign(&p1) @@ -532,17 +379,9 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b var Bs, deltaS curve.G2Jac <-chWireValuesB - - cfg := icicle_g2.G2GetDefaultMSMConfig() - res := make(icicle_core.HostSlice[icicle_g2.G2Projective], 1) - start := time.Now() - if err := icicle_g2.G2Msm(wireValuesBDevice, pk.G2Device.B, &cfg, res); err != icicle_runtime.Success { - panic(fmt.Sprintf("msm Bs2: %s", err.AsString())) - } - if isProfileMode { - log.Debug().Dur("took", time.Since(start)).Msg("MSM Bs2 G2") + if Bs, _, err = iciclegnark.MsmG2OnDevice(wireValuesBDevice.P, pk.G2Device.B, wireValuesBDevice.Size, true); err != nil { + return err } - Bs = g2ProjectiveToG2Jac(&res[0]) deltaS.FromAffine(&pk.G2.Delta) deltaS.ScalarMultiplication(&deltaS, &s) @@ -553,51 +392,31 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b return nil } - // schedule our proof part computations - icicle_runtime.RunOnDevice(&device, func(args ...any) { - if err := computeAR1(); err != nil { - panic(fmt.Sprintf("compute AR1: %v", err)) - } - }) - - icicle_runtime.RunOnDevice(&device, func(args ...any) { - if err := computeBS1(); err != nil { - panic(fmt.Sprintf("compute BS1: %v", err)) - } - }) - - icicle_runtime.RunOnDevice(&device, func(args ...any) { - if err := computeBS2(); err != nil { - panic(fmt.Sprintf("compute BS2: %v", err)) - } - }) - // wait for FFT to end <-chHDone - computeKrsDone := make(chan struct{}) - icicle_runtime.RunOnDevice(&device, func(args ...any) { - if err := computeKRS(); err != nil { - panic(fmt.Sprintf("compute KRS: %v", err)) - } - close(computeKrsDone) - }) - <-computeKrsDone + // schedule our proof part computations + if err := computeAR1(); err != nil { + return nil, err + } + if err := computeBS1(); err != nil { + return nil, err + } + if err := computeKRS(); err != nil { + return nil, err + } + if err := computeBS2(); err != nil { + return nil, err + } log.Debug().Dur("took", time.Since(start)).Msg("prover done") // free device/GPU memory that is not needed for future proofs (scalars/hpoly) - icicle_runtime.RunOnDevice(&device, func(args ...any) { - if err := wireValuesADevice.Free(); err != icicle_runtime.Success { - log.Error().Msgf("free wireValuesADevice failed: %s", err.AsString()) - } - if err := wireValuesBDevice.Free(); err != icicle_runtime.Success { - log.Error().Msgf("free wireValuesBDevice failed: %s", err.AsString()) - } - if err := h.Free(); err != icicle_runtime.Success { - log.Error().Msgf("free h failed: %s", err.AsString()) - } - }) + go func() { + iciclegnark.FreeDevicePointer(wireValuesADevice.P) + iciclegnark.FreeDevicePointer(wireValuesBDevice.P) + iciclegnark.FreeDevicePointer(h) + }() return proof, nil } @@ -631,14 +450,13 @@ func filterHeap(slice []fr.Element, sliceFirstIndex int, toRemove []int) (r []fr return } -func computeH(a, b, c []fr.Element, pk *ProvingKey, device *icicle_runtime.Device) icicle_core.DeviceSlice { +func computeH(a, b, c []fr.Element, pk *ProvingKey) unsafe.Pointer { // H part of Krs // Compute H (hz=ab-c, where z=-2 on ker X^n+1 (z(x)=x^n-1)) // 1 - _a = ifft(a), _b = ifft(b), _c = ifft(c) // 2 - ca = fft_coset(_a), ba = fft_coset(_b), cc = fft_coset(_c) // 3 - h = ifft_coset(ca o cb - cc) - log := logger.Logger() - startTotal := time.Now() + n := len(a) // add padding to ensure input length is domain cardinality @@ -648,83 +466,48 @@ func computeH(a, b, c []fr.Element, pk *ProvingKey, device *icicle_runtime.Devic c = append(c, padding...) n = len(a) - computeADone := make(chan icicle_core.DeviceSlice) - computeBDone := make(chan icicle_core.DeviceSlice) - computeCDone := make(chan icicle_core.DeviceSlice) - - computeInttNttOnDevice := func(args ...any) { - var scalars []fr.Element = args[0].([]fr.Element) - var channel chan icicle_core.DeviceSlice = args[1].(chan icicle_core.DeviceSlice) - - cfg := icicle_ntt.GetDefaultNttConfig() - scalarsStream, _ := icicle_runtime.CreateStream() - cfg.StreamHandle = scalarsStream - cfg.Ordering = icicle_core.KNM - cfg.IsAsync = true - scalarsHost := icicle_core.HostSliceFromElements(scalars) - var scalarsDevice icicle_core.DeviceSlice - scalarsHost.CopyToDeviceAsync(&scalarsDevice, scalarsStream, true) - start := time.Now() - icicle_ntt.Ntt(scalarsDevice, icicle_core.KInverse, &cfg, scalarsDevice) - cfg.Ordering = icicle_core.KMN - cfg.CosetGen = pk.CosetGenerator - icicle_ntt.Ntt(scalarsDevice, icicle_core.KForward, &cfg, scalarsDevice) - icicle_runtime.SynchronizeStream(scalarsStream) - if isProfileMode { - log.Debug().Dur("took", time.Since(start)).Msg("computeH: NTT + INTT") - } - channel <- scalarsDevice - close(channel) - } + sizeBytes := n * fr.Bytes - icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, a, computeADone) - icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, b, computeBDone) - icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, c, computeCDone) + /*********** Copy a,b,c to Device Start ************/ + // Individual channels are necessary to know which device pointers + // point to which vector + copyADone := make(chan unsafe.Pointer, 1) + copyBDone := make(chan unsafe.Pointer, 1) + copyCDone := make(chan unsafe.Pointer, 1) - aDevice := <-computeADone - bDevice := <-computeBDone - cDevice := <-computeCDone + go iciclegnark.CopyToDevice(a, sizeBytes, copyADone) + go iciclegnark.CopyToDevice(b, sizeBytes, copyBDone) + go iciclegnark.CopyToDevice(c, sizeBytes, copyCDone) - // The following does not need to be run in a RunOnDevice call because - // computeH is being run inside a RunOnDevice call and the following is not - // being run in a different goroutine unlike the calls above to - // computeInttNttOnDevice which are running in different goroutines - vecCfg := icicle_core.DefaultVecOpsConfig() - start := time.Now() - if err := icicle_bn254.FromMontgomery(aDevice); err != icicle_runtime.Success { - panic(fmt.Sprintf("fromMontgomery a in computeH: %s", err.AsString())) - } - if err := icicle_vecops.VecOp(aDevice, bDevice, aDevice, vecCfg, icicle_core.Mul); err != icicle_runtime.Success { - panic(fmt.Sprintf("mul a b in computeH: %s", err.AsString())) - } - if err := icicle_vecops.VecOp(aDevice, cDevice, aDevice, vecCfg, icicle_core.Sub); err != icicle_runtime.Success { - panic(fmt.Sprintf("sub a c in computeH: %s", err.AsString())) - } - if err := icicle_vecops.VecOp(aDevice, pk.DenDevice, aDevice, vecCfg, icicle_core.Mul); err != icicle_runtime.Success { - panic(fmt.Sprintf("mul a den in computeH: %s", err.AsString())) - } - if isProfileMode { - log.Debug().Dur("took", time.Since(start)).Msg("computeH: vecOps") - } - defer bDevice.Free() - defer cDevice.Free() - - cfg := icicle_ntt.GetDefaultNttConfig() - cfg.CosetGen = pk.CosetGenerator - cfg.Ordering = icicle_core.KNR - start = time.Now() - if err := icicle_ntt.Ntt(aDevice, icicle_core.KInverse, &cfg, aDevice); err != icicle_runtime.Success { - panic(fmt.Sprintf("ntt a in computeH: %s", err.AsString())) - } - if isProfileMode { - log.Debug().Dur("took", time.Since(start)).Msg("computeH: INTT final") - } - if err := icicle_bn254.FromMontgomery(aDevice); err != icicle_runtime.Success { - panic(fmt.Sprintf("fromMontgomery a in computeH: %s", err.AsString())) - } + a_device := <-copyADone + b_device := <-copyBDone + c_device := <-copyCDone + /*********** Copy a,b,c to Device End ************/ - if isProfileMode { - log.Debug().Dur("took", time.Since(startTotal)).Msg("computeH: Total") + computeInttNttDone := make(chan error, 1) + computeInttNttOnDevice := func(devicePointer unsafe.Pointer) { + a_intt_d := iciclegnark.INttOnDevice(devicePointer, pk.DomainDevice.TwiddlesInv, nil, n, sizeBytes, false) + iciclegnark.NttOnDevice(devicePointer, a_intt_d, pk.DomainDevice.Twiddles, pk.DomainDevice.CosetTable, n, n, sizeBytes, true) + computeInttNttDone <- nil + iciclegnark.FreeDevicePointer(a_intt_d) } - return aDevice + + go computeInttNttOnDevice(a_device) + go computeInttNttOnDevice(b_device) + go computeInttNttOnDevice(c_device) + _, _, _ = <-computeInttNttDone, <-computeInttNttDone, <-computeInttNttDone + + iciclegnark.PolyOps(a_device, b_device, c_device, pk.DenDevice, n) + + h := iciclegnark.INttOnDevice(a_device, pk.DomainDevice.TwiddlesInv, pk.DomainDevice.CosetTableInv, n, sizeBytes, true) + + go func() { + iciclegnark.FreeDevicePointer(a_device) + iciclegnark.FreeDevicePointer(b_device) + iciclegnark.FreeDevicePointer(c_device) + }() + + iciclegnark.ReverseScalars(h, n) + + return h } diff --git a/backend/groth16/bn254/icicle/marshal_test.go b/backend/groth16/bn254/icicle/marshal_test.go index 114765daf..75c5a2b57 100644 --- a/backend/groth16/bn254/icicle/marshal_test.go +++ b/backend/groth16/bn254/icicle/marshal_test.go @@ -1,13 +1,10 @@ -//go:build icicle - -package icicle_test +package icicle_bn254_test import ( "bytes" "testing" "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark/backend" "github.com/consensys/gnark/backend/groth16" groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" icicle_bn254 "github.com/consensys/gnark/backend/groth16/bn254/icicle" @@ -46,20 +43,6 @@ func TestMarshal(t *testing.T) { if pk.IsDifferent(&nativePK) { t.Error("marshal output difference") } - - assignment := circuit{A: 3, B: 5, Res: 15} - w, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) - assert.NoError(err) - pw, err := w.Public() - assert.NoError(err) - proofNative, err := groth16_bn254.Prove(tCcs, &nativePK, w) - assert.NoError(err) - proofIcicle, err := groth16.Prove(tCcs, pk, w, backend.WithIcicleAcceleration()) - assert.NoError(err) - err = groth16.Verify(proofNative, &nativeVK, pw) - assert.NoError(err) - err = groth16.Verify(proofIcicle, &nativeVK, pw) - assert.NoError(err) } func TestMarshal2(t *testing.T) { @@ -81,18 +64,4 @@ func TestMarshal2(t *testing.T) { if iciPK.IsDifferent(&nativePK) { t.Error("marshal output difference") } - - assignment := circuit{A: 3, B: 5, Res: 15} - w, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) - assert.NoError(err) - pw, err := w.Public() - assert.NoError(err) - proofNative, err := groth16_bn254.Prove(tCcs, &nativePK, w) - assert.NoError(err) - proofIcicle, err := groth16.Prove(tCcs, &iciPK, w, backend.WithIcicleAcceleration()) - assert.NoError(err) - err = groth16.Verify(proofNative, &iciVK, pw) - assert.NoError(err) - err = groth16.Verify(proofIcicle, &iciVK, pw) - assert.NoError(err) } diff --git a/backend/groth16/bn254/icicle/noicicle.go b/backend/groth16/bn254/icicle/noicicle.go index 5925dd0b1..87703339c 100644 --- a/backend/groth16/bn254/icicle/noicicle.go +++ b/backend/groth16/bn254/icicle/noicicle.go @@ -1,6 +1,6 @@ //go:build !icicle -package icicle +package icicle_bn254 import ( "fmt" diff --git a/backend/groth16/bn254/icicle/provingkey.go b/backend/groth16/bn254/icicle/provingkey.go index ee9d17057..146a79425 100644 --- a/backend/groth16/bn254/icicle/provingkey.go +++ b/backend/groth16/bn254/icicle/provingkey.go @@ -1,26 +1,25 @@ -package icicle +package icicle_bn254 import ( - "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "unsafe" + groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" cs "github.com/consensys/gnark/constraint/bn254" - icicle_core "github.com/ingonyama-zk/icicle/v3/wrappers/golang/core" ) type deviceInfo struct { - CosetGenerator [fr.Limbs * 2]uint32 - G1Device struct { - A, B, K, Z icicle_core.DeviceSlice + G1Device struct { + A, B, K, Z unsafe.Pointer } - G2Device struct { - B icicle_core.DeviceSlice + DomainDevice struct { + Twiddles, TwiddlesInv unsafe.Pointer + CosetTable, CosetTableInv unsafe.Pointer } - DenDevice icicle_core.DeviceSlice - - CommitmentKeysDevice struct { - Basis []icicle_core.DeviceSlice - BasisExpSigma []icicle_core.DeviceSlice // we compute in batch + G2Device struct { + B unsafe.Pointer } + DenDevice unsafe.Pointer + InfinityPointIndicesK []int } type ProvingKey struct { @@ -28,17 +27,10 @@ type ProvingKey struct { *deviceInfo } -func NewProvingKey() *ProvingKey { - warmUpDevice() - return &ProvingKey{} -} - func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *groth16_bn254.VerifyingKey) error { - warmUpDevice() return groth16_bn254.Setup(r1cs, &pk.ProvingKey, vk) } func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { - warmUpDevice() return groth16_bn254.DummySetup(r1cs, &pk.ProvingKey) } diff --git a/backend/groth16/groth16.go b/backend/groth16/groth16.go index aa27ef3f7..426621e2f 100644 --- a/backend/groth16/groth16.go +++ b/backend/groth16/groth16.go @@ -347,7 +347,7 @@ func NewProvingKey(curveID ecc.ID) ProvingKey { case ecc.BN254: pk = &groth16_bn254.ProvingKey{} if icicle_bn254.HasIcicle { - pk = icicle_bn254.NewProvingKey() + pk = &icicle_bn254.ProvingKey{} } case ecc.BLS12_377: pk = &groth16_bls12377.ProvingKey{} diff --git a/go.mod b/go.mod index a71f63c3c..6e0371490 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 github.com/icza/bitio v1.1.0 - github.com/ingonyama-zk/icicle/v3 v3.1.1-0.20241118092657-fccdb2f0921b + github.com/ingonyama-zk/iciclegnark v0.1.0 github.com/leanovate/gopter v0.2.11 github.com/ronanh/intcomp v1.1.0 github.com/rs/zerolog v1.33.0 @@ -26,10 +26,12 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/ingonyama-zk/icicle v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/sys v0.28.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 4676a0a14..5a48cfc66 100644 --- a/go.sum +++ b/go.sum @@ -184,8 +184,10 @@ github.com/icza/bitio v1.1.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6 h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k= github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/ingonyama-zk/icicle/v3 v3.1.1-0.20241118092657-fccdb2f0921b h1:AvQTK7l0PTHODD06PVQX1Tn2o29sRIaKIDOvTJmKurY= -github.com/ingonyama-zk/icicle/v3 v3.1.1-0.20241118092657-fccdb2f0921b/go.mod h1:e0JHb27/P6WorCJS3YolbY5XffS4PGBuoW38OthLkDs= +github.com/ingonyama-zk/icicle v1.1.0 h1:a2MUIaF+1i4JY2Lnb961ZMvaC8GFs9GqZgSnd9e95C8= +github.com/ingonyama-zk/icicle v1.1.0/go.mod h1:kAK8/EoN7fUEmakzgZIYdWy1a2rBnpCaZLqSHwZWxEk= +github.com/ingonyama-zk/iciclegnark v0.1.0 h1:88MkEghzjQBMjrYRJFxZ9oR9CTIpB8NG2zLeCJSvXKQ= +github.com/ingonyama-zk/iciclegnark v0.1.0/go.mod h1:wz6+IpyHKs6UhMMoQpNqz1VY+ddfKqC/gRwR/64W6WU= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= From 2945fbc2d18da51fb78fca3bccf8bfa43eda8978 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 16 Dec 2024 18:56:19 +0100 Subject: [PATCH 07/12] chore: remove duplicate nil-check (#1355) --- std/algebra/emulated/sw_bls12381/pairing.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/pairing.go b/std/algebra/emulated/sw_bls12381/pairing.go index 8f6018090..76798f42e 100644 --- a/std/algebra/emulated/sw_bls12381/pairing.go +++ b/std/algebra/emulated/sw_bls12381/pairing.go @@ -318,11 +318,6 @@ func (pr Pairing) AssertFinalExponentiationIsOne(x *GTEl) { panic(err) } - if err != nil { - // err is non-nil only for invalid number of inputs - panic(err) - } - residueWitness := pr.FromTower([12]*baseEl{res[0], res[1], res[2], res[3], res[4], res[5], res[6], res[7], res[8], res[9], res[10], res[11]}) // constrain cubicNonResiduePower to be in Fp6 // that is: a100=a101=a110=a111=a120=a121=0 From 627dc5915a294f0072ef1b33181d143cb28bde40 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 16 Dec 2024 22:31:32 +0100 Subject: [PATCH 08/12] fix: avoid linking icicle dependent files when tag not provided (#1352) Co-authored-by: Jeremy Felder --- README.md | 6 +- backend/groth16/bn254/icicle/device.go | 31 + backend/groth16/bn254/icicle/doc.go | 2 +- backend/groth16/bn254/icicle/icicle.go | 677 ++++++++++++------- backend/groth16/bn254/icicle/marshal_test.go | 33 +- backend/groth16/bn254/icicle/noicicle.go | 22 +- backend/groth16/bn254/icicle/provingkey.go | 34 +- backend/groth16/groth16.go | 2 +- go.mod | 4 +- go.sum | 6 +- 10 files changed, 558 insertions(+), 259 deletions(-) create mode 100644 backend/groth16/bn254/icicle/device.go diff --git a/README.md b/README.md index 229630ff9..02c008f95 100644 --- a/README.md +++ b/README.md @@ -162,9 +162,9 @@ func main() { ### GPU Support -#### Icicle Library +#### ICICLE Library -The following schemes and curves support experimental use of Ingonyama's Icicle GPU library for low level zk-SNARK primitives such as MSM, NTT, and polynomial operations: +The following schemes and curves support experimental use of Ingonyama's ICICLE GPU library for low level zk-SNARK primitives such as MSM, NTT, and polynomial operations: - [x] [Groth16](https://eprint.iacr.org/2016/260) @@ -184,7 +184,7 @@ You can then toggle on or off icicle acceleration by providing the `WithIcicleAc proof, err := groth16.Prove(ccs, pk, secretWitness) ``` -For more information about prerequisites see the [Icicle repo](https://github.com/ingonyama-zk/icicle). +For more information about prerequisites see the [ICICLE repo](https://github.com/ingonyama-zk/icicle). **NB! ICICLE CUDA kernels are covered by a special license for now. Follow the instructions to download and set up the kernels.** ## Citing diff --git a/backend/groth16/bn254/icicle/device.go b/backend/groth16/bn254/icicle/device.go new file mode 100644 index 000000000..d00fd1cd4 --- /dev/null +++ b/backend/groth16/bn254/icicle/device.go @@ -0,0 +1,31 @@ +//go:build icicle + +package icicle + +import ( + "fmt" + "sync" + + "github.com/consensys/gnark/logger" + icicle_runtime "github.com/ingonyama-zk/icicle/v3/wrappers/golang/runtime" +) + +var onceWarmUpDevice sync.Once + +func warmUpDevice() { + onceWarmUpDevice.Do(func() { + log := logger.Logger() + err := icicle_runtime.LoadBackendFromEnvOrDefault() + if err != icicle_runtime.Success { + panic(fmt.Sprintf("ICICLE backend loading error: %s", err.AsString())) + } + device := icicle_runtime.CreateDevice("CUDA", 0) + log.Debug().Int32("id", device.Id).Str("type", device.GetDeviceType()).Msg("ICICLE device created") + icicle_runtime.RunOnDevice(&device, func(args ...any) { + err := icicle_runtime.WarmUpDevice() + if err != icicle_runtime.Success { + panic(fmt.Sprintf("ICICLE device warmup error: %s", err.AsString())) + } + }) + }) +} diff --git a/backend/groth16/bn254/icicle/doc.go b/backend/groth16/bn254/icicle/doc.go index a77a7fbde..3a662b35d 100644 --- a/backend/groth16/bn254/icicle/doc.go +++ b/backend/groth16/bn254/icicle/doc.go @@ -1,2 +1,2 @@ // Package icicle_bn254 implements ICICLE acceleration for BN254 Groth16 backend. -package icicle_bn254 +package icicle diff --git a/backend/groth16/bn254/icicle/icicle.go b/backend/groth16/bn254/icicle/icicle.go index 5b1b235d3..49133cd8b 100644 --- a/backend/groth16/bn254/icicle/icicle.go +++ b/backend/groth16/bn254/icicle/icicle.go @@ -1,19 +1,20 @@ //go:build icicle -package icicle_bn254 +package icicle import ( "fmt" "math/big" "math/bits" + "os" "time" - "unsafe" + "github.com/consensys/gnark-crypto/ecc" curve "github.com/consensys/gnark-crypto/ecc/bn254" "github.com/consensys/gnark-crypto/ecc/bn254/fp" "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/consensys/gnark-crypto/ecc/bn254/fr/fft" "github.com/consensys/gnark-crypto/ecc/bn254/fr/hash_to_field" - "github.com/consensys/gnark-crypto/ecc/bn254/fr/pedersen" "github.com/consensys/gnark/backend" groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" "github.com/consensys/gnark/backend/groth16/internal" @@ -23,33 +24,40 @@ import ( "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark/internal/utils" "github.com/consensys/gnark/logger" - iciclegnark "github.com/ingonyama-zk/iciclegnark/curves/bn254" + + icicle_core "github.com/ingonyama-zk/icicle/v3/wrappers/golang/core" + icicle_bn254 "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254" + icicle_g2 "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254/g2" + icicle_msm "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254/msm" + icicle_ntt "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254/ntt" + icicle_vecops "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254/vecOps" + icicle_runtime "github.com/ingonyama-zk/icicle/v3/wrappers/golang/runtime" + + fcs "github.com/consensys/gnark/frontend/cs" ) const HasIcicle = true -func (pk *ProvingKey) setupDevicePointers() error { +var isProfileMode bool + +func init() { + _, isProfileMode = os.LookupEnv("ICICLE_STEP_PROFILE") +} + +func (pk *ProvingKey) setupDevicePointers(device *icicle_runtime.Device) error { if pk.deviceInfo != nil { return nil } pk.deviceInfo = &deviceInfo{} - n := int(pk.Domain.Cardinality) - sizeBytes := n * fr.Bytes - - /************************* Start Domain Device Setup ***************************/ - copyCosetInvDone := make(chan unsafe.Pointer, 1) - copyCosetDone := make(chan unsafe.Pointer, 1) - copyDenDone := make(chan unsafe.Pointer, 1) - /************************* CosetTableInv ***************************/ - go iciclegnark.CopyToDevice(pk.Domain.CosetTableInv, sizeBytes, copyCosetInvDone) - - /************************* CosetTable ***************************/ - go iciclegnark.CopyToDevice(pk.Domain.CosetTable, sizeBytes, copyCosetDone) - + gen, err := fft.Generator(2 * pk.Domain.Cardinality) + if err != nil { + return fmt.Errorf("get fft generator: %w", err) + } /************************* Den ***************************/ + n := int(pk.Domain.Cardinality) var denI, oneI fr.Element oneI.SetOne() - denI.Exp(pk.Domain.FrMultiplicativeGen, big.NewInt(int64(pk.Domain.Cardinality))) + denI.Exp(gen, big.NewInt(int64(pk.Domain.Cardinality))) denI.Sub(&denI, &oneI).Inverse(&denI) log2SizeFloor := bits.Len(uint(n)) - 1 @@ -62,71 +70,165 @@ func (pk *ProvingKey) setupDevicePointers() error { denIcicleArr = append(denIcicleArr, denI) } - go iciclegnark.CopyToDevice(denIcicleArr, sizeBytes, copyDenDone) + copyDenDone := make(chan struct{}) + icicle_runtime.RunOnDevice(device, func(args ...any) { + denIcicleArrHost := (icicle_core.HostSlice[fr.Element])(denIcicleArr) + denIcicleArrHost.CopyToDevice(&pk.DenDevice, true) + if err := icicle_bn254.FromMontgomery(pk.DenDevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy den to device: %s", err.AsString())) + } + close(copyDenDone) + }) + + /************************* Init Domain Device ***************************/ + genBits := gen.Bits() + limbs := icicle_core.ConvertUint64ArrToUint32Arr(genBits[:]) + copy(pk.CosetGenerator[:], limbs[:fr.Limbs*2]) + var rouIcicle icicle_bn254.ScalarField + rouIcicle.FromLimbs(limbs) + + initDomain := make(chan struct{}) + icicle_runtime.RunOnDevice(device, func(args ...any) { + if e := icicle_ntt.InitDomain(rouIcicle, icicle_core.GetDefaultNTTInitDomainConfig()); e != icicle_runtime.Success { + panic(fmt.Sprintf("couldn't initialize domain: %s", e.AsString())) // TODO + } + close(initDomain) + }) - /************************* Twiddles and Twiddles Inv ***************************/ - twiddlesInv_d_gen, twddles_err := iciclegnark.GenerateTwiddleFactors(n, true) - if twddles_err != nil { - return twddles_err - } + /************************* End Init Domain Device ***************************/ + /************************* Start G1 Device Setup ***************************/ + /************************* A ***************************/ + copyADone := make(chan struct{}) + icicle_runtime.RunOnDevice(device, func(args ...any) { + g1AHost := (icicle_core.HostSlice[curve.G1Affine])(pk.G1.A) + g1AHost.CopyToDevice(&pk.G1Device.A, true) + if err := icicle_bn254.AffineFromMontgomery(pk.G1Device.A); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy A to device: %s", err.AsString())) + } + close(copyADone) + }) + /************************* B ***************************/ + copyBDone := make(chan struct{}) + icicle_runtime.RunOnDevice(device, func(args ...any) { + g1BHost := (icicle_core.HostSlice[curve.G1Affine])(pk.G1.B) + g1BHost.CopyToDevice(&pk.G1Device.B, true) + if err := icicle_bn254.AffineFromMontgomery(pk.G1Device.B); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy B to device: %s", err.AsString())) + } + close(copyBDone) + }) + /************************* K ***************************/ + copyKDone := make(chan struct{}) + icicle_runtime.RunOnDevice(device, func(args ...any) { + g1KHost := (icicle_core.HostSlice[curve.G1Affine])(pk.G1.K) + g1KHost.CopyToDevice(&pk.G1Device.K, true) + if err := icicle_bn254.AffineFromMontgomery(pk.G1Device.K); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy K to device: %s", err.AsString())) + } + close(copyKDone) + }) + /************************* Z ***************************/ + copyZDone := make(chan struct{}) + icicle_runtime.RunOnDevice(device, func(args ...any) { + g1ZHost := (icicle_core.HostSlice[curve.G1Affine])(pk.G1.Z) + g1ZHost.CopyToDevice(&pk.G1Device.Z, true) + err := icicle_bn254.AffineFromMontgomery(pk.G1Device.Z) + if err != icicle_runtime.Success { + panic(fmt.Sprintf("copy Z to device: %s", err.AsString())) + } + close(copyZDone) + }) + /************************* End G1 Device Setup ***************************/ + /************************* Start G2 Device Setup ***************************/ + copyG2BDone := make(chan struct{}) + icicle_runtime.RunOnDevice(device, func(args ...any) { + g2BHost := (icicle_core.HostSlice[curve.G2Affine])(pk.G2.B) + g2BHost.CopyToDevice(&pk.G2Device.B, true) + if err := icicle_g2.G2AffineFromMontgomery(pk.G2Device.B); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy G2 B to device: %s", err.AsString())) + } + close(copyG2BDone) + }) + /************************* End G2 Device Setup ***************************/ - twiddles_d_gen, twddles_err := iciclegnark.GenerateTwiddleFactors(n, false) - if twddles_err != nil { - return twddles_err - } + /************************* Commitment Keys Device Setup ***************************/ + + commitmentKeysDeviceDone := make(chan struct{}) + pk.CommitmentKeysDevice.Basis = make([]icicle_core.DeviceSlice, len(pk.CommitmentKeys)) + pk.CommitmentKeysDevice.BasisExpSigma = make([]icicle_core.DeviceSlice, len(pk.CommitmentKeys)) + icicle_runtime.RunOnDevice(device, func(args ...any) { + for i := range pk.CommitmentKeys { + commitmentKeyBasisHost := icicle_core.HostSliceFromElements(pk.CommitmentKeys[i].Basis) + commitmentKeyBasisExpSigmaHost := icicle_core.HostSliceFromElements(pk.CommitmentKeys[i].BasisExpSigma) + commitmentKeyBasisHost.CopyToDevice(&pk.CommitmentKeysDevice.Basis[i], true) + commitmentKeyBasisExpSigmaHost.CopyToDevice(&pk.CommitmentKeysDevice.BasisExpSigma[i], true) + } + close(commitmentKeysDeviceDone) + }) + /************************* End Commitment Keys Device Setup ***************************/ + + /************************* Wait for all data tranfsers ***************************/ + <-initDomain + <-copyDenDone + <-copyADone + <-copyBDone + <-copyKDone + <-copyZDone + <-copyG2BDone + <-commitmentKeysDeviceDone - /************************* End Domain Device Setup ***************************/ - pk.DomainDevice.Twiddles = twiddles_d_gen - pk.DomainDevice.TwiddlesInv = twiddlesInv_d_gen + return nil +} - pk.DomainDevice.CosetTableInv = <-copyCosetInvDone - pk.DomainDevice.CosetTable = <-copyCosetDone - pk.DenDevice = <-copyDenDone +func projectiveToGnarkAffine(p icicle_bn254.Projective) *curve.G1Affine { + px, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(p.X.ToBytesLittleEndian())) + py, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(p.Y.ToBytesLittleEndian())) + pz, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(p.Z.ToBytesLittleEndian())) - /************************* Start G1 Device Setup ***************************/ - /************************* A ***************************/ - pointsBytesA := len(pk.G1.A) * fp.Bytes * 2 - copyADone := make(chan unsafe.Pointer, 1) - go iciclegnark.CopyPointsToDevice(pk.G1.A, pointsBytesA, copyADone) // Make a function for points + var x, y, zInv fp.Element - /************************* B ***************************/ - pointsBytesB := len(pk.G1.B) * fp.Bytes * 2 - copyBDone := make(chan unsafe.Pointer, 1) - go iciclegnark.CopyPointsToDevice(pk.G1.B, pointsBytesB, copyBDone) // Make a function for points + zInv.Inverse(&pz) + x.Mul(&px, &zInv) + y.Mul(&py, &zInv) - /************************* K ***************************/ - var pointsNoInfinity []curve.G1Affine - for i, gnarkPoint := range pk.G1.K { - if gnarkPoint.IsInfinity() { - pk.InfinityPointIndicesK = append(pk.InfinityPointIndicesK, i) - } else { - pointsNoInfinity = append(pointsNoInfinity, gnarkPoint) - } - } + return &curve.G1Affine{X: x, Y: y} +} - pointsBytesK := len(pointsNoInfinity) * fp.Bytes * 2 - copyKDone := make(chan unsafe.Pointer, 1) - go iciclegnark.CopyPointsToDevice(pointsNoInfinity, pointsBytesK, copyKDone) // Make a function for points +func g1ProjectiveToG1Jac(p icicle_bn254.Projective) curve.G1Jac { + var p1 curve.G1Jac + p1.FromAffine(projectiveToGnarkAffine(p)) - /************************* Z ***************************/ - pointsBytesZ := len(pk.G1.Z) * fp.Bytes * 2 - copyZDone := make(chan unsafe.Pointer, 1) - go iciclegnark.CopyPointsToDevice(pk.G1.Z, pointsBytesZ, copyZDone) // Make a function for points + return p1 +} - /************************* End G1 Device Setup ***************************/ - pk.G1Device.A = <-copyADone - pk.G1Device.B = <-copyBDone - pk.G1Device.K = <-copyKDone - pk.G1Device.Z = <-copyZDone +func toGnarkE2(f icicle_g2.G2BaseField) curve.E2 { + bytes := f.ToBytesLittleEndian() + a0, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(bytes[:fp.Bytes])) + a1, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(bytes[fp.Bytes:])) + return curve.E2{ + A0: a0, + A1: a1, + } +} - /************************* Start G2 Device Setup ***************************/ - pointsBytesB2 := len(pk.G2.B) * fp.Bytes * 4 - copyG2BDone := make(chan unsafe.Pointer, 1) - go iciclegnark.CopyG2PointsToDevice(pk.G2.B, pointsBytesB2, copyG2BDone) // Make a function for points - pk.G2Device.B = <-copyG2BDone +func g2ProjectiveToG2Jac(p *icicle_g2.G2Projective) curve.G2Jac { + x := toGnarkE2(p.X) + y := toGnarkE2(p.Y) + z := toGnarkE2(p.Z) + var zSquared curve.E2 + zSquared.Mul(&z, &z) - /************************* End G2 Device Setup ***************************/ - return nil + var X curve.E2 + X.Mul(&x, &z) + + var Y curve.E2 + Y.Mul(&y, &zSquared) + + return curve.G2Jac{ + X: X, + Y: Y, + Z: z, + } } // Prove generates the proof of knowledge of a r1cs with full witness (secret + public part). @@ -142,9 +244,13 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b return groth16_bn254.Prove(r1cs, &pk.ProvingKey, fullWitness, opts...) } log := logger.Logger().With().Str("curve", r1cs.CurveID().String()).Str("acceleration", "icicle").Int("nbConstraints", r1cs.GetNbConstraints()).Str("backend", "groth16").Logger() + + device := icicle_runtime.CreateDevice("CUDA", 0) + if pk.deviceInfo == nil { log.Debug().Msg("precomputing proving key in GPU") - if err := pk.setupDevicePointers(); err != nil { + + if err := pk.setupDevicePointers(&device); err != nil { return nil, fmt.Errorf("setup device pointers: %w", err) } } @@ -156,42 +262,48 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b solverOpts := opt.SolverOpts[:len(opt.SolverOpts):len(opt.SolverOpts)] privateCommittedValues := make([][]fr.Element, len(commitmentInfo)) - for i := range commitmentInfo { - solverOpts = append(solverOpts, solver.OverrideHint(commitmentInfo[i].HintID, func(i int) solver.Hint { - return func(_ *big.Int, in []*big.Int, out []*big.Int) error { - privateCommittedValues[i] = make([]fr.Element, len(commitmentInfo[i].PrivateCommitted)) - hashed := in[:len(commitmentInfo[i].PublicAndCommitmentCommitted)] - committed := in[len(hashed):] - for j, inJ := range committed { - privateCommittedValues[i][j].SetBigInt(inJ) - } - - var err error - if proof.Commitments[i], err = pk.CommitmentKeys[i].Commit(privateCommittedValues[i]); err != nil { - return err - } + privateCommittedValuesDevice := make([]icicle_core.DeviceSlice, len(commitmentInfo)) + + // override hints + bsb22ID := solver.GetHintID(fcs.Bsb22CommitmentComputePlaceholder) + solverOpts = append(solverOpts, solver.OverrideHint(bsb22ID, func(_ *big.Int, in []*big.Int, out []*big.Int) error { + i := int(in[0].Int64()) + in = in[1:] + privateCommittedValues[i] = make([]fr.Element, len(commitmentInfo[i].PrivateCommitted)) + hashed := in[:len(commitmentInfo[i].PublicAndCommitmentCommitted)] + committed := in[+len(hashed):] + for j, inJ := range committed { + privateCommittedValues[i][j].SetBigInt(inJ) + } - opt.HashToFieldFn.Write(constraint.SerializeCommitment(proof.Commitments[i].Marshal(), hashed, (fr.Bits-1)/8+1)) - hashBts := opt.HashToFieldFn.Sum(nil) - opt.HashToFieldFn.Reset() - nbBuf := fr.Bytes - if opt.HashToFieldFn.Size() < fr.Bytes { - nbBuf = opt.HashToFieldFn.Size() - } - var res fr.Element - res.SetBytes(hashBts[:nbBuf]) - res.BigInt(out[0]) - return err + proofCommitmentIcicle := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) + ckBasisMsmDone := make(chan struct{}) + icicle_runtime.RunOnDevice(&device, func(args ...any) { + cfg := icicle_msm.GetDefaultMSMConfig() + cfg.AreBasesMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + privateCommittedValuesHost := icicle_core.HostSliceFromElements(privateCommittedValues[i]) + privateCommittedValuesHost.CopyToDevice(&privateCommittedValuesDevice[i], true) + if err := icicle_msm.Msm(privateCommittedValuesDevice[i], pk.CommitmentKeysDevice.Basis[i], &cfg, proofCommitmentIcicle); err != icicle_runtime.Success { + panic(fmt.Sprintf("commitment: %s", err.AsString())) } - }(i))) - } - - if r1cs.GkrInfo.Is() { - var gkrData cs.GkrSolvingData - solverOpts = append(solverOpts, - solver.OverrideHint(r1cs.GkrInfo.SolveHintID, cs.GkrSolveHint(r1cs.GkrInfo, &gkrData)), - solver.OverrideHint(r1cs.GkrInfo.ProveHintID, cs.GkrProveHint(r1cs.GkrInfo.HashName, &gkrData))) - } + close(ckBasisMsmDone) + }) + <-ckBasisMsmDone + proof.Commitments[i] = *projectiveToGnarkAffine(proofCommitmentIcicle[0]) + + opt.HashToFieldFn.Write(constraint.SerializeCommitment(proof.Commitments[i].Marshal(), hashed, (fr.Bits-1)/8+1)) + hashBts := opt.HashToFieldFn.Sum(nil) + opt.HashToFieldFn.Reset() + nbBuf := fr.Bytes + if opt.HashToFieldFn.Size() < fr.Bytes { + nbBuf = opt.HashToFieldFn.Size() + } + var res fr.Element + res.SetBytes(hashBts[:nbBuf]) + res.BigInt(out[0]) + return nil + })) _solution, err := r1cs.Solve(fullWitness, solverOpts...) if err != nil { @@ -202,33 +314,66 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b wireValues := []fr.Element(solution.W) start := time.Now() - + numCommitmentKeys := len(pk.CommitmentKeys) + poks := make([]curve.G1Affine, numCommitmentKeys) + + // if there are CommitmentKeys, run a batch MSM for pederson Proof of Knowledge + if numCommitmentKeys > 0 { + startPoKBatch := time.Now() + poksIcicle := make([]icicle_core.HostSlice[icicle_bn254.Projective], numCommitmentKeys) + for i := range poksIcicle { + poksIcicle[i] = make(icicle_core.HostSlice[icicle_bn254.Projective], 1) + } + ckBasisExpSigmaMsmBatchDone := make(chan struct{}) + icicle_runtime.RunOnDevice(&device, func(args ...any) { + cfg := icicle_msm.GetDefaultMSMConfig() + cfg.AreBasesMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + for i := range pk.CommitmentKeysDevice.BasisExpSigma { + if err := icicle_msm.Msm(privateCommittedValuesDevice[i], pk.CommitmentKeysDevice.BasisExpSigma[i], &cfg, poksIcicle[i]); err != icicle_runtime.Success { + panic(fmt.Sprintf("commitment POK: %s", err.AsString())) + } + } + close(ckBasisExpSigmaMsmBatchDone) + }) + <-ckBasisExpSigmaMsmBatchDone + for i := range pk.CommitmentKeys { + poks[i] = *projectiveToGnarkAffine(poksIcicle[i][0]) + } + if isProfileMode { + log.Debug().Dur("took", time.Since(startPoKBatch)).Msg("ICICLE Batch Proof of Knowledge") + } + } + // compute challenge for folding the PoKs from the commitments commitmentsSerialized := make([]byte, fr.Bytes*len(commitmentInfo)) for i := range commitmentInfo { copy(commitmentsSerialized[fr.Bytes*i:], wireValues[commitmentInfo[i].CommitmentIndex].Marshal()) } - - if proof.CommitmentPok, err = pedersen.BatchProve(pk.CommitmentKeys, privateCommittedValues, commitmentsSerialized); err != nil { + challenge, err := fr.Hash(commitmentsSerialized, []byte("G16-BSB22"), 1) + if err != nil { + return nil, err + } + if _, err = proof.CommitmentPok.Fold(poks, challenge[0], ecc.MultiExpConfig{NbTasks: 1}); err != nil { return nil, err } - // H (witness reduction / FFT part) - var h unsafe.Pointer - chHDone := make(chan struct{}, 1) - go func() { - h = computeH(solution.A, solution.B, solution.C, pk) + var h icicle_core.DeviceSlice + chHDone := make(chan struct{}) + icicle_runtime.RunOnDevice(&device, func(args ...any) { + h = computeH(solution.A, solution.B, solution.C, pk, &device) + solution.A = nil solution.B = nil solution.C = nil - chHDone <- struct{}{} - }() + close(chHDone) + }) // we need to copy and filter the wireValues for each multi exp // as pk.G1.A, pk.G1.B and pk.G2.B may have (a significant) number of point at infinity - var wireValuesADevice, wireValuesBDevice iciclegnark.OnDeviceData - chWireValuesA, chWireValuesB := make(chan struct{}, 1), make(chan struct{}, 1) + var wireValuesADevice, wireValuesBDevice icicle_core.DeviceSlice + chWireValuesA, chWireValuesB := make(chan struct{}), make(chan struct{}) - go func() { + icicle_runtime.RunOnDevice(&device, func(args ...any) { wireValuesA := make([]fr.Element, len(wireValues)-int(pk.NbInfinityA)) for i, j := 0, 0; j < len(wireValuesA); i++ { if pk.InfinityA[i] { @@ -237,22 +382,18 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b wireValuesA[j] = wireValues[i] j++ } - wireValuesASize := len(wireValuesA) - scalarBytes := wireValuesASize * fr.Bytes // Copy scalars to the device and retain ptr to them - copyDone := make(chan unsafe.Pointer, 1) - iciclegnark.CopyToDevice(wireValuesA, scalarBytes, copyDone) - wireValuesADevicePtr := <-copyDone - - wireValuesADevice = iciclegnark.OnDeviceData{ - P: wireValuesADevicePtr, - Size: wireValuesASize, + wireValuesAHost := (icicle_core.HostSlice[fr.Element])(wireValuesA) + wireValuesAHost.CopyToDevice(&wireValuesADevice, true) + if err := icicle_bn254.FromMontgomery(wireValuesADevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy A to device: %s", err.AsString())) } close(chWireValuesA) - }() - go func() { + }) + + icicle_runtime.RunOnDevice(&device, func(args ...any) { wireValuesB := make([]fr.Element, len(wireValues)-int(pk.NbInfinityB)) for i, j := 0, 0; j < len(wireValuesB); i++ { if pk.InfinityB[i] { @@ -261,21 +402,16 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b wireValuesB[j] = wireValues[i] j++ } - wireValuesBSize := len(wireValuesB) - scalarBytes := wireValuesBSize * fr.Bytes // Copy scalars to the device and retain ptr to them - copyDone := make(chan unsafe.Pointer, 1) - iciclegnark.CopyToDevice(wireValuesB, scalarBytes, copyDone) - wireValuesBDevicePtr := <-copyDone - - wireValuesBDevice = iciclegnark.OnDeviceData{ - P: wireValuesBDevicePtr, - Size: wireValuesBSize, + wireValuesBHost := (icicle_core.HostSlice[fr.Element])(wireValuesB) + wireValuesBHost.CopyToDevice(&wireValuesBDevice, true) + if err := icicle_bn254.FromMontgomery(wireValuesBDevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy B to device: %s", err.AsString())) } close(chWireValuesB) - }() + }) // sample random r and s var r, s big.Int @@ -295,74 +431,91 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b deltas := curve.BatchScalarMultiplicationG1(&pk.G1.Delta, []fr.Element{_r, _s, _kr}) var bs1, ar curve.G1Jac + chArDone, chBs1Done := make(chan struct{}), make(chan struct{}) computeBS1 := func() error { <-chWireValuesB - if bs1, _, err = iciclegnark.MsmOnDevice(wireValuesBDevice.P, pk.G1Device.B, wireValuesBDevice.Size, true); err != nil { - return err + cfg := icicle_msm.GetDefaultMSMConfig() + res := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) + start := time.Now() + if err := icicle_msm.Msm(wireValuesBDevice, pk.G1Device.B, &cfg, res); err != icicle_runtime.Success { + panic(fmt.Sprintf("msm Bs1: %s", err.AsString())) + } + + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("MSM Bs1") } + bs1 = g1ProjectiveToG1Jac(res[0]) bs1.AddMixed(&pk.G1.Beta) bs1.AddMixed(&deltas[1]) + close(chBs1Done) return nil } computeAR1 := func() error { <-chWireValuesA - if ar, _, err = iciclegnark.MsmOnDevice(wireValuesADevice.P, pk.G1Device.A, wireValuesADevice.Size, true); err != nil { - return err + cfg := icicle_msm.GetDefaultMSMConfig() + res := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) + start := time.Now() + if err := icicle_msm.Msm(wireValuesADevice, pk.G1Device.A, &cfg, res); err != icicle_runtime.Success { + panic(fmt.Sprintf("msm Ar1: %s", err.AsString())) + } + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("MSM Ar1") } + ar = g1ProjectiveToG1Jac(res[0]) ar.AddMixed(&pk.G1.Alpha) ar.AddMixed(&deltas[0]) proof.Ar.FromJacobian(&ar) + close(chArDone) return nil } computeKRS := func() error { var krs, krs2, p1 curve.G1Jac - sizeH := int(pk.Domain.Cardinality - 1) // comes from the fact the deg(H)=(n-1)+(n-1)-n=n-2 + sizeH := int(pk.Domain.Cardinality - 1) - // check for small circuits as iciclegnark doesn't handle zero sizes well - if len(pk.G1.Z) > 0 { - if krs2, _, err = iciclegnark.MsmOnDevice(h, pk.G1Device.Z, sizeH, true); err != nil { - return err - } + cfg := icicle_msm.GetDefaultMSMConfig() + resKrs2 := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) + start := time.Now() + if err := icicle_msm.Msm(h.RangeTo(sizeH, false), pk.G1Device.Z, &cfg, resKrs2); err != icicle_runtime.Success { + panic(fmt.Sprintf("msm Krs2: %s", err.AsString())) + } + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("MSM Krs2") } + krs2 = g1ProjectiveToG1Jac(resKrs2[0]) // filter the wire values if needed // TODO Perf @Tabaie worst memory allocation offender toRemove := commitmentInfo.GetPrivateCommitted() toRemove = append(toRemove, commitmentInfo.CommitmentIndexes()) - scalars := filterHeap(wireValues[r1cs.GetNbPublicVariables():], r1cs.GetNbPublicVariables(), internal.ConcatAll(toRemove...)) - - // filter zero/infinity points since icicle doesn't handle them - // See https://github.com/ingonyama-zk/icicle/issues/169 for more info - for _, indexToRemove := range pk.InfinityPointIndicesK { - scalars = append(scalars[:indexToRemove], scalars[indexToRemove+1:]...) + _wireValues := filterHeap(wireValues[r1cs.GetNbPublicVariables():], r1cs.GetNbPublicVariables(), internal.ConcatAll(toRemove...)) + _wireValuesHost := (icicle_core.HostSlice[fr.Element])(_wireValues) + resKrs := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) + cfg.AreScalarsMontgomeryForm = true + start = time.Now() + if err := icicle_msm.Msm(_wireValuesHost, pk.G1Device.K, &cfg, resKrs); err != icicle_runtime.Success { + panic(fmt.Sprintf("msm Krs: %s", err.AsString())) } - - scalarBytes := len(scalars) * fr.Bytes - - copyDone := make(chan unsafe.Pointer, 1) - iciclegnark.CopyToDevice(scalars, scalarBytes, copyDone) - scalars_d := <-copyDone - - krs, _, err = iciclegnark.MsmOnDevice(scalars_d, pk.G1Device.K, len(scalars), true) - iciclegnark.FreeDevicePointer(scalars_d) - - if err != nil { - return err + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("MSM Krs") } + krs = g1ProjectiveToG1Jac(resKrs[0]) krs.AddMixed(&deltas[2]) krs.AddAssign(&krs2) + <-chArDone + <-chBs1Done + p1.ScalarMultiplication(&ar, &s) krs.AddAssign(&p1) @@ -379,9 +532,17 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b var Bs, deltaS curve.G2Jac <-chWireValuesB - if Bs, _, err = iciclegnark.MsmG2OnDevice(wireValuesBDevice.P, pk.G2Device.B, wireValuesBDevice.Size, true); err != nil { - return err + + cfg := icicle_g2.G2GetDefaultMSMConfig() + res := make(icicle_core.HostSlice[icicle_g2.G2Projective], 1) + start := time.Now() + if err := icicle_g2.G2Msm(wireValuesBDevice, pk.G2Device.B, &cfg, res); err != icicle_runtime.Success { + panic(fmt.Sprintf("msm Bs2: %s", err.AsString())) + } + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("MSM Bs2 G2") } + Bs = g2ProjectiveToG2Jac(&res[0]) deltaS.FromAffine(&pk.G2.Delta) deltaS.ScalarMultiplication(&deltaS, &s) @@ -392,31 +553,51 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b return nil } + // schedule our proof part computations + icicle_runtime.RunOnDevice(&device, func(args ...any) { + if err := computeAR1(); err != nil { + panic(fmt.Sprintf("compute AR1: %v", err)) + } + }) + + icicle_runtime.RunOnDevice(&device, func(args ...any) { + if err := computeBS1(); err != nil { + panic(fmt.Sprintf("compute BS1: %v", err)) + } + }) + + icicle_runtime.RunOnDevice(&device, func(args ...any) { + if err := computeBS2(); err != nil { + panic(fmt.Sprintf("compute BS2: %v", err)) + } + }) + // wait for FFT to end <-chHDone - // schedule our proof part computations - if err := computeAR1(); err != nil { - return nil, err - } - if err := computeBS1(); err != nil { - return nil, err - } - if err := computeKRS(); err != nil { - return nil, err - } - if err := computeBS2(); err != nil { - return nil, err - } + computeKrsDone := make(chan struct{}) + icicle_runtime.RunOnDevice(&device, func(args ...any) { + if err := computeKRS(); err != nil { + panic(fmt.Sprintf("compute KRS: %v", err)) + } + close(computeKrsDone) + }) + <-computeKrsDone log.Debug().Dur("took", time.Since(start)).Msg("prover done") // free device/GPU memory that is not needed for future proofs (scalars/hpoly) - go func() { - iciclegnark.FreeDevicePointer(wireValuesADevice.P) - iciclegnark.FreeDevicePointer(wireValuesBDevice.P) - iciclegnark.FreeDevicePointer(h) - }() + icicle_runtime.RunOnDevice(&device, func(args ...any) { + if err := wireValuesADevice.Free(); err != icicle_runtime.Success { + log.Error().Msgf("free wireValuesADevice failed: %s", err.AsString()) + } + if err := wireValuesBDevice.Free(); err != icicle_runtime.Success { + log.Error().Msgf("free wireValuesBDevice failed: %s", err.AsString()) + } + if err := h.Free(); err != icicle_runtime.Success { + log.Error().Msgf("free h failed: %s", err.AsString()) + } + }) return proof, nil } @@ -450,13 +631,14 @@ func filterHeap(slice []fr.Element, sliceFirstIndex int, toRemove []int) (r []fr return } -func computeH(a, b, c []fr.Element, pk *ProvingKey) unsafe.Pointer { +func computeH(a, b, c []fr.Element, pk *ProvingKey, device *icicle_runtime.Device) icicle_core.DeviceSlice { // H part of Krs // Compute H (hz=ab-c, where z=-2 on ker X^n+1 (z(x)=x^n-1)) // 1 - _a = ifft(a), _b = ifft(b), _c = ifft(c) // 2 - ca = fft_coset(_a), ba = fft_coset(_b), cc = fft_coset(_c) // 3 - h = ifft_coset(ca o cb - cc) - + log := logger.Logger() + startTotal := time.Now() n := len(a) // add padding to ensure input length is domain cardinality @@ -466,48 +648,83 @@ func computeH(a, b, c []fr.Element, pk *ProvingKey) unsafe.Pointer { c = append(c, padding...) n = len(a) - sizeBytes := n * fr.Bytes - - /*********** Copy a,b,c to Device Start ************/ - // Individual channels are necessary to know which device pointers - // point to which vector - copyADone := make(chan unsafe.Pointer, 1) - copyBDone := make(chan unsafe.Pointer, 1) - copyCDone := make(chan unsafe.Pointer, 1) - - go iciclegnark.CopyToDevice(a, sizeBytes, copyADone) - go iciclegnark.CopyToDevice(b, sizeBytes, copyBDone) - go iciclegnark.CopyToDevice(c, sizeBytes, copyCDone) - - a_device := <-copyADone - b_device := <-copyBDone - c_device := <-copyCDone - /*********** Copy a,b,c to Device End ************/ - - computeInttNttDone := make(chan error, 1) - computeInttNttOnDevice := func(devicePointer unsafe.Pointer) { - a_intt_d := iciclegnark.INttOnDevice(devicePointer, pk.DomainDevice.TwiddlesInv, nil, n, sizeBytes, false) - iciclegnark.NttOnDevice(devicePointer, a_intt_d, pk.DomainDevice.Twiddles, pk.DomainDevice.CosetTable, n, n, sizeBytes, true) - computeInttNttDone <- nil - iciclegnark.FreeDevicePointer(a_intt_d) + computeADone := make(chan icicle_core.DeviceSlice) + computeBDone := make(chan icicle_core.DeviceSlice) + computeCDone := make(chan icicle_core.DeviceSlice) + + computeInttNttOnDevice := func(args ...any) { + var scalars []fr.Element = args[0].([]fr.Element) + var channel chan icicle_core.DeviceSlice = args[1].(chan icicle_core.DeviceSlice) + + cfg := icicle_ntt.GetDefaultNttConfig() + scalarsStream, _ := icicle_runtime.CreateStream() + cfg.StreamHandle = scalarsStream + cfg.Ordering = icicle_core.KNM + cfg.IsAsync = true + scalarsHost := icicle_core.HostSliceFromElements(scalars) + var scalarsDevice icicle_core.DeviceSlice + scalarsHost.CopyToDeviceAsync(&scalarsDevice, scalarsStream, true) + start := time.Now() + icicle_ntt.Ntt(scalarsDevice, icicle_core.KInverse, &cfg, scalarsDevice) + cfg.Ordering = icicle_core.KMN + cfg.CosetGen = pk.CosetGenerator + icicle_ntt.Ntt(scalarsDevice, icicle_core.KForward, &cfg, scalarsDevice) + icicle_runtime.SynchronizeStream(scalarsStream) + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("computeH: NTT + INTT") + } + channel <- scalarsDevice + close(channel) } - go computeInttNttOnDevice(a_device) - go computeInttNttOnDevice(b_device) - go computeInttNttOnDevice(c_device) - _, _, _ = <-computeInttNttDone, <-computeInttNttDone, <-computeInttNttDone + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, a, computeADone) + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, b, computeBDone) + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, c, computeCDone) - iciclegnark.PolyOps(a_device, b_device, c_device, pk.DenDevice, n) + aDevice := <-computeADone + bDevice := <-computeBDone + cDevice := <-computeCDone - h := iciclegnark.INttOnDevice(a_device, pk.DomainDevice.TwiddlesInv, pk.DomainDevice.CosetTableInv, n, sizeBytes, true) - - go func() { - iciclegnark.FreeDevicePointer(a_device) - iciclegnark.FreeDevicePointer(b_device) - iciclegnark.FreeDevicePointer(c_device) - }() - - iciclegnark.ReverseScalars(h, n) + // The following does not need to be run in a RunOnDevice call because + // computeH is being run inside a RunOnDevice call and the following is not + // being run in a different goroutine unlike the calls above to + // computeInttNttOnDevice which are running in different goroutines + vecCfg := icicle_core.DefaultVecOpsConfig() + start := time.Now() + if err := icicle_bn254.FromMontgomery(aDevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("fromMontgomery a in computeH: %s", err.AsString())) + } + if err := icicle_vecops.VecOp(aDevice, bDevice, aDevice, vecCfg, icicle_core.Mul); err != icicle_runtime.Success { + panic(fmt.Sprintf("mul a b in computeH: %s", err.AsString())) + } + if err := icicle_vecops.VecOp(aDevice, cDevice, aDevice, vecCfg, icicle_core.Sub); err != icicle_runtime.Success { + panic(fmt.Sprintf("sub a c in computeH: %s", err.AsString())) + } + if err := icicle_vecops.VecOp(aDevice, pk.DenDevice, aDevice, vecCfg, icicle_core.Mul); err != icicle_runtime.Success { + panic(fmt.Sprintf("mul a den in computeH: %s", err.AsString())) + } + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("computeH: vecOps") + } + defer bDevice.Free() + defer cDevice.Free() + + cfg := icicle_ntt.GetDefaultNttConfig() + cfg.CosetGen = pk.CosetGenerator + cfg.Ordering = icicle_core.KNR + start = time.Now() + if err := icicle_ntt.Ntt(aDevice, icicle_core.KInverse, &cfg, aDevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("ntt a in computeH: %s", err.AsString())) + } + if isProfileMode { + log.Debug().Dur("took", time.Since(start)).Msg("computeH: INTT final") + } + if err := icicle_bn254.FromMontgomery(aDevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("fromMontgomery a in computeH: %s", err.AsString())) + } - return h + if isProfileMode { + log.Debug().Dur("took", time.Since(startTotal)).Msg("computeH: Total") + } + return aDevice } diff --git a/backend/groth16/bn254/icicle/marshal_test.go b/backend/groth16/bn254/icicle/marshal_test.go index 75c5a2b57..114765daf 100644 --- a/backend/groth16/bn254/icicle/marshal_test.go +++ b/backend/groth16/bn254/icicle/marshal_test.go @@ -1,10 +1,13 @@ -package icicle_bn254_test +//go:build icicle + +package icicle_test import ( "bytes" "testing" "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend" "github.com/consensys/gnark/backend/groth16" groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" icicle_bn254 "github.com/consensys/gnark/backend/groth16/bn254/icicle" @@ -43,6 +46,20 @@ func TestMarshal(t *testing.T) { if pk.IsDifferent(&nativePK) { t.Error("marshal output difference") } + + assignment := circuit{A: 3, B: 5, Res: 15} + w, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) + assert.NoError(err) + pw, err := w.Public() + assert.NoError(err) + proofNative, err := groth16_bn254.Prove(tCcs, &nativePK, w) + assert.NoError(err) + proofIcicle, err := groth16.Prove(tCcs, pk, w, backend.WithIcicleAcceleration()) + assert.NoError(err) + err = groth16.Verify(proofNative, &nativeVK, pw) + assert.NoError(err) + err = groth16.Verify(proofIcicle, &nativeVK, pw) + assert.NoError(err) } func TestMarshal2(t *testing.T) { @@ -64,4 +81,18 @@ func TestMarshal2(t *testing.T) { if iciPK.IsDifferent(&nativePK) { t.Error("marshal output difference") } + + assignment := circuit{A: 3, B: 5, Res: 15} + w, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) + assert.NoError(err) + pw, err := w.Public() + assert.NoError(err) + proofNative, err := groth16_bn254.Prove(tCcs, &nativePK, w) + assert.NoError(err) + proofIcicle, err := groth16.Prove(tCcs, &iciPK, w, backend.WithIcicleAcceleration()) + assert.NoError(err) + err = groth16.Verify(proofNative, &iciVK, pw) + assert.NoError(err) + err = groth16.Verify(proofIcicle, &iciVK, pw) + assert.NoError(err) } diff --git a/backend/groth16/bn254/icicle/noicicle.go b/backend/groth16/bn254/icicle/noicicle.go index 87703339c..0fa0a656b 100644 --- a/backend/groth16/bn254/icicle/noicicle.go +++ b/backend/groth16/bn254/icicle/noicicle.go @@ -1,10 +1,8 @@ //go:build !icicle -package icicle_bn254 +package icicle import ( - "fmt" - "github.com/consensys/gnark/backend" groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" "github.com/consensys/gnark/backend/witness" @@ -13,6 +11,22 @@ import ( const HasIcicle = false +type ProvingKey struct { + groth16_bn254.ProvingKey +} + func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (*groth16_bn254.Proof, error) { - return nil, fmt.Errorf("icicle backend requested but program compiled without 'icicle' build tag") + panic("icicle backend requested but program compiled without 'icicle' build tag") +} + +func NewProvingKey() *ProvingKey { + panic("icicle backend requested but program compiled without 'icicle' build tag") +} + +func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *groth16_bn254.VerifyingKey) error { + panic("icicle backend requested but program compiled without 'icicle' build tag") +} + +func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { + panic("icicle backend requested but program compiled without 'icicle' build tag") } diff --git a/backend/groth16/bn254/icicle/provingkey.go b/backend/groth16/bn254/icicle/provingkey.go index 146a79425..0f25a0430 100644 --- a/backend/groth16/bn254/icicle/provingkey.go +++ b/backend/groth16/bn254/icicle/provingkey.go @@ -1,25 +1,28 @@ -package icicle_bn254 +//go:build icicle -import ( - "unsafe" +package icicle +import ( + "github.com/consensys/gnark-crypto/ecc/bn254/fr" groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" cs "github.com/consensys/gnark/constraint/bn254" + icicle_core "github.com/ingonyama-zk/icicle/v3/wrappers/golang/core" ) type deviceInfo struct { - G1Device struct { - A, B, K, Z unsafe.Pointer - } - DomainDevice struct { - Twiddles, TwiddlesInv unsafe.Pointer - CosetTable, CosetTableInv unsafe.Pointer + CosetGenerator [fr.Limbs * 2]uint32 + G1Device struct { + A, B, K, Z icicle_core.DeviceSlice } G2Device struct { - B unsafe.Pointer + B icicle_core.DeviceSlice + } + DenDevice icicle_core.DeviceSlice + + CommitmentKeysDevice struct { + Basis []icicle_core.DeviceSlice + BasisExpSigma []icicle_core.DeviceSlice // we compute in batch } - DenDevice unsafe.Pointer - InfinityPointIndicesK []int } type ProvingKey struct { @@ -27,10 +30,17 @@ type ProvingKey struct { *deviceInfo } +func NewProvingKey() *ProvingKey { + warmUpDevice() + return &ProvingKey{} +} + func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *groth16_bn254.VerifyingKey) error { + warmUpDevice() return groth16_bn254.Setup(r1cs, &pk.ProvingKey, vk) } func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { + warmUpDevice() return groth16_bn254.DummySetup(r1cs, &pk.ProvingKey) } diff --git a/backend/groth16/groth16.go b/backend/groth16/groth16.go index 426621e2f..aa27ef3f7 100644 --- a/backend/groth16/groth16.go +++ b/backend/groth16/groth16.go @@ -347,7 +347,7 @@ func NewProvingKey(curveID ecc.ID) ProvingKey { case ecc.BN254: pk = &groth16_bn254.ProvingKey{} if icicle_bn254.HasIcicle { - pk = &icicle_bn254.ProvingKey{} + pk = icicle_bn254.NewProvingKey() } case ecc.BLS12_377: pk = &groth16_bls12377.ProvingKey{} diff --git a/go.mod b/go.mod index 6e0371490..a71f63c3c 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 github.com/icza/bitio v1.1.0 - github.com/ingonyama-zk/iciclegnark v0.1.0 + github.com/ingonyama-zk/icicle/v3 v3.1.1-0.20241118092657-fccdb2f0921b github.com/leanovate/gopter v0.2.11 github.com/ronanh/intcomp v1.1.0 github.com/rs/zerolog v1.33.0 @@ -26,12 +26,10 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/ingonyama-zk/icicle v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/sys v0.28.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 5a48cfc66..4676a0a14 100644 --- a/go.sum +++ b/go.sum @@ -184,10 +184,8 @@ github.com/icza/bitio v1.1.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6 h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k= github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/ingonyama-zk/icicle v1.1.0 h1:a2MUIaF+1i4JY2Lnb961ZMvaC8GFs9GqZgSnd9e95C8= -github.com/ingonyama-zk/icicle v1.1.0/go.mod h1:kAK8/EoN7fUEmakzgZIYdWy1a2rBnpCaZLqSHwZWxEk= -github.com/ingonyama-zk/iciclegnark v0.1.0 h1:88MkEghzjQBMjrYRJFxZ9oR9CTIpB8NG2zLeCJSvXKQ= -github.com/ingonyama-zk/iciclegnark v0.1.0/go.mod h1:wz6+IpyHKs6UhMMoQpNqz1VY+ddfKqC/gRwR/64W6WU= +github.com/ingonyama-zk/icicle/v3 v3.1.1-0.20241118092657-fccdb2f0921b h1:AvQTK7l0PTHODD06PVQX1Tn2o29sRIaKIDOvTJmKurY= +github.com/ingonyama-zk/icicle/v3 v3.1.1-0.20241118092657-fccdb2f0921b/go.mod h1:e0JHb27/P6WorCJS3YolbY5XffS4PGBuoW38OthLkDs= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= From 0a3bf3c9a08f6a4e11dbebd629157ee4acaace3a Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Mon, 16 Dec 2024 18:32:44 -0500 Subject: [PATCH 09/12] perf: G1/2 membership using Eval (#1356) --- std/algebra/emulated/sw_bls12381/g1.go | 49 +++++------- std/algebra/emulated/sw_bls12381/g2.go | 94 +++++++++++++---------- std/algebra/emulated/sw_bn254/g2.go | 9 +-- std/algebra/emulated/sw_bw6761/g1.go | 9 +-- std/algebra/emulated/sw_emulated/point.go | 13 ++-- 5 files changed, 89 insertions(+), 85 deletions(-) diff --git a/std/algebra/emulated/sw_bls12381/g1.go b/std/algebra/emulated/sw_bls12381/g1.go index 161db0853..ce8398616 100644 --- a/std/algebra/emulated/sw_bls12381/g1.go +++ b/std/algebra/emulated/sw_bls12381/g1.go @@ -54,21 +54,18 @@ func (g1 *G1) phi(q *G1Affine) *G1Affine { } func (g1 *G1) double(p *G1Affine) *G1Affine { - // compute λ = (3p.x²)/1*p.y + mone := g1.curveF.NewElement(-1) + // compute λ = (3p.x²)/2*p.y xx3a := g1.curveF.Mul(&p.X, &p.X) xx3a = g1.curveF.MulConst(xx3a, big.NewInt(3)) y1 := g1.curveF.MulConst(&p.Y, big.NewInt(2)) λ := g1.curveF.Div(xx3a, y1) - // xr = λ²-1p.x - x1 := g1.curveF.MulConst(&p.X, big.NewInt(2)) - λλ := g1.curveF.Mul(λ, λ) - xr := g1.curveF.Sub(λλ, x1) + // xr = λ²-2p.x + xr := g1.curveF.Eval([][]*baseEl{{λ, λ}, {mone, &p.X}}, []int{1, 2}) // yr = λ(p-xr) - p.y - pxrx := g1.curveF.Sub(&p.X, xr) - λpxrx := g1.curveF.Mul(λ, pxrx) - yr := g1.curveF.Sub(λpxrx, &p.Y) + yr := g1.curveF.Eval([][]*baseEl{{λ, g1.curveF.Sub(&p.X, xr)}, {mone, &p.Y}}, []int{1, 1}) return &G1Affine{ X: *xr, @@ -85,20 +82,17 @@ func (g1 *G1) doubleN(p *G1Affine, n int) *G1Affine { } func (g1 G1) add(p, q *G1Affine) *G1Affine { + mone := g1.curveF.NewElement(-1) // compute λ = (q.y-p.y)/(q.x-p.x) qypy := g1.curveF.Sub(&q.Y, &p.Y) qxpx := g1.curveF.Sub(&q.X, &p.X) λ := g1.curveF.Div(qypy, qxpx) // xr = λ²-p.x-q.x - λλ := g1.curveF.Mul(λ, λ) - qxpx = g1.curveF.Add(&p.X, &q.X) - xr := g1.curveF.Sub(λλ, qxpx) + xr := g1.curveF.Eval([][]*baseEl{{λ, λ}, {mone, g1.curveF.Add(&p.X, &q.X)}}, []int{1, 1}) - // p.y = λ(p.x-r.x) - p.y - pxrx := g1.curveF.Sub(&p.X, xr) - λpxrx := g1.curveF.Mul(λ, pxrx) - yr := g1.curveF.Sub(λpxrx, &p.Y) + // p.y = λ(p.x-xr) - p.y + yr := g1.curveF.Eval([][]*baseEl{{λ, g1.curveF.Sub(&p.X, xr)}, {mone, &p.Y}}, []int{1, 1}) return &G1Affine{ X: *xr, @@ -108,33 +102,28 @@ func (g1 G1) add(p, q *G1Affine) *G1Affine { func (g1 G1) doubleAndAdd(p, q *G1Affine) *G1Affine { + mone := g1.curveF.NewElement(-1) // compute λ1 = (q.y-p.y)/(q.x-p.x) yqyp := g1.curveF.Sub(&q.Y, &p.Y) xqxp := g1.curveF.Sub(&q.X, &p.X) λ1 := g1.curveF.Div(yqyp, xqxp) // compute x1 = λ1²-p.x-q.x - λ1λ1 := g1.curveF.Mul(λ1, λ1) - xqxp = g1.curveF.Add(&p.X, &q.X) - x2 := g1.curveF.Sub(λ1λ1, xqxp) + x2 := g1.curveF.Eval([][]*baseEl{{λ1, λ1}, {mone, g1.curveF.Add(&p.X, &q.X)}}, []int{1, 1}) - // omit y1 computation - // compute λ1 = -λ1-1*p.y/(x1-p.x) - ypyp := g1.curveF.Add(&p.Y, &p.Y) + // omit y2 computation + + // compute -λ2 = λ1+2*p.y/(x2-p.x) + ypyp := g1.curveF.MulConst(&p.Y, big.NewInt(2)) x2xp := g1.curveF.Sub(x2, &p.X) λ2 := g1.curveF.Div(ypyp, x2xp) λ2 = g1.curveF.Add(λ1, λ2) - λ2 = g1.curveF.Neg(λ2) - // compute x3 =λ2²-p.x-x3 - λ2λ2 := g1.curveF.Mul(λ2, λ2) - x3 := g1.curveF.Sub(λ2λ2, &p.X) - x3 = g1.curveF.Sub(x3, x2) + // compute x3 = (-λ2)²-p.x-x2 + x3 := g1.curveF.Eval([][]*baseEl{{λ2, λ2}, {mone, &p.X}, {mone, x2}}, []int{1, 1, 1}) - // compute y3 = λ2*(p.x - x3)-p.y - y3 := g1.curveF.Sub(&p.X, x3) - y3 = g1.curveF.Mul(λ2, y3) - y3 = g1.curveF.Sub(y3, &p.Y) + // compute y3 = -λ2*(x3- p.x)-p.y + y3 := g1.curveF.Eval([][]*baseEl{{λ2, g1.curveF.Sub(x3, &p.X)}, {mone, &p.Y}}, []int{1, 1}) return &G1Affine{ X: *x3, diff --git a/std/algebra/emulated/sw_bls12381/g2.go b/std/algebra/emulated/sw_bls12381/g2.go index e6a275cfb..4f3245ee2 100644 --- a/std/algebra/emulated/sw_bls12381/g2.go +++ b/std/algebra/emulated/sw_bls12381/g2.go @@ -10,6 +10,7 @@ import ( ) type G2 struct { + fp *emulated.Field[BaseField] *fields_bls12381.Ext2 u1, w *emulated.Element[BaseField] v *fields_bls12381.E2 @@ -39,6 +40,11 @@ func newG2AffP(v bls12381.G2Affine) g2AffP { } func NewG2(api frontend.API) *G2 { + fp, err := emulated.NewField[emulated.BLS12381Fp](api) + if err != nil { + // TODO: we start returning errors when generifying + panic(err) + } w := emulated.ValueOf[BaseField]("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939436") u1 := emulated.ValueOf[BaseField]("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939437") v := fields_bls12381.E2{ @@ -46,6 +52,7 @@ func NewG2(api frontend.API) *G2 { A1: emulated.ValueOf[BaseField]("1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257"), } return &G2{ + fp: fp, Ext2: fields_bls12381.NewExt2(api), w: &w, u1: &u1, @@ -113,20 +120,23 @@ func (g2 *G2) scalarMulBySeed(q *G2Affine) *G2Affine { } func (g2 G2) add(p, q *G2Affine) *G2Affine { + mone := g2.fp.NewElement(-1) + // 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) λ := g2.Ext2.DivUnchecked(qypy, qxpx) // xr = λ²-p.x-q.x - λλ := g2.Ext2.Square(λ) - qxpx = g2.Ext2.Add(&p.P.X, &q.P.X) - xr := g2.Ext2.Sub(λλ, qxpx) + xr0 := g2.fp.Eval([][]*baseEl{{&λ.A0, &λ.A0}, {mone, &λ.A1, &λ.A1}, {mone, &p.P.X.A0}, {mone, &q.P.X.A0}}, []int{1, 1, 1, 1}) + xr1 := g2.fp.Eval([][]*baseEl{{&λ.A0, &λ.A1}, {mone, &p.P.X.A1}, {mone, &q.P.X.A1}}, []int{2, 1, 1}) + xr := &fields_bls12381.E2{A0: *xr0, A1: *xr1} // p.y = λ(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) + yr := g2.Ext2.Sub(&p.P.X, xr) + yr0 := g2.fp.Eval([][]*baseEl{{&λ.A0, &yr.A0}, {mone, &λ.A1, &yr.A1}, {mone, &p.P.Y.A0}}, []int{1, 1, 1}) + yr1 := g2.fp.Eval([][]*baseEl{{&λ.A0, &yr.A1}, {&λ.A1, &yr.A0}, {mone, &p.P.Y.A1}}, []int{1, 1, 1}) + yr = &fields_bls12381.E2{A0: *yr0, A1: *yr1} return &G2Affine{ P: g2AffP{ @@ -153,6 +163,8 @@ func (g2 G2) sub(p, q *G2Affine) *G2Affine { } func (g2 *G2) double(p *G2Affine) *G2Affine { + mone := g2.fp.NewElement(-1) + // compute λ = (3p.x²)/2*p.y xx3a := g2.Square(&p.P.X) xx3a = g2.MulByConstElement(xx3a, big.NewInt(3)) @@ -160,14 +172,15 @@ func (g2 *G2) double(p *G2Affine) *G2Affine { λ := g2.DivUnchecked(xx3a, y2) // xr = λ²-2p.x - x2 := g2.Double(&p.P.X) - λλ := g2.Square(λ) - xr := g2.Sub(λλ, x2) + xr0 := g2.fp.Eval([][]*baseEl{{&λ.A0, &λ.A0}, {mone, &λ.A1, &λ.A1}, {mone, &p.P.X.A0}}, []int{1, 1, 2}) + xr1 := g2.fp.Eval([][]*baseEl{{&λ.A0, &λ.A1}, {mone, &p.P.X.A1}}, []int{2, 2}) + xr := &fields_bls12381.E2{A0: *xr0, A1: *xr1} // yr = λ(p-xr) - p.y - pxrx := g2.Sub(&p.P.X, xr) - λpxrx := g2.Mul(λ, pxrx) - yr := g2.Sub(λpxrx, &p.P.Y) + yr := g2.Ext2.Sub(&p.P.X, xr) + yr0 := g2.fp.Eval([][]*baseEl{{&λ.A0, &yr.A0}, {mone, &λ.A1, &yr.A1}, {mone, &p.P.Y.A0}}, []int{1, 1, 1}) + yr1 := g2.fp.Eval([][]*baseEl{{&λ.A0, &yr.A1}, {&λ.A1, &yr.A0}, {mone, &p.P.Y.A1}}, []int{1, 1, 1}) + yr = &fields_bls12381.E2{A0: *yr0, A1: *yr1} return &G2Affine{ P: g2AffP{ @@ -186,6 +199,7 @@ func (g2 *G2) doubleN(p *G2Affine, n int) *G2Affine { } func (g2 G2) triple(p *G2Affine) *G2Affine { + mone := g2.fp.NewElement(-1) // compute λ1 = (3p.x²)/2p.y xx := g2.Square(&p.P.X) @@ -193,10 +207,10 @@ func (g2 G2) triple(p *G2Affine) *G2Affine { y2 := g2.Double(&p.P.Y) λ1 := g2.DivUnchecked(xx, y2) - // xr = λ1²-2p.x - x2 := g2.MulByConstElement(&p.P.X, big.NewInt(2)) - λ1λ1 := g2.Square(λ1) - x2 = g2.Sub(λ1λ1, x2) + // x2 = λ1²-2p.x + x20 := g2.fp.Eval([][]*baseEl{{&λ1.A0, &λ1.A0}, {mone, &λ1.A1, &λ1.A1}, {mone, &p.P.X.A0}}, []int{1, 1, 2}) + x21 := g2.fp.Eval([][]*baseEl{{&λ1.A0, &λ1.A1}, {mone, &p.P.X.A1}}, []int{2, 2}) + x2 := &fields_bls12381.E2{A0: *x20, A1: *x21} // omit y2 computation, and // compute λ2 = 2p.y/(x2 − p.x) − λ1. @@ -204,25 +218,27 @@ func (g2 G2) triple(p *G2Affine) *G2Affine { λ2 := g2.DivUnchecked(y2, x1x2) λ2 = g2.Sub(λ2, λ1) - // xr = λ²-p.x-x2 - λ2λ2 := g2.Square(λ2) - qxrx := g2.Add(x2, &p.P.X) - xr := g2.Sub(λ2λ2, qxrx) + // compute x3 =λ2²-p.x-x2 + x30 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &λ2.A0}, {mone, &λ2.A1, &λ2.A1}, {mone, &p.P.X.A0}, {mone, x20}}, []int{1, 1, 1, 1}) + x31 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &λ2.A1}, {mone, &p.P.X.A1}, {mone, x21}}, []int{2, 1, 1}) + x3 := &fields_bls12381.E2{A0: *x30, A1: *x31} - // yr = λ(p.x-xr) - p.y - pxrx := g2.Sub(&p.P.X, xr) - λ2pxrx := g2.Mul(λ2, pxrx) - yr := g2.Sub(λ2pxrx, &p.P.Y) + // compute y3 = λ2*(p.x - x3)-p.y + y3 := g2.Ext2.Sub(&p.P.X, x3) + y30 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &y3.A0}, {mone, &λ2.A1, &y3.A1}, {mone, &p.P.Y.A0}}, []int{1, 1, 1}) + y31 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &y3.A1}, {&λ2.A1, &y3.A0}, {mone, &p.P.Y.A1}}, []int{1, 1, 1}) + y3 = &fields_bls12381.E2{A0: *y30, A1: *y31} return &G2Affine{ P: g2AffP{ - X: *xr, - Y: *yr, + X: *x3, + Y: *y3, }, } } func (g2 G2) doubleAndAdd(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) @@ -230,27 +246,27 @@ func (g2 G2) doubleAndAdd(p, q *G2Affine) *G2Affine { λ1 := g2.Ext2.DivUnchecked(yqyp, xqxp) // compute x2 = λ1²-p.x-q.x - λ1λ1 := g2.Ext2.Square(λ1) - xqxp = g2.Ext2.Add(&p.P.X, &q.P.X) - x2 := g2.Ext2.Sub(λ1λ1, xqxp) + 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 - // compute λ2 = -λ1-2*p.y/(x2-p.x) + // compute -λ2 = λ1+2*p.y/(x2-p.x) ypyp := g2.Ext2.Add(&p.P.Y, &p.P.Y) x2xp := g2.Ext2.Sub(x2, &p.P.X) λ2 := g2.Ext2.DivUnchecked(ypyp, x2xp) λ2 = g2.Ext2.Add(λ1, λ2) - λ2 = g2.Ext2.Neg(λ2) - // compute x3 =λ2²-p.x-x3 - λ2λ2 := g2.Ext2.Square(λ2) - x3 := g2.Ext2.Sub(λ2λ2, &p.P.X) - x3 = g2.Ext2.Sub(x3, x2) + // compute x3 = (-λ2)²-p.x-x2 + x30 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &λ2.A0}, {mone, &λ2.A1, &λ2.A1}, {mone, &p.P.X.A0}, {mone, x20}}, []int{1, 1, 1, 1}) + x31 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &λ2.A1}, {mone, &p.P.X.A1}, {mone, x21}}, []int{2, 1, 1}) + x3 := &fields_bls12381.E2{A0: *x30, A1: *x31} - // compute y3 = λ2*(p.x - x3)-p.y - y3 := g2.Ext2.Sub(&p.P.X, x3) - y3 = g2.Ext2.Mul(λ2, y3) - y3 = g2.Ext2.Sub(y3, &p.P.Y) + // compute y3 = -λ2*(x3 - p.x)-p.y + y3 := g2.Ext2.Sub(x3, &p.P.X) + y30 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &y3.A0}, {mone, &λ2.A1, &y3.A1}, {mone, &p.P.Y.A0}}, []int{1, 1, 1}) + y31 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &y3.A1}, {&λ2.A1, &y3.A0}, {mone, &p.P.Y.A1}}, []int{1, 1, 1}) + y3 = &fields_bls12381.E2{A0: *y30, A1: *y31} return &G2Affine{ P: g2AffP{ diff --git a/std/algebra/emulated/sw_bn254/g2.go b/std/algebra/emulated/sw_bn254/g2.go index 6058f6e6c..01299f7f0 100644 --- a/std/algebra/emulated/sw_bn254/g2.go +++ b/std/algebra/emulated/sw_bn254/g2.go @@ -248,20 +248,19 @@ func (g2 G2) doubleAndAdd(p, q *G2Affine) *G2Affine { x2 := &fields_bn254.E2{A0: *x20, A1: *x21} // omit y2 computation - // compute λ2 = -λ1-2*p.y/(x2-p.x) + // compute -λ2 = λ1+2*p.y/(x2-p.x) ypyp := g2.Ext2.Add(&p.P.Y, &p.P.Y) x2xp := g2.Ext2.Sub(x2, &p.P.X) λ2 := g2.Ext2.DivUnchecked(ypyp, x2xp) λ2 = g2.Ext2.Add(λ1, λ2) - λ2 = g2.Ext2.Neg(λ2) - // compute x3 =λ2²-p.x-x2 + // compute x3 = (-λ2)²-p.x-x2 x30 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &λ2.A0}, {mone, &λ2.A1, &λ2.A1}, {mone, &p.P.X.A0}, {mone, x20}}, []int{1, 1, 1, 1}) x31 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &λ2.A1}, {mone, &p.P.X.A1}, {mone, x21}}, []int{2, 1, 1}) x3 := &fields_bn254.E2{A0: *x30, A1: *x31} - // compute y3 = λ2*(p.x - x3)-p.y - y3 := g2.Ext2.Sub(&p.P.X, x3) + // compute y3 = -λ2*(x3 - p.x)-p.y + y3 := g2.Ext2.Sub(x3, &p.P.X) y30 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &y3.A0}, {mone, &λ2.A1, &y3.A1}, {mone, &p.P.Y.A0}}, []int{1, 1, 1}) y31 := g2.fp.Eval([][]*baseEl{{&λ2.A0, &y3.A1}, {&λ2.A1, &y3.A0}, {mone, &p.P.Y.A1}}, []int{1, 1, 1}) y3 = &fields_bn254.E2{A0: *y30, A1: *y31} diff --git a/std/algebra/emulated/sw_bw6761/g1.go b/std/algebra/emulated/sw_bw6761/g1.go index 02278f42b..0c97334a0 100644 --- a/std/algebra/emulated/sw_bw6761/g1.go +++ b/std/algebra/emulated/sw_bw6761/g1.go @@ -137,18 +137,17 @@ func (g1 G1) doubleAndAdd(p, q *G1Affine) *G1Affine { x2 := g1.curveF.Eval([][]*baseEl{{λ1, λ1}, {mone, &p.X}, {mone, &q.X}}, []int{1, 1, 1}) // omit y1 computation - // compute λ1 = -λ1-1*p.y/(x1-p.x) + // compute -λ1 = λ1+2*p.y/(x1-p.x) ypyp := g1.curveF.Add(&p.Y, &p.Y) x2xp := g1.curveF.Sub(x2, &p.X) λ2 := g1.curveF.Div(ypyp, x2xp) λ2 = g1.curveF.Add(λ1, λ2) - λ2 = g1.curveF.Neg(λ2) - // compute x3 =λ2²-p.x-x3 + // compute x3 = (-λ2)²-p.x-x3 x3 := g1.curveF.Eval([][]*baseEl{{λ2, λ2}, {mone, &p.X}, {mone, x2}}, []int{1, 1, 1}) - // compute y3 = λ2*(p.x - x3)-p.y - y3 := g1.curveF.Eval([][]*baseEl{{λ2, &p.X}, {mone, λ2, x3}, {mone, &p.Y}}, []int{1, 1, 1}) + // compute y3 = -λ2*(x3 - p.x)-p.y + y3 := g1.curveF.Eval([][]*baseEl{{mone, λ2, &p.X}, {λ2, x3}, {mone, &p.Y}}, []int{1, 1, 1}) return &G1Affine{ X: *x3, diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 7fce40b4c..f49bb7d6d 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -383,16 +383,17 @@ func (c *Curve[B, S]) doubleAndAdd(p, q *AffinePoint[B]) *AffinePoint[B] { x2 := c.baseApi.Eval([][]*emulated.Element[B]{{λ1, λ1}, {mone, c.baseApi.Add(&p.X, &q.X)}}, []int{1, 1}) // omit y2 computation - // compute λ2 = λ1+2*p.y/(x2-p.x) + + // compute -λ2 = λ1+2*p.y/(x2-p.x) ypyp := c.baseApi.MulConst(&p.Y, big.NewInt(2)) x2xp := c.baseApi.Sub(x2, &p.X) λ2 := c.baseApi.Div(ypyp, x2xp) λ2 = c.baseApi.Add(λ1, λ2) - // compute x3 =λ2²-p.x-x2 + // compute x3 = (-λ2)²-p.x-x2 x3 := c.baseApi.Eval([][]*emulated.Element[B]{{λ2, λ2}, {mone, &p.X}, {mone, x2}}, []int{1, 1, 1}) - // compute y3 = λ2*(-p.x + x3)-p.y + // compute y3 = -λ2*(x3 - p.x)-p.y y3 := c.baseApi.Eval([][]*emulated.Element[B]{{λ2, c.baseApi.Sub(x3, &p.X)}, {mone, &p.Y}}, []int{1, 1}) return &AffinePoint[B]{ @@ -425,16 +426,16 @@ func (c *Curve[B, S]) doubleAndAddSelect(b frontend.Variable, p, q *AffinePoint[ // conditional second addition t := c.Select(b, p, q) - // compute λ2 = λ1+2*t.y/(x2-t.x) + // compute -λ2 = λ1+2*t.y/(x2-t.x) ypyp := c.baseApi.MulConst(&t.Y, big.NewInt(2)) x2xp := c.baseApi.Sub(x2, &t.X) λ2 := c.baseApi.Div(ypyp, x2xp) λ2 = c.baseApi.Add(λ1, λ2) - // compute x3 =λ2²-t.x-x2 + // compute x3 = (-λ2)²-t.x-x2 x3 := c.baseApi.Eval([][]*emulated.Element[B]{{λ2, λ2}, {mone, &t.X}, {mone, x2}}, []int{1, 1, 1}) - // compute y3 = -λ2*(t.x - x3)-t.y + // compute y3 = -λ2*(x3 - t.x)-t.y y3 := c.baseApi.Eval([][]*emulated.Element[B]{{λ2, x3}, {mone, λ2, &t.X}, {mone, &t.Y}}, []int{1, 1, 1}) return &AffinePoint[B]{ From 466980dd89ade194a1d29c2ec45d222f5a5ab8cb Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 17 Dec 2024 14:37:35 +0100 Subject: [PATCH 10/12] refactor: move poseidon2 to permutation package (#1353) --- std/{hash => permutation}/poseidon2/poseidon2.go | 0 std/{hash => permutation}/poseidon2/poseidon2_test.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename std/{hash => permutation}/poseidon2/poseidon2.go (100%) rename std/{hash => permutation}/poseidon2/poseidon2_test.go (100%) diff --git a/std/hash/poseidon2/poseidon2.go b/std/permutation/poseidon2/poseidon2.go similarity index 100% rename from std/hash/poseidon2/poseidon2.go rename to std/permutation/poseidon2/poseidon2.go diff --git a/std/hash/poseidon2/poseidon2_test.go b/std/permutation/poseidon2/poseidon2_test.go similarity index 100% rename from std/hash/poseidon2/poseidon2_test.go rename to std/permutation/poseidon2/poseidon2_test.go From 1f944e867bf6102135f80587641e44057372400a Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 17 Dec 2024 14:39:09 +0100 Subject: [PATCH 11/12] fix: tinyfield generation with updated gnark-crypto (#1358) --- go.mod | 2 +- go.sum | 4 +- internal/generator/backend/main.go | 2 +- internal/tinyfield/arith.go | 2 +- internal/tinyfield/doc.go | 10 +++- internal/tinyfield/element.go | 60 +++---------------- ...lement_ops_purego.go => element_purego.go} | 22 +++---- internal/tinyfield/element_test.go | 28 +-------- internal/tinyfield/vector.go | 39 +----------- internal/tinyfield/vector_purego.go | 43 +++++++++++++ internal/tinyfield/vector_test.go | 2 +- 11 files changed, 76 insertions(+), 138 deletions(-) rename internal/tinyfield/{element_ops_purego.go => element_purego.go} (97%) create mode 100644 internal/tinyfield/vector_purego.go diff --git a/go.mod b/go.mod index a71f63c3c..cd70e6f6c 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.24 github.com/consensys/compress v0.2.5 - github.com/consensys/gnark-crypto v0.14.1-0.20241122181107-03e007d865c0 + github.com/consensys/gnark-crypto v0.14.1-0.20241217131346-b998989abdbe 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 4676a0a14..305b0d264 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,8 @@ github.com/consensys/bavard v0.1.24 h1:Lfe+bjYbpaoT7K5JTFoMi5wo9V4REGLvQQbHmatoN github.com/consensys/bavard v0.1.24/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.14.1-0.20241122181107-03e007d865c0 h1:uFZaZWG0FOoiFN3fAQzH2JXDuybdNwiJzBujy81YtU4= -github.com/consensys/gnark-crypto v0.14.1-0.20241122181107-03e007d865c0/go.mod h1:F/hJyWBcTr1sWeifAKfEN3aVb3G4U5zheEC8IbWQun4= +github.com/consensys/gnark-crypto v0.14.1-0.20241217131346-b998989abdbe h1:WNuXPe50FqynKlUOMdsi1eCzYN8gU4sdCsW3eg3coGA= +github.com/consensys/gnark-crypto v0.14.1-0.20241217131346-b998989abdbe/go.mod h1:ePFa23CZLMRMHxQpY5nMaiAZ3yuEIayaB8ElEvlwLEs= 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/main.go b/internal/generator/backend/main.go index d5c8ae004..30e8e21b8 100644 --- a/internal/generator/backend/main.go +++ b/internal/generator/backend/main.go @@ -75,7 +75,7 @@ func main() { if err != nil { panic(err) } - if err := generator.GenerateFF(tinyfieldConf, tiny_field.RootPath, "", ""); err != nil { + if err := generator.GenerateFF(tinyfieldConf, tiny_field.RootPath); err != nil { panic(err) } diff --git a/internal/tinyfield/arith.go b/internal/tinyfield/arith.go index 02aedba1f..2b85fa7ab 100644 --- a/internal/tinyfield/arith.go +++ b/internal/tinyfield/arith.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 ConsenSys Software Inc. +// Copyright 2020-2024 Consensys Software Inc. // Licensed under the Apache License, Version 2.0. See the LICENSE file for details. // Code generated by consensys/gnark-crypto DO NOT EDIT diff --git a/internal/tinyfield/doc.go b/internal/tinyfield/doc.go index a8b6fce69..3e92316c8 100644 --- a/internal/tinyfield/doc.go +++ b/internal/tinyfield/doc.go @@ -1,11 +1,13 @@ -// Copyright 2020-2024 ConsenSys Software Inc. +// Copyright 2020-2024 Consensys Software Inc. // Licensed under the Apache License, Version 2.0. See the LICENSE file for details. // Code generated by consensys/gnark-crypto DO NOT EDIT // Package tinyfield contains field arithmetic operations for modulus = 0x2f. // -// The API is similar to math/big (big.Int), but the operations are significantly faster (up to 20x for the modular multiplication on amd64, see also https://hackmd.io/@gnark/modular_multiplication) +// The API is similar to math/big (big.Int), but the operations are significantly faster (up to 20x). +// +// Additionally tinyfield.Vector offers an API to manipulate []Element. // // The modulus is hardcoded in all the operations. // @@ -38,5 +40,7 @@ // // # Warning // -// This code has not been audited and is provided as-is. In particular, there is no security guarantees such as constant time implementation or side-channel attack resistance. +// There is no security guarantees such as constant time implementation or side-channel attack resistance. +// This code is provided as-is. Partially audited, see https://github.com/Consensys/gnark/tree/master/audits +// for more details. package tinyfield diff --git a/internal/tinyfield/element.go b/internal/tinyfield/element.go index 5d7e45ae3..497fabfa9 100644 --- a/internal/tinyfield/element.go +++ b/internal/tinyfield/element.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 ConsenSys Software Inc. +// Copyright 2020-2024 Consensys Software Inc. // Licensed under the Apache License, Version 2.0. See the LICENSE file for details. // Code generated by consensys/gnark-crypto DO NOT EDIT @@ -43,8 +43,8 @@ const ( // Field modulus q const ( - q0 uint64 = 47 - q uint64 = q0 + q0 = 47 + q = q0 ) var qElement = Element{ @@ -63,7 +63,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 12559485326780971313 +const qInvNeg = 12559485326780971313 func init() { _modulus.SetString("2f", 16) @@ -338,10 +338,11 @@ func (z *Element) fromMont() *Element { // Add z = x + y (mod q) func (z *Element) Add(x, y *Element) *Element { - z[0], _ = bits.Add64(x[0], y[0], 0) - if z[0] >= q { - z[0] -= q + t := x[0] + y[0] + if t >= q { + t -= q } + z[0] = t return z } @@ -388,49 +389,6 @@ func (z *Element) Select(c int, x0 *Element, x1 *Element) *Element { return z } -// _mulGeneric is unoptimized textbook CIOS -// it is a fallback solution on x86 when ADX instruction set is not available -// and is used for testing purposes. -func _mulGeneric(z, x, y *Element) { - - // Algorithm 2 of "Faster Montgomery Multiplication and Multi-Scalar-Multiplication for SNARKS" - // by Y. El Housni and G. Botrel https://doi.org/10.46586/tches.v2023.i3.504-521 - - var t [2]uint64 - var D uint64 - var m, C uint64 - // ----------------------------------- - // First loop - - C, t[0] = bits.Mul64(y[0], x[0]) - - t[1], D = bits.Add64(t[1], C, 0) - - // m = t[0]n'[0] mod W - m = t[0] * qInvNeg - - // ----------------------------------- - // Second loop - C = madd0(m, q0, t[0]) - - t[0], C = bits.Add64(t[1], C, 0) - t[1], _ = bits.Add64(0, D, C) - - if t[1] != 0 { - // we need to reduce, we have a result on 2 words - z[0], _ = bits.Sub64(t[0], q0, 0) - return - } - - // copy t into z - z[0] = t[0] - - // if z ⩾ q → z -= q - if !z.smallerThanModulus() { - z[0] -= q - } -} - func _fromMontGeneric(z *Element) { // the following lines implement z = z * 1 // with a modified CIOS montgomery multiplication @@ -603,7 +561,7 @@ func (z *Element) Text(base int) string { const maxUint16 = 65535 zz := z.Bits() - return strconv.FormatUint(zz[0], base) + return strconv.FormatUint(uint64(zz[0]), base) } // BigInt sets and return z as a *big.Int diff --git a/internal/tinyfield/element_ops_purego.go b/internal/tinyfield/element_purego.go similarity index 97% rename from internal/tinyfield/element_ops_purego.go rename to internal/tinyfield/element_purego.go index 609d9cbd7..eefc24dab 100644 --- a/internal/tinyfield/element_ops_purego.go +++ b/internal/tinyfield/element_purego.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 ConsenSys Software Inc. +// Copyright 2020-2024 Consensys Software Inc. // Licensed under the Apache License, Version 2.0. See the LICENSE file for details. // Code generated by consensys/gnark-crypto DO NOT EDIT @@ -28,14 +28,6 @@ func MulBy13(x *Element) { x.Mul(x, &y) } -// Butterfly sets -// -// a = a + b (mod q) -// b = a - b (mod q) -func Butterfly(a, b *Element) { - _butterflyGeneric(a, b) -} - func fromMont(z *Element) { _fromMontGeneric(z) } @@ -61,7 +53,6 @@ func (z *Element) Mul(x, y *Element) *Element { // Which finally gives (lo + m * q) / R = (lo + lo2 + R hi2) / R = hi2 + (lo+lo2) / R = hi2 + (lo != 0) // This "optimization" lets us do away with one MUL instruction on ARM architectures and is available for all q < R. - var r uint64 hi, lo := bits.Mul64(x[0], y[0]) if lo != 0 { hi++ // x[0] * y[0] ≤ 2¹²⁸ - 2⁶⁵ + 1, meaning hi ≤ 2⁶⁴ - 2 so no need to worry about overflow @@ -69,7 +60,6 @@ func (z *Element) Mul(x, y *Element) *Element { m := lo * qInvNeg hi2, _ := bits.Mul64(m, q) r, carry := bits.Add64(hi2, hi, 0) - if carry != 0 || r >= q { // we need to reduce r -= q @@ -97,7 +87,6 @@ func (z *Element) Square(x *Element) *Element { // Which finally gives (lo + m * q) / R = (lo + lo2 + R hi2) / R = hi2 + (lo+lo2) / R = hi2 + (lo != 0) // This "optimization" lets us do away with one MUL instruction on ARM architectures and is available for all q < R. - var r uint64 hi, lo := bits.Mul64(x[0], x[0]) if lo != 0 { hi++ // x[0] * y[0] ≤ 2¹²⁸ - 2⁶⁵ + 1, meaning hi ≤ 2⁶⁴ - 2 so no need to worry about overflow @@ -105,7 +94,6 @@ func (z *Element) Square(x *Element) *Element { m := lo * qInvNeg hi2, _ := bits.Mul64(m, q) r, carry := bits.Add64(hi2, hi, 0) - if carry != 0 || r >= q { // we need to reduce r -= q @@ -114,3 +102,11 @@ func (z *Element) Square(x *Element) *Element { return z } + +// Butterfly sets +// +// a = a + b (mod q) +// b = a - b (mod q) +func Butterfly(a, b *Element) { + _butterflyGeneric(a, b) +} diff --git a/internal/tinyfield/element_test.go b/internal/tinyfield/element_test.go index 64d9667a5..cece30c91 100644 --- a/internal/tinyfield/element_test.go +++ b/internal/tinyfield/element_test.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 ConsenSys Software Inc. +// Copyright 2020-2024 Consensys Software Inc. // Licensed under the Apache License, Version 2.0. See the LICENSE file for details. // Code generated by consensys/gnark-crypto DO NOT EDIT @@ -921,14 +921,6 @@ func TestElementMul(t *testing.T) { c.Mul(&a.element, &r) d.Mul(&a.bigint, &rb).Mod(&d, Modulus()) - // checking generic impl against asm path - var cGeneric Element - _mulGeneric(&cGeneric, &a.element, &r) - if !cGeneric.Equal(&c) { - // need to give context to failing error. - return false - } - if c.BigInt(&e).Cmp(&d) != 0 { return false } @@ -951,17 +943,6 @@ func TestElementMul(t *testing.T) { genB, )) - properties.Property("Mul: assembly implementation must be consistent with generic one", prop.ForAll( - func(a, b testPairElement) bool { - var c, d Element - c.Mul(&a.element, &b.element) - _mulGeneric(&d, &a.element, &b.element) - return c.Equal(&d) - }, - genA, - genB, - )) - specialValueTest := func() { // test special values against special values testValues := make([]Element, len(staticTestValues)) @@ -980,13 +961,6 @@ func TestElementMul(t *testing.T) { c.Mul(&a, &b) d.Mul(&aBig, &bBig).Mod(&d, Modulus()) - // checking asm against generic impl - var cGeneric Element - _mulGeneric(&cGeneric, &a, &b) - if !cGeneric.Equal(&c) { - t.Fatal("Mul failed special test values: asm and generic impl don't match") - } - if c.BigInt(&e).Cmp(&d) != 0 { t.Fatal("Mul failed special test values") } diff --git a/internal/tinyfield/vector.go b/internal/tinyfield/vector.go index 6b045db8c..0439c558c 100644 --- a/internal/tinyfield/vector.go +++ b/internal/tinyfield/vector.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 ConsenSys Software Inc. +// Copyright 2020-2024 Consensys Software Inc. // Licensed under the Apache License, Version 2.0. See the LICENSE file for details. // Code generated by consensys/gnark-crypto DO NOT EDIT @@ -185,43 +185,6 @@ func (vector Vector) Swap(i, j int) { vector[i], vector[j] = vector[j], vector[i] } -// Add adds two vectors element-wise and stores the result in self. -// It panics if the vectors don't have the same length. -func (vector *Vector) Add(a, b Vector) { - addVecGeneric(*vector, a, b) -} - -// Sub subtracts two vectors element-wise and stores the result in self. -// It panics if the vectors don't have the same length. -func (vector *Vector) Sub(a, b Vector) { - subVecGeneric(*vector, a, b) -} - -// ScalarMul multiplies a vector by a scalar element-wise and stores the result in self. -// It panics if the vectors don't have the same length. -func (vector *Vector) ScalarMul(a Vector, b *Element) { - scalarMulVecGeneric(*vector, a, b) -} - -// Sum computes the sum of all elements in the vector. -func (vector *Vector) Sum() (res Element) { - sumVecGeneric(&res, *vector) - return -} - -// InnerProduct computes the inner product of two vectors. -// It panics if the vectors don't have the same length. -func (vector *Vector) InnerProduct(other Vector) (res Element) { - innerProductVecGeneric(&res, *vector, other) - return -} - -// Mul multiplies two vectors element-wise and stores the result in self. -// It panics if the vectors don't have the same length. -func (vector *Vector) Mul(a, b Vector) { - mulVecGeneric(*vector, a, b) -} - 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_purego.go b/internal/tinyfield/vector_purego.go new file mode 100644 index 000000000..22a2964d1 --- /dev/null +++ b/internal/tinyfield/vector_purego.go @@ -0,0 +1,43 @@ +// Copyright 2020-2024 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package tinyfield + +// Add adds two vectors element-wise and stores the result in self. +// It panics if the vectors don't have the same length. +func (vector *Vector) Add(a, b Vector) { + addVecGeneric(*vector, a, b) +} + +// Sub subtracts two vectors element-wise and stores the result in self. +// It panics if the vectors don't have the same length. +func (vector *Vector) Sub(a, b Vector) { + subVecGeneric(*vector, a, b) +} + +// ScalarMul multiplies a vector by a scalar element-wise and stores the result in self. +// It panics if the vectors don't have the same length. +func (vector *Vector) ScalarMul(a Vector, b *Element) { + scalarMulVecGeneric(*vector, a, b) +} + +// Sum computes the sum of all elements in the vector. +func (vector *Vector) Sum() (res Element) { + sumVecGeneric(&res, *vector) + return +} + +// InnerProduct computes the inner product of two vectors. +// It panics if the vectors don't have the same length. +func (vector *Vector) InnerProduct(other Vector) (res Element) { + innerProductVecGeneric(&res, *vector, other) + return +} + +// Mul multiplies two vectors element-wise and stores the result in self. +// It panics if the vectors don't have the same length. +func (vector *Vector) Mul(a, b Vector) { + mulVecGeneric(*vector, a, b) +} diff --git a/internal/tinyfield/vector_test.go b/internal/tinyfield/vector_test.go index d17149d30..82867cee9 100644 --- a/internal/tinyfield/vector_test.go +++ b/internal/tinyfield/vector_test.go @@ -1,4 +1,4 @@ -// Copyright 2020-2024 ConsenSys Software Inc. +// Copyright 2020-2024 Consensys Software Inc. // Licensed under the Apache License, Version 2.0. See the LICENSE file for details. // Code generated by consensys/gnark-crypto DO NOT EDIT From f3d91999250b51d7457d3b945469e7a0d567ef38 Mon Sep 17 00:00:00 2001 From: AlexandreBelling Date: Tue, 17 Dec 2024 15:11:16 +0100 Subject: [PATCH 12/12] Feat: settable hasher for MiMC (#1345) Co-authored-by: Ivo Kubjas --- std/hash/hash.go | 15 +++++ std/hash/mimc/mimc.go | 25 ++++++++ std/hash/mimc/mimc_test.go | 128 +++++++++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+) diff --git a/std/hash/hash.go b/std/hash/hash.go index d537e7f56..b86336038 100644 --- a/std/hash/hash.go +++ b/std/hash/hash.go @@ -27,6 +27,21 @@ type FieldHasher interface { Reset() } +// StateStorer allows to store and retrieve the state of a hash function. +type StateStorer interface { + FieldHasher + // State retrieves the current state of the hash function. Calling this + // method should not destroy the current state and allow continue the use of + // the current hasher. The number of returned Variable is implementation + // dependent. + State() []frontend.Variable + // SetState sets the state of the hash function from a previously stored + // state retrieved using [StateStorer.State] method. The implementation + // returns an error if the number of supplied Variable does not match the + // number of Variable expected. + SetState(state []frontend.Variable) error +} + var ( builderRegistry = make(map[string]func(api frontend.API) (FieldHasher, error)) lock sync.RWMutex diff --git a/std/hash/mimc/mimc.go b/std/hash/mimc/mimc.go index 69ff66191..bc2daaaa4 100644 --- a/std/hash/mimc/mimc.go +++ b/std/hash/mimc/mimc.go @@ -48,6 +48,31 @@ func (h *MiMC) Reset() { h.h = 0 } +// SetState manually sets the state of the hasher to the provided value. In the +// case of MiMC only a single frontend variable is expected to represent the +// state. +func (h *MiMC) SetState(newState []frontend.Variable) error { + + if len(h.data) > 0 { + return errors.New("the hasher is not in an initial state") + } + + if len(newState) != 1 { + return errors.New("the MiMC hasher expects a single field element to represent the state") + } + + h.h = newState[0] + h.data = nil + return nil +} + +// State returns the inner-state of the hasher. In the context of MiMC only a +// single field element is returned. +func (h *MiMC) State() []frontend.Variable { + h.Sum() // this flushes the unsummed data + return []frontend.Variable{h.h} +} + // Sum hash using [Miyaguchi–Preneel] where the XOR operation is replaced by // field addition. // diff --git a/std/hash/mimc/mimc_test.go b/std/hash/mimc/mimc_test.go index e698a95f6..019862cb3 100644 --- a/std/hash/mimc/mimc_test.go +++ b/std/hash/mimc/mimc_test.go @@ -4,6 +4,9 @@ package mimc import ( + "crypto/rand" + "errors" + "fmt" "math/big" "testing" @@ -80,3 +83,128 @@ func TestMimcAll(t *testing.T) { } } + +// stateStoreCircuit checks that SetState works as expected. The circuit, however +// does not check the correctness of the hashes returned by the MiMC function +// as there is another test already testing this property. +type stateStoreTestCircuit struct { + X frontend.Variable +} + +func (s *stateStoreTestCircuit) Define(api frontend.API) error { + + hsh1, err1 := NewMiMC(api) + hsh2, err2 := NewMiMC(api) + + if err1 != nil || err2 != nil { + return fmt.Errorf("could not instantiate the MIMC hasher: %w", errors.Join(err1, err2)) + } + + // This pre-shuffle the hasher state so that the test does not start from + // a zero state. + hsh1.Write(s.X) + + state := hsh1.State() + hsh2.SetState(state) + + hsh1.Write(s.X) + hsh2.Write(s.X) + + var ( + dig1 = hsh1.Sum() + dig2 = hsh2.Sum() + newState1 = hsh1.State() + newState2 = hsh2.State() + ) + + api.AssertIsEqual(dig1, dig2) + + for i := range newState1 { + api.AssertIsEqual(newState1[i], newState2[i]) + } + + return nil +} + +func TestStateStoreMiMC(t *testing.T) { + + assert := test.NewAssert(t) + + curves := map[ecc.ID]hash.Hash{ + ecc.BN254: hash.MIMC_BN254, + ecc.BLS12_381: hash.MIMC_BLS12_381, + ecc.BLS12_377: hash.MIMC_BLS12_377, + ecc.BW6_761: hash.MIMC_BW6_761, + ecc.BW6_633: hash.MIMC_BW6_633, + ecc.BLS24_315: hash.MIMC_BLS24_315, + ecc.BLS24_317: hash.MIMC_BLS24_317, + } + + for curve := range curves { + + // minimal cs res = hash(data) + var ( + circuit = &stateStoreTestCircuit{} + assignment = &stateStoreTestCircuit{X: 2} + ) + + assert.CheckCircuit(circuit, + test.WithValidAssignment(assignment), + test.WithCurves(curve)) + } +} + +type recoveredStateTestCircuit struct { + State []frontend.Variable + Input frontend.Variable + Expected frontend.Variable `gnark:",public"` +} + +func (c *recoveredStateTestCircuit) Define(api frontend.API) error { + h, err := NewMiMC(api) + if err != nil { + return fmt.Errorf("initialize hash: %w", err) + } + if err = h.SetState(c.State); err != nil { + return fmt.Errorf("set state: %w", err) + } + h.Write(c.Input) + res := h.Sum() + api.AssertIsEqual(res, c.Expected) + return nil +} + +func TestHasherFromState(t *testing.T) { + assert := test.NewAssert(t) + + hashes := map[ecc.ID]hash.Hash{ + ecc.BN254: hash.MIMC_BN254, + ecc.BLS12_381: hash.MIMC_BLS12_381, + ecc.BLS12_377: hash.MIMC_BLS12_377, + ecc.BW6_761: hash.MIMC_BW6_761, + ecc.BW6_633: hash.MIMC_BW6_633, + ecc.BLS24_315: hash.MIMC_BLS24_315, + ecc.BLS24_317: hash.MIMC_BLS24_317, + } + + for cc, hh := range hashes { + hasher := hh.New() + ss, ok := hasher.(hash.StateStorer) + assert.True(ok) + _, err := ss.Write([]byte("hello world")) + assert.NoError(err) + state := ss.State() + nbBytes := cc.ScalarField().BitLen() / 8 + buf := make([]byte, nbBytes) + _, err = rand.Read(buf) + assert.NoError(err) + ss.Write(buf) + expected := ss.Sum(nil) + bstate := new(big.Int).SetBytes(state) + binput := new(big.Int).SetBytes(buf) + assert.CheckCircuit( + &recoveredStateTestCircuit{State: make([]frontend.Variable, 1)}, + test.WithValidAssignment(&recoveredStateTestCircuit{State: []frontend.Variable{bstate}, Input: binput, Expected: expected}), + test.WithCurves(cc)) + } +}