diff --git a/README.md b/README.md index 229630ff94..02c008f95a 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 0000000000..d00fd1cd4b --- /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 a77a7fbde9..3a662b35da 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 5b1b235d33..49133cd8b1 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 75c5a2b57e..114765dafe 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 87703339ce..0fa0a656bb 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 146a794255..0f25a0430f 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 426621e2f6..aa27ef3f74 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 3d5038a5c8..cd70e6f6c5 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,12 @@ 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.20241211083239-be3c2bbb1724 + 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 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 2044322751..305b0d2649 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.20241211083239-be3c2bbb1724 h1:lfTzZSy3FG2z5qFfRihDHmuolUvyEBWW8gsrjlZJQ/I= -github.com/consensys/gnark-crypto v0.14.1-0.20241211083239-be3c2bbb1724/go.mod h1:ePFa23CZLMRMHxQpY5nMaiAZ3yuEIayaB8ElEvlwLEs= +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= @@ -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= diff --git a/internal/generator/backend/main.go b/internal/generator/backend/main.go index d5c8ae0045..30e8e21b87 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 02aedba1ff..2b85fa7ab0 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 a8b6fce697..3e92316c85 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 5d7e45ae33..497fabfa95 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 609d9cbd78..eefc24dab6 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 64d9667a54..cece30c911 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 6b045db8cd..0439c558c1 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 0000000000..22a2964d1f --- /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 d17149d308..82867cee93 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 diff --git a/std/algebra/emulated/sw_bls12381/g1.go b/std/algebra/emulated/sw_bls12381/g1.go index 161db08538..ce8398616a 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 e6a275cfbb..4f3245ee21 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_bls12381/pairing.go b/std/algebra/emulated/sw_bls12381/pairing.go index 8f60180901..76798f42eb 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 diff --git a/std/algebra/emulated/sw_bn254/g2.go b/std/algebra/emulated/sw_bn254/g2.go index 6058f6e6c3..01299f7f03 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 02278f42be..0c97334a0d 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 7fce40b4cc..f49bb7d6de 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]{ 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