From 6243be5f934550ba12af43dd1c5983991a4fba4c Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 11 Sep 2025 09:45:48 +0000 Subject: [PATCH 01/24] refactor: move icicle acceleration out --- .../icicle/groth16/bls12-377/doc.go | 2 + .../icicle/groth16/bls12-377/icicle.go | 728 ++++++++++++++++++ .../icicle/groth16/bls12-377/marshal_test.go | 98 +++ .../icicle/groth16/bls12-377/provingkey.go | 30 + .../icicle/groth16/bls12-381/doc.go | 2 + .../icicle/groth16/bls12-381/icicle.go | 728 ++++++++++++++++++ .../icicle/groth16/bls12-381/marshal_test.go | 98 +++ .../icicle/groth16/bls12-381/provingkey.go | 30 + .../icicle/groth16/bn254}/doc.go | 2 +- .../icicle/groth16/bn254}/icicle.go | 8 +- .../icicle/groth16/bn254}/marshal_test.go | 6 +- .../icicle/groth16/bn254}/provingkey.go | 18 +- .../accelerated/icicle/groth16/bw6-761/doc.go | 2 + .../icicle/groth16/bw6-761/icicle.go | 717 +++++++++++++++++ .../icicle/groth16/bw6-761/marshal_test.go | 98 +++ .../icicle/groth16/bw6-761/provingkey.go | 30 + .../accelerated/icicle/groth16/groth16_all.go | 25 + .../icicle/groth16/groth16_icicle.go | 153 ++++ .../icicle/groth16/groth16_noicicle.go | 27 + backend/groth16/bn254/icicle/device.go | 35 - backend/groth16/bn254/icicle/noicicle.go | 32 - backend/groth16/groth16.go | 23 +- 22 files changed, 2777 insertions(+), 115 deletions(-) create mode 100644 backend/accelerated/icicle/groth16/bls12-377/doc.go create mode 100644 backend/accelerated/icicle/groth16/bls12-377/icicle.go create mode 100644 backend/accelerated/icicle/groth16/bls12-377/marshal_test.go create mode 100644 backend/accelerated/icicle/groth16/bls12-377/provingkey.go create mode 100644 backend/accelerated/icicle/groth16/bls12-381/doc.go create mode 100644 backend/accelerated/icicle/groth16/bls12-381/icicle.go create mode 100644 backend/accelerated/icicle/groth16/bls12-381/marshal_test.go create mode 100644 backend/accelerated/icicle/groth16/bls12-381/provingkey.go rename backend/{groth16/bn254/icicle => accelerated/icicle/groth16/bn254}/doc.go (84%) rename backend/{groth16/bn254/icicle => accelerated/icicle/groth16/bn254}/icicle.go (99%) rename backend/{groth16/bn254/icicle => accelerated/icicle/groth16/bn254}/marshal_test.go (94%) rename backend/{groth16/bn254/icicle => accelerated/icicle/groth16/bn254}/provingkey.go (60%) create mode 100644 backend/accelerated/icicle/groth16/bw6-761/doc.go create mode 100644 backend/accelerated/icicle/groth16/bw6-761/icicle.go create mode 100644 backend/accelerated/icicle/groth16/bw6-761/marshal_test.go create mode 100644 backend/accelerated/icicle/groth16/bw6-761/provingkey.go create mode 100644 backend/accelerated/icicle/groth16/groth16_all.go create mode 100644 backend/accelerated/icicle/groth16/groth16_icicle.go create mode 100644 backend/accelerated/icicle/groth16/groth16_noicicle.go delete mode 100644 backend/groth16/bn254/icicle/device.go delete mode 100644 backend/groth16/bn254/icicle/noicicle.go diff --git a/backend/accelerated/icicle/groth16/bls12-377/doc.go b/backend/accelerated/icicle/groth16/bls12-377/doc.go new file mode 100644 index 0000000000..bf2f0b65ac --- /dev/null +++ b/backend/accelerated/icicle/groth16/bls12-377/doc.go @@ -0,0 +1,2 @@ +// Package icicle_bls12377 implements ICICLE acceleration for BLS12-377 Groth16 backend. +package bls12377 diff --git a/backend/accelerated/icicle/groth16/bls12-377/icicle.go b/backend/accelerated/icicle/groth16/bls12-377/icicle.go new file mode 100644 index 0000000000..802474041d --- /dev/null +++ b/backend/accelerated/icicle/groth16/bls12-377/icicle.go @@ -0,0 +1,728 @@ +//go:build icicle + +package bls12377 + +import ( + "fmt" + "math/big" + "math/bits" + "os" + "slices" + "time" + + "github.com/consensys/gnark-crypto/ecc" + curve "github.com/consensys/gnark-crypto/ecc/bls12-377" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/fft" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/hash_to_field" + "github.com/consensys/gnark/backend" + groth16_bls12377 "github.com/consensys/gnark/backend/groth16/bls12-377" + "github.com/consensys/gnark/backend/witness" + "github.com/consensys/gnark/constraint" + cs "github.com/consensys/gnark/constraint/bls12-377" + "github.com/consensys/gnark/constraint/solver" + "github.com/consensys/gnark/internal/utils" + "github.com/consensys/gnark/logger" + + icicle_core "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/core" + icicle_bls12377 "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bls12377" + icicle_g2 "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bls12377/g2" + icicle_msm "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bls12377/msm" + icicle_ntt "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bls12377/ntt" + icicle_vecops "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bls12377/vecOps" + icicle_runtime "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/runtime" + + fcs "github.com/consensys/gnark/frontend/cs" +) + +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{} + 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(gen, big.NewInt(int64(pk.Domain.Cardinality))) + denI.Sub(&denI, &oneI).Inverse(&denI) + + log2SizeFloor := bits.Len(uint(n)) - 1 + denIcicleArr := []fr.Element{denI} + for i := 0; i < log2SizeFloor; i++ { + denIcicleArr = append(denIcicleArr, denIcicleArr...) + } + pow2Remainder := n - 1< 0 { + startPoKBatch := time.Now() + poksIcicle := make([]icicle_core.HostSlice[icicle_bls12377.Projective], numCommitmentKeys) + for i := range poksIcicle { + poksIcicle[i] = make(icicle_core.HostSlice[icicle_bls12377.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 { + 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) + + solution.A = nil + solution.B = nil + solution.C = nil + 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 icicle_core.DeviceSlice + chWireValuesA, chWireValuesB := make(chan struct{}), make(chan struct{}) + + 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] { + continue + } + wireValuesA[j] = wireValues[i] + j++ + } + + // Copy scalars to the device and retain ptr to them + wireValuesAHost := (icicle_core.HostSlice[fr.Element])(wireValuesA) + wireValuesAHost.CopyToDevice(&wireValuesADevice, true) + if err := icicle_bls12377.FromMontgomery(wireValuesADevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy A to device: %s", err.AsString())) + } + + close(chWireValuesA) + }) + + 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] { + continue + } + wireValuesB[j] = wireValues[i] + j++ + } + + // Copy scalars to the device and retain ptr to them + wireValuesBHost := (icicle_core.HostSlice[fr.Element])(wireValuesB) + wireValuesBHost.CopyToDevice(&wireValuesBDevice, true) + if err := icicle_bls12377.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 + var _r, _s, _kr fr.Element + if _, err := _r.SetRandom(); err != nil { + return nil, err + } + if _, err := _s.SetRandom(); err != nil { + return nil, err + } + _kr.Mul(&_r, &_s).Neg(&_kr) + + _r.BigInt(&r) + _s.BigInt(&s) + + // computes r[δ], s[δ], kr[δ] + 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_bls12377.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 + + cfg := icicle_msm.GetDefaultMSMConfig() + res := make(icicle_core.HostSlice[icicle_bls12377.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) + + cfg := icicle_msm.GetDefaultMSMConfig() + resKrs2 := make(icicle_core.HostSlice[icicle_bls12377.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()) + _wireValues := filterHeap(wireValues[r1cs.GetNbPublicVariables():], r1cs.GetNbPublicVariables(), slices.Concat(toRemove...)) + _wireValuesHost := (icicle_core.HostSlice[fr.Element])(_wireValues) + resKrs := make(icicle_core.HostSlice[icicle_bls12377.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())) + } + 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) + + p1.ScalarMultiplication(&bs1, &r) + krs.AddAssign(&p1) + + proof.Krs.FromJacobian(&krs) + + return nil + } + + computeBS2 := func() error { + // Bs2 (1 multi exp G2 - size = len(wires)) + 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") + } + Bs = g2ProjectiveToG2Jac(&res[0]) + + deltaS.FromAffine(&pk.G2.Delta) + deltaS.ScalarMultiplication(&deltaS, &s) + Bs.AddAssign(&deltaS) + Bs.AddMixed(&pk.G2.Beta) + + proof.Bs.FromJacobian(&Bs) + 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 + + 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()) + } + }) + + return proof, nil +} + +// if len(toRemove) == 0, returns slice +// else, returns a new slice without the indexes in toRemove. The first value in the slice is taken as indexes as sliceFirstIndex +// this assumes len(slice) > len(toRemove) +// filterHeap modifies toRemove +func filterHeap(slice []fr.Element, sliceFirstIndex int, toRemove []int) (r []fr.Element) { + + if len(toRemove) == 0 { + return slice + } + + heap := utils.IntHeap(toRemove) + heap.Heapify() + + r = make([]fr.Element, 0, len(slice)) + + // note: we can optimize that for the likely case where len(slice) >>> len(toRemove) + for i := 0; i < len(slice); i++ { + if len(heap) > 0 && i+sliceFirstIndex == heap[0] { + for len(heap) > 0 && i+sliceFirstIndex == heap[0] { + heap.Pop() + } + continue + } + r = append(r, slice[i]) + } + + return +} + +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 + padding := make([]fr.Element, int(pk.Domain.Cardinality)-n) + a = append(a, padding...) + b = append(b, padding...) + 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) + } + + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, a, computeADone) + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, b, computeBDone) + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, c, computeCDone) + + aDevice := <-computeADone + bDevice := <-computeBDone + cDevice := <-computeCDone + + // 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_bls12377.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_bls12377.FromMontgomery(aDevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("fromMontgomery a in computeH: %s", err.AsString())) + } + + if isProfileMode { + log.Debug().Dur("took", time.Since(startTotal)).Msg("computeH: Total") + } + return aDevice +} diff --git a/backend/accelerated/icicle/groth16/bls12-377/marshal_test.go b/backend/accelerated/icicle/groth16/bls12-377/marshal_test.go new file mode 100644 index 0000000000..cc67d71f9d --- /dev/null +++ b/backend/accelerated/icicle/groth16/bls12-377/marshal_test.go @@ -0,0 +1,98 @@ +//go:build icicle + +package bls12377_test + +import ( + "bytes" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend" + icicle_bls12377 "github.com/consensys/gnark/backend/accelerated/icicle/groth16/bls12-377" + "github.com/consensys/gnark/backend/groth16" + groth16_bls12377 "github.com/consensys/gnark/backend/groth16/bls12-377" + cs_bls12377 "github.com/consensys/gnark/constraint/bls12-377" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" + "github.com/consensys/gnark/test" +) + +type circuit struct { + A, B frontend.Variable `gnark:",public"` + Res frontend.Variable +} + +func (c *circuit) Define(api frontend.API) error { + api.AssertIsEqual(api.Mul(c.A, c.B), c.Res) + return nil +} + +func TestMarshal(t *testing.T) { + assert := test.NewAssert(t) + ccs, err := frontend.Compile(ecc.BLS12_377.ScalarField(), r1cs.NewBuilder, &circuit{}) + assert.NoError(err) + tCcs := ccs.(*cs_bls12377.R1CS) + nativePK := groth16_bls12377.ProvingKey{} + nativeVK := groth16_bls12377.VerifyingKey{} + err = groth16_bls12377.Setup(tCcs, &nativePK, &nativeVK) + assert.NoError(err) + + pk := groth16.NewProvingKey(ecc.BLS12_377) + buf := new(bytes.Buffer) + _, err = nativePK.WriteTo(buf) + assert.NoError(err) + _, err = pk.ReadFrom(buf) + assert.NoError(err) + if pk.IsDifferent(&nativePK) { + t.Error("marshal output difference") + } + + assignment := circuit{A: 3, B: 5, Res: 15} + w, err := frontend.NewWitness(&assignment, ecc.BLS12_377.ScalarField()) + assert.NoError(err) + pw, err := w.Public() + assert.NoError(err) + proofNative, err := groth16_bls12377.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) { + assert := test.NewAssert(t) + ccs, err := frontend.Compile(ecc.BLS12_377.ScalarField(), r1cs.NewBuilder, &circuit{}) + assert.NoError(err) + tCcs := ccs.(*cs_bls12377.R1CS) + iciPK := icicle_bls12377.ProvingKey{} + iciVK := groth16_bls12377.VerifyingKey{} + err = groth16_bls12377.Setup(tCcs, &iciPK.ProvingKey, &iciVK) + assert.NoError(err) + + nativePK := groth16_bls12377.ProvingKey{} + buf := new(bytes.Buffer) + _, err = iciPK.WriteTo(buf) + assert.NoError(err) + _, err = nativePK.ReadFrom(buf) + assert.NoError(err) + if iciPK.IsDifferent(&nativePK) { + t.Error("marshal output difference") + } + + assignment := circuit{A: 3, B: 5, Res: 15} + w, err := frontend.NewWitness(&assignment, ecc.BLS12_377.ScalarField()) + assert.NoError(err) + pw, err := w.Public() + assert.NoError(err) + proofNative, err := groth16_bls12377.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/accelerated/icicle/groth16/bls12-377/provingkey.go b/backend/accelerated/icicle/groth16/bls12-377/provingkey.go new file mode 100644 index 0000000000..4ab23308e1 --- /dev/null +++ b/backend/accelerated/icicle/groth16/bls12-377/provingkey.go @@ -0,0 +1,30 @@ +//go:build icicle + +package bls12377 + +import ( + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + groth16_bls12377 "github.com/consensys/gnark/backend/groth16/bls12-377" + icicle_core "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/core" +) + +type deviceInfo struct { + CosetGenerator [fr.Limbs * 2]uint32 + G1Device struct { + A, B, K, Z icicle_core.DeviceSlice + } + G2Device struct { + B icicle_core.DeviceSlice + } + DenDevice icicle_core.DeviceSlice + + CommitmentKeysDevice struct { + Basis []icicle_core.DeviceSlice + BasisExpSigma []icicle_core.DeviceSlice // we compute in batch + } +} + +type ProvingKey struct { + groth16_bls12377.ProvingKey + *deviceInfo +} diff --git a/backend/accelerated/icicle/groth16/bls12-381/doc.go b/backend/accelerated/icicle/groth16/bls12-381/doc.go new file mode 100644 index 0000000000..7a264af031 --- /dev/null +++ b/backend/accelerated/icicle/groth16/bls12-381/doc.go @@ -0,0 +1,2 @@ +// Package icicle_bls12381 implements ICICLE acceleration for BLS12-381 Groth16 backend. +package bls12381 diff --git a/backend/accelerated/icicle/groth16/bls12-381/icicle.go b/backend/accelerated/icicle/groth16/bls12-381/icicle.go new file mode 100644 index 0000000000..cbfbb5e062 --- /dev/null +++ b/backend/accelerated/icicle/groth16/bls12-381/icicle.go @@ -0,0 +1,728 @@ +//go:build icicle + +package bls12381 + +import ( + "fmt" + "math/big" + "math/bits" + "os" + "slices" + "time" + + "github.com/consensys/gnark-crypto/ecc" + curve "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/fft" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/hash_to_field" + "github.com/consensys/gnark/backend" + groth16_bls12381 "github.com/consensys/gnark/backend/groth16/bls12-381" + "github.com/consensys/gnark/backend/witness" + "github.com/consensys/gnark/constraint" + cs "github.com/consensys/gnark/constraint/bls12-381" + "github.com/consensys/gnark/constraint/solver" + "github.com/consensys/gnark/internal/utils" + "github.com/consensys/gnark/logger" + + icicle_core "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/core" + icicle_bls12381 "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bls12381" + icicle_g2 "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bls12381/g2" + icicle_msm "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bls12381/msm" + icicle_ntt "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bls12381/ntt" + icicle_vecops "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bls12381/vecOps" + icicle_runtime "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/runtime" + + fcs "github.com/consensys/gnark/frontend/cs" +) + +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{} + 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(gen, big.NewInt(int64(pk.Domain.Cardinality))) + denI.Sub(&denI, &oneI).Inverse(&denI) + + log2SizeFloor := bits.Len(uint(n)) - 1 + denIcicleArr := []fr.Element{denI} + for i := 0; i < log2SizeFloor; i++ { + denIcicleArr = append(denIcicleArr, denIcicleArr...) + } + pow2Remainder := n - 1< 0 { + startPoKBatch := time.Now() + poksIcicle := make([]icicle_core.HostSlice[icicle_bls12381.Projective], numCommitmentKeys) + for i := range poksIcicle { + poksIcicle[i] = make(icicle_core.HostSlice[icicle_bls12381.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 { + 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) + + solution.A = nil + solution.B = nil + solution.C = nil + 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 icicle_core.DeviceSlice + chWireValuesA, chWireValuesB := make(chan struct{}), make(chan struct{}) + + 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] { + continue + } + wireValuesA[j] = wireValues[i] + j++ + } + + // Copy scalars to the device and retain ptr to them + wireValuesAHost := (icicle_core.HostSlice[fr.Element])(wireValuesA) + wireValuesAHost.CopyToDevice(&wireValuesADevice, true) + if err := icicle_bls12381.FromMontgomery(wireValuesADevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy A to device: %s", err.AsString())) + } + + close(chWireValuesA) + }) + + 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] { + continue + } + wireValuesB[j] = wireValues[i] + j++ + } + + // Copy scalars to the device and retain ptr to them + wireValuesBHost := (icicle_core.HostSlice[fr.Element])(wireValuesB) + wireValuesBHost.CopyToDevice(&wireValuesBDevice, true) + if err := icicle_bls12381.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 + var _r, _s, _kr fr.Element + if _, err := _r.SetRandom(); err != nil { + return nil, err + } + if _, err := _s.SetRandom(); err != nil { + return nil, err + } + _kr.Mul(&_r, &_s).Neg(&_kr) + + _r.BigInt(&r) + _s.BigInt(&s) + + // computes r[δ], s[δ], kr[δ] + 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_bls12381.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 + + cfg := icicle_msm.GetDefaultMSMConfig() + res := make(icicle_core.HostSlice[icicle_bls12381.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) + + cfg := icicle_msm.GetDefaultMSMConfig() + resKrs2 := make(icicle_core.HostSlice[icicle_bls12381.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()) + _wireValues := filterHeap(wireValues[r1cs.GetNbPublicVariables():], r1cs.GetNbPublicVariables(), slices.Concat(toRemove...)) + _wireValuesHost := (icicle_core.HostSlice[fr.Element])(_wireValues) + resKrs := make(icicle_core.HostSlice[icicle_bls12381.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())) + } + 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) + + p1.ScalarMultiplication(&bs1, &r) + krs.AddAssign(&p1) + + proof.Krs.FromJacobian(&krs) + + return nil + } + + computeBS2 := func() error { + // Bs2 (1 multi exp G2 - size = len(wires)) + 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") + } + Bs = g2ProjectiveToG2Jac(&res[0]) + + deltaS.FromAffine(&pk.G2.Delta) + deltaS.ScalarMultiplication(&deltaS, &s) + Bs.AddAssign(&deltaS) + Bs.AddMixed(&pk.G2.Beta) + + proof.Bs.FromJacobian(&Bs) + 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 + + 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()) + } + }) + + return proof, nil +} + +// if len(toRemove) == 0, returns slice +// else, returns a new slice without the indexes in toRemove. The first value in the slice is taken as indexes as sliceFirstIndex +// this assumes len(slice) > len(toRemove) +// filterHeap modifies toRemove +func filterHeap(slice []fr.Element, sliceFirstIndex int, toRemove []int) (r []fr.Element) { + + if len(toRemove) == 0 { + return slice + } + + heap := utils.IntHeap(toRemove) + heap.Heapify() + + r = make([]fr.Element, 0, len(slice)) + + // note: we can optimize that for the likely case where len(slice) >>> len(toRemove) + for i := 0; i < len(slice); i++ { + if len(heap) > 0 && i+sliceFirstIndex == heap[0] { + for len(heap) > 0 && i+sliceFirstIndex == heap[0] { + heap.Pop() + } + continue + } + r = append(r, slice[i]) + } + + return +} + +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 + padding := make([]fr.Element, int(pk.Domain.Cardinality)-n) + a = append(a, padding...) + b = append(b, padding...) + 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) + } + + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, a, computeADone) + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, b, computeBDone) + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, c, computeCDone) + + aDevice := <-computeADone + bDevice := <-computeBDone + cDevice := <-computeCDone + + // 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_bls12381.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_bls12381.FromMontgomery(aDevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("fromMontgomery a in computeH: %s", err.AsString())) + } + + if isProfileMode { + log.Debug().Dur("took", time.Since(startTotal)).Msg("computeH: Total") + } + return aDevice +} diff --git a/backend/accelerated/icicle/groth16/bls12-381/marshal_test.go b/backend/accelerated/icicle/groth16/bls12-381/marshal_test.go new file mode 100644 index 0000000000..a7ce3784d4 --- /dev/null +++ b/backend/accelerated/icicle/groth16/bls12-381/marshal_test.go @@ -0,0 +1,98 @@ +//go:build icicle + +package bls12381_test + +import ( + "bytes" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend" + icicle_bls12381 "github.com/consensys/gnark/backend/accelerated/icicle/groth16/bls12-381" + "github.com/consensys/gnark/backend/groth16" + groth16_bls12381 "github.com/consensys/gnark/backend/groth16/bls12-381" + cs_bls12381 "github.com/consensys/gnark/constraint/bls12-381" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" + "github.com/consensys/gnark/test" +) + +type circuit struct { + A, B frontend.Variable `gnark:",public"` + Res frontend.Variable +} + +func (c *circuit) Define(api frontend.API) error { + api.AssertIsEqual(api.Mul(c.A, c.B), c.Res) + return nil +} + +func TestMarshal(t *testing.T) { + assert := test.NewAssert(t) + ccs, err := frontend.Compile(ecc.BLS12_381.ScalarField(), r1cs.NewBuilder, &circuit{}) + assert.NoError(err) + tCcs := ccs.(*cs_bls12381.R1CS) + nativePK := groth16_bls12381.ProvingKey{} + nativeVK := groth16_bls12381.VerifyingKey{} + err = groth16_bls12381.Setup(tCcs, &nativePK, &nativeVK) + assert.NoError(err) + + pk := groth16.NewProvingKey(ecc.BLS12_381) + buf := new(bytes.Buffer) + _, err = nativePK.WriteTo(buf) + assert.NoError(err) + _, err = pk.ReadFrom(buf) + assert.NoError(err) + if pk.IsDifferent(&nativePK) { + t.Error("marshal output difference") + } + + assignment := circuit{A: 3, B: 5, Res: 15} + w, err := frontend.NewWitness(&assignment, ecc.BLS12_381.ScalarField()) + assert.NoError(err) + pw, err := w.Public() + assert.NoError(err) + proofNative, err := groth16_bls12381.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) { + assert := test.NewAssert(t) + ccs, err := frontend.Compile(ecc.BLS12_381.ScalarField(), r1cs.NewBuilder, &circuit{}) + assert.NoError(err) + tCcs := ccs.(*cs_bls12381.R1CS) + iciPK := icicle_bls12381.ProvingKey{} + iciVK := groth16_bls12381.VerifyingKey{} + err = groth16_bls12381.Setup(tCcs, &iciPK.ProvingKey, &iciVK) + assert.NoError(err) + + nativePK := groth16_bls12381.ProvingKey{} + buf := new(bytes.Buffer) + _, err = iciPK.WriteTo(buf) + assert.NoError(err) + _, err = nativePK.ReadFrom(buf) + assert.NoError(err) + if iciPK.IsDifferent(&nativePK) { + t.Error("marshal output difference") + } + + assignment := circuit{A: 3, B: 5, Res: 15} + w, err := frontend.NewWitness(&assignment, ecc.BLS12_381.ScalarField()) + assert.NoError(err) + pw, err := w.Public() + assert.NoError(err) + proofNative, err := groth16_bls12381.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/accelerated/icicle/groth16/bls12-381/provingkey.go b/backend/accelerated/icicle/groth16/bls12-381/provingkey.go new file mode 100644 index 0000000000..d6a9ed4525 --- /dev/null +++ b/backend/accelerated/icicle/groth16/bls12-381/provingkey.go @@ -0,0 +1,30 @@ +//go:build icicle + +package bls12381 + +import ( + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + groth16_bls12381 "github.com/consensys/gnark/backend/groth16/bls12-381" + icicle_core "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/core" +) + +type deviceInfo struct { + CosetGenerator [fr.Limbs * 2]uint32 + G1Device struct { + A, B, K, Z icicle_core.DeviceSlice + } + G2Device struct { + B icicle_core.DeviceSlice + } + DenDevice icicle_core.DeviceSlice + + CommitmentKeysDevice struct { + Basis []icicle_core.DeviceSlice + BasisExpSigma []icicle_core.DeviceSlice // we compute in batch + } +} + +type ProvingKey struct { + groth16_bls12381.ProvingKey + *deviceInfo +} diff --git a/backend/groth16/bn254/icicle/doc.go b/backend/accelerated/icicle/groth16/bn254/doc.go similarity index 84% rename from backend/groth16/bn254/icicle/doc.go rename to backend/accelerated/icicle/groth16/bn254/doc.go index 3a662b35da..dd4d21076b 100644 --- a/backend/groth16/bn254/icicle/doc.go +++ b/backend/accelerated/icicle/groth16/bn254/doc.go @@ -1,2 +1,2 @@ // Package icicle_bn254 implements ICICLE acceleration for BN254 Groth16 backend. -package icicle +package bn254 diff --git a/backend/groth16/bn254/icicle/icicle.go b/backend/accelerated/icicle/groth16/bn254/icicle.go similarity index 99% rename from backend/groth16/bn254/icicle/icicle.go rename to backend/accelerated/icicle/groth16/bn254/icicle.go index 31beb5a680..261eae1641 100644 --- a/backend/groth16/bn254/icicle/icicle.go +++ b/backend/accelerated/icicle/groth16/bn254/icicle.go @@ -1,12 +1,13 @@ //go:build icicle -package icicle +package bn254 import ( "fmt" "math/big" "math/bits" "os" + "slices" "time" "github.com/consensys/gnark-crypto/ecc" @@ -17,7 +18,6 @@ import ( "github.com/consensys/gnark-crypto/ecc/bn254/fr/hash_to_field" "github.com/consensys/gnark/backend" groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" - "github.com/consensys/gnark/backend/groth16/internal" "github.com/consensys/gnark/backend/witness" "github.com/consensys/gnark/constraint" cs "github.com/consensys/gnark/constraint/bn254" @@ -36,8 +36,6 @@ import ( fcs "github.com/consensys/gnark/frontend/cs" ) -const HasIcicle = true - var isProfileMode bool func init() { @@ -496,7 +494,7 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b // 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...)) + _wireValues := filterHeap(wireValues[r1cs.GetNbPublicVariables():], r1cs.GetNbPublicVariables(), slices.Concat(toRemove...)) _wireValuesHost := (icicle_core.HostSlice[fr.Element])(_wireValues) resKrs := make(icicle_core.HostSlice[icicle_bn254.Projective], 1) cfg.AreScalarsMontgomeryForm = true diff --git a/backend/groth16/bn254/icicle/marshal_test.go b/backend/accelerated/icicle/groth16/bn254/marshal_test.go similarity index 94% rename from backend/groth16/bn254/icicle/marshal_test.go rename to backend/accelerated/icicle/groth16/bn254/marshal_test.go index 114765dafe..eda2183226 100644 --- a/backend/groth16/bn254/icicle/marshal_test.go +++ b/backend/accelerated/icicle/groth16/bn254/marshal_test.go @@ -1,6 +1,6 @@ //go:build icicle -package icicle_test +package bn254_test import ( "bytes" @@ -8,9 +8,9 @@ import ( "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark/backend" + icicle_bn254 "github.com/consensys/gnark/backend/accelerated/icicle/groth16/bn254" "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" cs_bn254 "github.com/consensys/gnark/constraint/bn254" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/frontend/cs/r1cs" @@ -69,7 +69,7 @@ func TestMarshal2(t *testing.T) { tCcs := ccs.(*cs_bn254.R1CS) iciPK := icicle_bn254.ProvingKey{} iciVK := groth16_bn254.VerifyingKey{} - err = icicle_bn254.Setup(tCcs, &iciPK, &iciVK) + err = groth16_bn254.Setup(tCcs, &iciPK.ProvingKey, &iciVK) assert.NoError(err) nativePK := groth16_bn254.ProvingKey{} diff --git a/backend/groth16/bn254/icicle/provingkey.go b/backend/accelerated/icicle/groth16/bn254/provingkey.go similarity index 60% rename from backend/groth16/bn254/icicle/provingkey.go rename to backend/accelerated/icicle/groth16/bn254/provingkey.go index 625f402ae0..c1894e23ed 100644 --- a/backend/groth16/bn254/icicle/provingkey.go +++ b/backend/accelerated/icicle/groth16/bn254/provingkey.go @@ -1,11 +1,10 @@ //go:build icicle -package icicle +package bn254 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-gnark/v3/wrappers/golang/core" ) @@ -29,18 +28,3 @@ type ProvingKey struct { groth16_bn254.ProvingKey *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/accelerated/icicle/groth16/bw6-761/doc.go b/backend/accelerated/icicle/groth16/bw6-761/doc.go new file mode 100644 index 0000000000..511803213d --- /dev/null +++ b/backend/accelerated/icicle/groth16/bw6-761/doc.go @@ -0,0 +1,2 @@ +// Package icicle_bw6761 implements ICICLE acceleration for BW6-761 Groth16 backend. +package bw6761 diff --git a/backend/accelerated/icicle/groth16/bw6-761/icicle.go b/backend/accelerated/icicle/groth16/bw6-761/icicle.go new file mode 100644 index 0000000000..84e88fa378 --- /dev/null +++ b/backend/accelerated/icicle/groth16/bw6-761/icicle.go @@ -0,0 +1,717 @@ +//go:build icicle + +package bw6761 + +import ( + "fmt" + "math/big" + "math/bits" + "os" + "slices" + "time" + + "github.com/consensys/gnark-crypto/ecc" + curve "github.com/consensys/gnark-crypto/ecc/bw6-761" + "github.com/consensys/gnark-crypto/ecc/bw6-761/fp" + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/fft" + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/hash_to_field" + "github.com/consensys/gnark/backend" + groth16_bw6761 "github.com/consensys/gnark/backend/groth16/bw6-761" + "github.com/consensys/gnark/backend/witness" + "github.com/consensys/gnark/constraint" + cs "github.com/consensys/gnark/constraint/bw6-761" + "github.com/consensys/gnark/constraint/solver" + "github.com/consensys/gnark/internal/utils" + "github.com/consensys/gnark/logger" + + icicle_core "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/core" + icicle_bw6761 "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bw6761" + icicle_g2 "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bw6761/g2" + icicle_msm "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bw6761/msm" + icicle_ntt "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bw6761/ntt" + icicle_vecops "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/bw6761/vecOps" + icicle_runtime "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/runtime" + + fcs "github.com/consensys/gnark/frontend/cs" +) + +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{} + 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(gen, big.NewInt(int64(pk.Domain.Cardinality))) + denI.Sub(&denI, &oneI).Inverse(&denI) + + log2SizeFloor := bits.Len(uint(n)) - 1 + denIcicleArr := []fr.Element{denI} + for i := 0; i < log2SizeFloor; i++ { + denIcicleArr = append(denIcicleArr, denIcicleArr...) + } + pow2Remainder := n - 1< 0 { + startPoKBatch := time.Now() + poksIcicle := make([]icicle_core.HostSlice[icicle_bw6761.Projective], numCommitmentKeys) + for i := range poksIcicle { + poksIcicle[i] = make(icicle_core.HostSlice[icicle_bw6761.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 { + 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) + + solution.A = nil + solution.B = nil + solution.C = nil + 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 icicle_core.DeviceSlice + chWireValuesA, chWireValuesB := make(chan struct{}), make(chan struct{}) + + 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] { + continue + } + wireValuesA[j] = wireValues[i] + j++ + } + + // Copy scalars to the device and retain ptr to them + wireValuesAHost := (icicle_core.HostSlice[fr.Element])(wireValuesA) + wireValuesAHost.CopyToDevice(&wireValuesADevice, true) + if err := icicle_bw6761.FromMontgomery(wireValuesADevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy A to device: %s", err.AsString())) + } + + close(chWireValuesA) + }) + + 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] { + continue + } + wireValuesB[j] = wireValues[i] + j++ + } + + // Copy scalars to the device and retain ptr to them + wireValuesBHost := (icicle_core.HostSlice[fr.Element])(wireValuesB) + wireValuesBHost.CopyToDevice(&wireValuesBDevice, true) + if err := icicle_bw6761.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 + var _r, _s, _kr fr.Element + if _, err := _r.SetRandom(); err != nil { + return nil, err + } + if _, err := _s.SetRandom(); err != nil { + return nil, err + } + _kr.Mul(&_r, &_s).Neg(&_kr) + + _r.BigInt(&r) + _s.BigInt(&s) + + // computes r[δ], s[δ], kr[δ] + 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_bw6761.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 + + cfg := icicle_msm.GetDefaultMSMConfig() + res := make(icicle_core.HostSlice[icicle_bw6761.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) + + cfg := icicle_msm.GetDefaultMSMConfig() + resKrs2 := make(icicle_core.HostSlice[icicle_bw6761.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()) + _wireValues := filterHeap(wireValues[r1cs.GetNbPublicVariables():], r1cs.GetNbPublicVariables(), slices.Concat(toRemove...)) + _wireValuesHost := (icicle_core.HostSlice[fr.Element])(_wireValues) + resKrs := make(icicle_core.HostSlice[icicle_bw6761.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())) + } + 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) + + p1.ScalarMultiplication(&bs1, &r) + krs.AddAssign(&p1) + + proof.Krs.FromJacobian(&krs) + + return nil + } + + computeBS2 := func() error { + // Bs2 (1 multi exp G2 - size = len(wires)) + 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") + } + Bs = g2ProjectiveToG2Jac(&res[0]) + + deltaS.FromAffine(&pk.G2.Delta) + deltaS.ScalarMultiplication(&deltaS, &s) + Bs.AddAssign(&deltaS) + Bs.AddMixed(&pk.G2.Beta) + + proof.Bs.FromJacobian(&Bs) + 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 + + 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()) + } + }) + + return proof, nil +} + +// if len(toRemove) == 0, returns slice +// else, returns a new slice without the indexes in toRemove. The first value in the slice is taken as indexes as sliceFirstIndex +// this assumes len(slice) > len(toRemove) +// filterHeap modifies toRemove +func filterHeap(slice []fr.Element, sliceFirstIndex int, toRemove []int) (r []fr.Element) { + + if len(toRemove) == 0 { + return slice + } + + heap := utils.IntHeap(toRemove) + heap.Heapify() + + r = make([]fr.Element, 0, len(slice)) + + // note: we can optimize that for the likely case where len(slice) >>> len(toRemove) + for i := 0; i < len(slice); i++ { + if len(heap) > 0 && i+sliceFirstIndex == heap[0] { + for len(heap) > 0 && i+sliceFirstIndex == heap[0] { + heap.Pop() + } + continue + } + r = append(r, slice[i]) + } + + return +} + +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 + padding := make([]fr.Element, int(pk.Domain.Cardinality)-n) + a = append(a, padding...) + b = append(b, padding...) + 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) + } + + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, a, computeADone) + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, b, computeBDone) + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, c, computeCDone) + + aDevice := <-computeADone + bDevice := <-computeBDone + cDevice := <-computeCDone + + // 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_bw6761.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_bw6761.FromMontgomery(aDevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("fromMontgomery a in computeH: %s", err.AsString())) + } + + if isProfileMode { + log.Debug().Dur("took", time.Since(startTotal)).Msg("computeH: Total") + } + return aDevice +} diff --git a/backend/accelerated/icicle/groth16/bw6-761/marshal_test.go b/backend/accelerated/icicle/groth16/bw6-761/marshal_test.go new file mode 100644 index 0000000000..c5dd609acb --- /dev/null +++ b/backend/accelerated/icicle/groth16/bw6-761/marshal_test.go @@ -0,0 +1,98 @@ +//go:build icicle + +package bw6761_test + +import ( + "bytes" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend" + icicle_bw6761 "github.com/consensys/gnark/backend/accelerated/icicle/groth16/bw6-761" + "github.com/consensys/gnark/backend/groth16" + groth16_bw6761 "github.com/consensys/gnark/backend/groth16/bw6-761" + cs_bw6761 "github.com/consensys/gnark/constraint/bw6-761" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" + "github.com/consensys/gnark/test" +) + +type circuit struct { + A, B frontend.Variable `gnark:",public"` + Res frontend.Variable +} + +func (c *circuit) Define(api frontend.API) error { + api.AssertIsEqual(api.Mul(c.A, c.B), c.Res) + return nil +} + +func TestMarshal(t *testing.T) { + assert := test.NewAssert(t) + ccs, err := frontend.Compile(ecc.BLS12_381.ScalarField(), r1cs.NewBuilder, &circuit{}) + assert.NoError(err) + tCcs := ccs.(*cs_bw6761.R1CS) + nativePK := groth16_bw6761.ProvingKey{} + nativeVK := groth16_bw6761.VerifyingKey{} + err = groth16_bw6761.Setup(tCcs, &nativePK, &nativeVK) + assert.NoError(err) + + pk := groth16.NewProvingKey(ecc.BLS12_381) + buf := new(bytes.Buffer) + _, err = nativePK.WriteTo(buf) + assert.NoError(err) + _, err = pk.ReadFrom(buf) + assert.NoError(err) + if pk.IsDifferent(&nativePK) { + t.Error("marshal output difference") + } + + assignment := circuit{A: 3, B: 5, Res: 15} + w, err := frontend.NewWitness(&assignment, ecc.BLS12_381.ScalarField()) + assert.NoError(err) + pw, err := w.Public() + assert.NoError(err) + proofNative, err := groth16_bw6761.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) { + assert := test.NewAssert(t) + ccs, err := frontend.Compile(ecc.BLS12_381.ScalarField(), r1cs.NewBuilder, &circuit{}) + assert.NoError(err) + tCcs := ccs.(*cs_bw6761.R1CS) + iciPK := icicle_bw6761.ProvingKey{} + iciVK := groth16_bw6761.VerifyingKey{} + err = groth16_bw6761.Setup(tCcs, &iciPK.ProvingKey, &iciVK) + assert.NoError(err) + + nativePK := groth16_bw6761.ProvingKey{} + buf := new(bytes.Buffer) + _, err = iciPK.WriteTo(buf) + assert.NoError(err) + _, err = nativePK.ReadFrom(buf) + assert.NoError(err) + if iciPK.IsDifferent(&nativePK) { + t.Error("marshal output difference") + } + + assignment := circuit{A: 3, B: 5, Res: 15} + w, err := frontend.NewWitness(&assignment, ecc.BLS12_381.ScalarField()) + assert.NoError(err) + pw, err := w.Public() + assert.NoError(err) + proofNative, err := groth16_bw6761.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/accelerated/icicle/groth16/bw6-761/provingkey.go b/backend/accelerated/icicle/groth16/bw6-761/provingkey.go new file mode 100644 index 0000000000..c5c9f08c2d --- /dev/null +++ b/backend/accelerated/icicle/groth16/bw6-761/provingkey.go @@ -0,0 +1,30 @@ +//go:build icicle + +package bw6761 + +import ( + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" + groth16_bw6761 "github.com/consensys/gnark/backend/groth16/bw6-761" + icicle_core "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/core" +) + +type deviceInfo struct { + CosetGenerator [fr.Limbs * 2]uint32 + G1Device struct { + A, B, K, Z icicle_core.DeviceSlice + } + G2Device struct { + B icicle_core.DeviceSlice + } + DenDevice icicle_core.DeviceSlice + + CommitmentKeysDevice struct { + Basis []icicle_core.DeviceSlice + BasisExpSigma []icicle_core.DeviceSlice // we compute in batch + } +} + +type ProvingKey struct { + groth16_bw6761.ProvingKey + *deviceInfo +} diff --git a/backend/accelerated/icicle/groth16/groth16_all.go b/backend/accelerated/icicle/groth16/groth16_all.go new file mode 100644 index 0000000000..556f04b433 --- /dev/null +++ b/backend/accelerated/icicle/groth16/groth16_all.go @@ -0,0 +1,25 @@ +package groth16 + +import ( + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/groth16" + "github.com/consensys/gnark/backend/witness" + "github.com/consensys/gnark/constraint" +) + +func Verify(proof groth16.Proof, vk groth16.VerifyingKey, publicWitness witness.Witness, opts ...backend.VerifierOption) error { + return groth16.Verify(proof, vk, publicWitness, opts...) +} + +func NewVerifyingKey(curveID ecc.ID) groth16.VerifyingKey { + return groth16.NewVerifyingKey(curveID) +} + +func NewProof(curveID ecc.ID) groth16.Proof { + return groth16.NewProof(curveID) +} + +func NewCS(curveID ecc.ID) constraint.ConstraintSystem { + return groth16.NewCS(curveID) +} diff --git a/backend/accelerated/icicle/groth16/groth16_icicle.go b/backend/accelerated/icicle/groth16/groth16_icicle.go new file mode 100644 index 0000000000..90c3196645 --- /dev/null +++ b/backend/accelerated/icicle/groth16/groth16_icicle.go @@ -0,0 +1,153 @@ +//go:build icicle + +package groth16 + +import ( + "fmt" + "sync" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/groth16" + groth16_bls12377 "github.com/consensys/gnark/backend/groth16/bls12-377" + groth16_bls12381 "github.com/consensys/gnark/backend/groth16/bls12-381" + groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" + groth16_bw6761 "github.com/consensys/gnark/backend/groth16/bw6-761" + "github.com/consensys/gnark/backend/witness" + "github.com/consensys/gnark/constraint" + cs_bls12377 "github.com/consensys/gnark/constraint/bls12-377" + cs_bls12381 "github.com/consensys/gnark/constraint/bls12-381" + cs_bn254 "github.com/consensys/gnark/constraint/bn254" + cs_bw6761 "github.com/consensys/gnark/constraint/bw6-761" + "github.com/consensys/gnark/logger" + + icicle_bls12377 "github.com/consensys/gnark/backend/accelerated/icicle/groth16/bls12-377" + icicle_bls12381 "github.com/consensys/gnark/backend/accelerated/icicle/groth16/bls12-381" + icicle_bn254 "github.com/consensys/gnark/backend/accelerated/icicle/groth16/bn254" + icicle_bw6761 "github.com/consensys/gnark/backend/accelerated/icicle/groth16/bw6-761" + + icicle_runtime "github.com/ingonyama-zk/icicle-gnark/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) { + stream, err := icicle_runtime.CreateStream() + if err != icicle_runtime.Success { + panic(fmt.Sprintf("ICICLE create stream error: %s", err.AsString())) + } + err = icicle_runtime.WarmUpDevice(stream) + if err != icicle_runtime.Success { + panic(fmt.Sprintf("ICICLE device warmup error: %s", err.AsString())) + } + }) + }) +} + +func Prove(r1cs constraint.ConstraintSystem, pk groth16.ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (groth16.Proof, error) { + switch _r1cs := r1cs.(type) { + case *cs_bls12377.R1CS: + return icicle_bls12377.Prove(_r1cs, pk.(*icicle_bls12377.ProvingKey), fullWitness, opts...) + case *cs_bls12381.R1CS: + return icicle_bls12381.Prove(_r1cs, pk.(*icicle_bls12381.ProvingKey), fullWitness, opts...) + case *cs_bn254.R1CS: + return icicle_bn254.Prove(_r1cs, pk.(*icicle_bn254.ProvingKey), fullWitness, opts...) + case *cs_bw6761.R1CS: + return icicle_bw6761.Prove(_r1cs, pk.(*icicle_bw6761.ProvingKey), fullWitness, opts...) + default: + panic("icicle backend requested but r1cs is not of a supported curve") + } +} + +func Setup(r1cs constraint.ConstraintSystem) (groth16.ProvingKey, groth16.VerifyingKey, error) { + warmUpDevice() + switch _r1cs := r1cs.(type) { + case *cs_bls12377.R1CS: + var pk icicle_bls12377.ProvingKey + var vk groth16_bls12377.VerifyingKey + if err := groth16_bls12377.Setup(_r1cs, &pk.ProvingKey, &vk); err != nil { + return nil, nil, err + } + return &pk, &vk, nil + case *cs_bls12381.R1CS: + var pk icicle_bls12381.ProvingKey + var vk groth16_bls12381.VerifyingKey + if err := groth16_bls12381.Setup(_r1cs, &pk.ProvingKey, &vk); err != nil { + return nil, nil, err + } + return &pk, &vk, nil + case *cs_bn254.R1CS: + var pk icicle_bn254.ProvingKey + var vk groth16_bn254.VerifyingKey + if err := groth16_bn254.Setup(_r1cs, &pk.ProvingKey, &vk); err != nil { + return nil, nil, err + } + return &pk, &vk, nil + case *cs_bw6761.R1CS: + var pk icicle_bw6761.ProvingKey + var vk groth16_bw6761.VerifyingKey + if err := groth16_bw6761.Setup(_r1cs, &pk.ProvingKey, &vk); err != nil { + return nil, nil, err + } + return &pk, &vk, nil + default: + panic("icicle backend requested but r1cs is not of a supported curve") + } +} + +func DummySetup(r1cs constraint.ConstraintSystem) (groth16.ProvingKey, error) { + warmUpDevice() + switch _r1cs := r1cs.(type) { + case *cs_bls12377.R1CS: + var pk icicle_bls12377.ProvingKey + if err := groth16_bls12377.DummySetup(_r1cs, &pk.ProvingKey); err != nil { + return nil, err + } + return &pk, nil + case *cs_bls12381.R1CS: + var pk icicle_bls12381.ProvingKey + if err := groth16_bls12381.DummySetup(_r1cs, &pk.ProvingKey); err != nil { + return nil, err + } + return &pk, nil + case *cs_bn254.R1CS: + var pk icicle_bn254.ProvingKey + if err := groth16_bn254.DummySetup(_r1cs, &pk.ProvingKey); err != nil { + return nil, err + } + return &pk, nil + case *cs_bw6761.R1CS: + var pk icicle_bw6761.ProvingKey + if err := groth16_bw6761.DummySetup(_r1cs, &pk.ProvingKey); err != nil { + return nil, err + } + return &pk, nil + default: + panic("icicle backend requested but r1cs is not of a supported curve") + } +} + +func NewProvingKey(curveID ecc.ID) groth16.ProvingKey { + warmUpDevice() + switch curveID { + case ecc.BLS12_377: + return &icicle_bls12377.ProvingKey{} + case ecc.BLS12_381: + return &icicle_bls12381.ProvingKey{} + case ecc.BN254: + return &icicle_bn254.ProvingKey{} + case ecc.BW6_761: + return &icicle_bw6761.ProvingKey{} + default: + panic("icicle backend requested but curve is not supported") + } +} diff --git a/backend/accelerated/icicle/groth16/groth16_noicicle.go b/backend/accelerated/icicle/groth16/groth16_noicicle.go new file mode 100644 index 0000000000..9a3be8f98a --- /dev/null +++ b/backend/accelerated/icicle/groth16/groth16_noicicle.go @@ -0,0 +1,27 @@ +//go:build !icicle + +package groth16 + +import ( + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/groth16" + "github.com/consensys/gnark/backend/witness" + "github.com/consensys/gnark/constraint" +) + +func Prove(r1cs constraint.ConstraintSystem, pk groth16.ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (groth16.Proof, error) { + panic("icicle backend requested but program compiled without 'icicle' build tag") +} + +func Setup(r1cs constraint.ConstraintSystem) (groth16.ProvingKey, groth16.VerifyingKey, error) { + panic("icicle backend requested but program compiled without 'icicle' build tag") +} + +func DummySetup(r1cs constraint.ConstraintSystem) (groth16.ProvingKey, error) { + panic("icicle backend requested but program compiled without 'icicle' build tag") +} + +func NewProvingKey(curveID ecc.ID) groth16.ProvingKey { + panic("icicle backend requested but program compiled without 'icicle' build tag") +} diff --git a/backend/groth16/bn254/icicle/device.go b/backend/groth16/bn254/icicle/device.go deleted file mode 100644 index 88a5d9f72b..0000000000 --- a/backend/groth16/bn254/icicle/device.go +++ /dev/null @@ -1,35 +0,0 @@ -//go:build icicle - -package icicle - -import ( - "fmt" - "sync" - - "github.com/consensys/gnark/logger" - icicle_runtime "github.com/ingonyama-zk/icicle-gnark/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) { - stream, err := icicle_runtime.CreateStream() - if err != icicle_runtime.Success { - panic(fmt.Sprintf("ICICLE create stream error: %s", err.AsString())) - } - err = icicle_runtime.WarmUpDevice(stream) - if err != icicle_runtime.Success { - panic(fmt.Sprintf("ICICLE device warmup error: %s", err.AsString())) - } - }) - }) -} diff --git a/backend/groth16/bn254/icicle/noicicle.go b/backend/groth16/bn254/icicle/noicicle.go deleted file mode 100644 index 0fa0a656bb..0000000000 --- a/backend/groth16/bn254/icicle/noicicle.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build !icicle - -package icicle - -import ( - "github.com/consensys/gnark/backend" - groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" - "github.com/consensys/gnark/backend/witness" - cs "github.com/consensys/gnark/constraint/bn254" -) - -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) { - 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/groth16.go b/backend/groth16/groth16.go index 3c4d1b5645..5b6fc5a744 100644 --- a/backend/groth16/groth16.go +++ b/backend/groth16/groth16.go @@ -39,7 +39,6 @@ import ( groth16_bls24315 "github.com/consensys/gnark/backend/groth16/bls24-315" groth16_bls24317 "github.com/consensys/gnark/backend/groth16/bls24-317" groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" - icicle_bn254 "github.com/consensys/gnark/backend/groth16/bn254/icicle" groth16_bw6633 "github.com/consensys/gnark/backend/groth16/bw6-633" groth16_bw6761 "github.com/consensys/gnark/backend/groth16/bw6-761" ) @@ -187,9 +186,6 @@ func Prove(r1cs constraint.ConstraintSystem, pk ProvingKey, fullWitness witness. return groth16_bls12381.Prove(_r1cs, pk.(*groth16_bls12381.ProvingKey), fullWitness, opts...) case *cs_bn254.R1CS: - if icicle_bn254.HasIcicle { - return icicle_bn254.Prove(_r1cs, pk.(*icicle_bn254.ProvingKey), fullWitness, opts...) - } return groth16_bn254.Prove(_r1cs, pk.(*groth16_bn254.ProvingKey), fullWitness, opts...) case *cs_bw6761.R1CS: @@ -235,15 +231,8 @@ func Setup(r1cs constraint.ConstraintSystem) (ProvingKey, VerifyingKey, error) { } return &pk, &vk, nil case *cs_bn254.R1CS: - var vk groth16_bn254.VerifyingKey - if icicle_bn254.HasIcicle { - var pk icicle_bn254.ProvingKey - if err := icicle_bn254.Setup(_r1cs, &pk, &vk); err != nil { - return nil, nil, err - } - return &pk, &vk, nil - } var pk groth16_bn254.ProvingKey + var vk groth16_bn254.VerifyingKey if err := groth16_bn254.Setup(_r1cs, &pk, &vk); err != nil { return nil, nil, err } @@ -298,13 +287,6 @@ func DummySetup(r1cs constraint.ConstraintSystem) (ProvingKey, error) { } return &pk, nil case *cs_bn254.R1CS: - if icicle_bn254.HasIcicle { - var pk icicle_bn254.ProvingKey - if err := icicle_bn254.DummySetup(_r1cs, &pk); err != nil { - return nil, err - } - return &pk, nil - } var pk groth16_bn254.ProvingKey if err := groth16_bn254.DummySetup(_r1cs, &pk); err != nil { return nil, err @@ -346,9 +328,6 @@ func NewProvingKey(curveID ecc.ID) ProvingKey { switch curveID { case ecc.BN254: pk = &groth16_bn254.ProvingKey{} - if icicle_bn254.HasIcicle { - pk = icicle_bn254.NewProvingKey() - } case ecc.BLS12_377: pk = &groth16_bls12377.ProvingKey{} case ecc.BLS12_381: From 42ada05ce57b567e55a906dfb236ca8a05db6252 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 11 Sep 2025 09:46:20 +0000 Subject: [PATCH 02/24] refactor: remove option for switching acceleration --- .../icicle/groth16/bls12-377/icicle.go | 3 --- .../icicle/groth16/bls12-381/icicle.go | 3 --- .../accelerated/icicle/groth16/bn254/icicle.go | 3 --- .../accelerated/icicle/groth16/bw6-761/icicle.go | 3 --- backend/backend.go | 16 +++++++++------- 5 files changed, 9 insertions(+), 19 deletions(-) diff --git a/backend/accelerated/icicle/groth16/bls12-377/icicle.go b/backend/accelerated/icicle/groth16/bls12-377/icicle.go index 802474041d..4dac6eb286 100644 --- a/backend/accelerated/icicle/groth16/bls12-377/icicle.go +++ b/backend/accelerated/icicle/groth16/bls12-377/icicle.go @@ -238,9 +238,6 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b if opt.HashToFieldFn == nil { opt.HashToFieldFn = hash_to_field.New([]byte(constraint.CommitmentDst)) } - if opt.Accelerator != "icicle" { - return groth16_bls12377.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) diff --git a/backend/accelerated/icicle/groth16/bls12-381/icicle.go b/backend/accelerated/icicle/groth16/bls12-381/icicle.go index cbfbb5e062..67e7c402e6 100644 --- a/backend/accelerated/icicle/groth16/bls12-381/icicle.go +++ b/backend/accelerated/icicle/groth16/bls12-381/icicle.go @@ -238,9 +238,6 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b if opt.HashToFieldFn == nil { opt.HashToFieldFn = hash_to_field.New([]byte(constraint.CommitmentDst)) } - if opt.Accelerator != "icicle" { - return groth16_bls12381.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) diff --git a/backend/accelerated/icicle/groth16/bn254/icicle.go b/backend/accelerated/icicle/groth16/bn254/icicle.go index 261eae1641..30b9a32e64 100644 --- a/backend/accelerated/icicle/groth16/bn254/icicle.go +++ b/backend/accelerated/icicle/groth16/bn254/icicle.go @@ -238,9 +238,6 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b if opt.HashToFieldFn == nil { opt.HashToFieldFn = hash_to_field.New([]byte(constraint.CommitmentDst)) } - if opt.Accelerator != "icicle" { - 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) diff --git a/backend/accelerated/icicle/groth16/bw6-761/icicle.go b/backend/accelerated/icicle/groth16/bw6-761/icicle.go index 84e88fa378..cc016e57b7 100644 --- a/backend/accelerated/icicle/groth16/bw6-761/icicle.go +++ b/backend/accelerated/icicle/groth16/bw6-761/icicle.go @@ -227,9 +227,6 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b if opt.HashToFieldFn == nil { opt.HashToFieldFn = hash_to_field.New([]byte(constraint.CommitmentDst)) } - if opt.Accelerator != "icicle" { - return groth16_bw6761.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) diff --git a/backend/backend.go b/backend/backend.go index 6b3756a5ab..89157bfb9a 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -6,6 +6,7 @@ package backend import ( "crypto/sha256" + "fmt" "hash" "github.com/consensys/gnark/constraint/solver" @@ -60,7 +61,6 @@ type ProverConfig struct { HashToFieldFn hash.Hash ChallengeHash hash.Hash KZGFoldingHash hash.Hash - Accelerator string StatisticalZK bool } @@ -122,16 +122,18 @@ func WithProverKZGFoldingHashFunction(hFunc hash.Hash) ProverOption { } } -// WithIcicleAcceleration requests to use [ICICLE] GPU proving backend for the -// prover. This option requires that the program is compiled with `icicle` build -// tag and the ICICLE dependencies are properly installed. See [ICICLE] for -// installation description. +// WithIcicleAcceleration requests to use [ICICLE] GPU proving backend. +// +// DEPRECATED: we don't switch to ICICLE automatically anymore, the user has to +// explicitly use methods in the [github.com/consensys/gnark/backend/accelerated/icicle] +// package to use ICICLE acceleration. This option will be remove in a future release, +// but kept for now for API backward compatibility. It will error at runtime instead. // // [ICICLE]: https://github.com/ingonyama-zk/icicle-gnark func WithIcicleAcceleration() ProverOption { return func(pc *ProverConfig) error { - pc.Accelerator = "icicle" - return nil + return fmt.Errorf("WithIcicleAcceleration for switching is deprecated, please use the ICICLE backend directly. " + + "Import \"github.com/consensys/gnark/backend/accelerated/icicle\" and use the Prove method there") } } From 57bb7daf335e4efc9eed3a75e1ad5b5b20144157 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 1 Oct 2025 13:27:39 +0000 Subject: [PATCH 03/24] fix: bw6 g2 projective to jac --- backend/accelerated/icicle/groth16/bw6-761/icicle.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/accelerated/icicle/groth16/bw6-761/icicle.go b/backend/accelerated/icicle/groth16/bw6-761/icicle.go index cc016e57b7..0d2c157e30 100644 --- a/backend/accelerated/icicle/groth16/bw6-761/icicle.go +++ b/backend/accelerated/icicle/groth16/bw6-761/icicle.go @@ -209,11 +209,11 @@ func g2ProjectiveToG2Jac(p *icicle_g2.G2Projective) curve.G2Jac { var jZSquared, jX, jY fp.Element jZSquared.Mul(&z, &z) - x.Mul(&x, &z) - y.Mul(&y, &jZSquared) + jX.Mul(&x, &z) + jY.Mul(&y, &jZSquared) return curve.G2Jac{ - X: jY, - Y: jX, + X: jX, + Y: jY, Z: z, } } From 4f1ee58c3136f2bb0ff19c5bd725df5e47329099 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Wed, 1 Oct 2025 13:27:52 +0000 Subject: [PATCH 04/24] fix: warm up device before proving --- backend/accelerated/icicle/groth16/groth16_icicle.go | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/accelerated/icicle/groth16/groth16_icicle.go b/backend/accelerated/icicle/groth16/groth16_icicle.go index 90c3196645..2365fd06d9 100644 --- a/backend/accelerated/icicle/groth16/groth16_icicle.go +++ b/backend/accelerated/icicle/groth16/groth16_icicle.go @@ -54,6 +54,7 @@ func warmUpDevice() { } func Prove(r1cs constraint.ConstraintSystem, pk groth16.ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (groth16.Proof, error) { + warmUpDevice() switch _r1cs := r1cs.(type) { case *cs_bls12377.R1CS: return icicle_bls12377.Prove(_r1cs, pk.(*icicle_bls12377.ProvingKey), fullWitness, opts...) From 9817c6717405b97f3ba9b9c1d9a35694458ee725 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 2 Oct 2025 09:43:20 +0000 Subject: [PATCH 05/24] feat: opts and documentation --- README.md | 17 +-- backend/accelerated/icicle/doc.go | 46 ++++++++ .../icicle/groth16/bls12-377/icicle.go | 7 +- .../icicle/groth16/bls12-381/icicle.go | 7 +- .../icicle/groth16/bn254/icicle.go | 7 +- .../icicle/groth16/bw6-761/icicle.go | 7 +- .../accelerated/icicle/groth16/groth16_all.go | 5 + .../icicle/groth16/groth16_icicle.go | 86 ++++++++++---- .../icicle/groth16/groth16_noicicle.go | 25 ++++- backend/accelerated/icicle/opts.go | 106 ++++++++++++++++++ 10 files changed, 263 insertions(+), 50 deletions(-) create mode 100644 backend/accelerated/icicle/doc.go create mode 100644 backend/accelerated/icicle/opts.go diff --git a/README.md b/README.md index 5b5ac28b16..3703f32828 100644 --- a/README.md +++ b/README.md @@ -171,20 +171,11 @@ The following schemes and curves support experimental use of Ingonyama's ICICLE instantiated with the following curve(s) - [x] BN254 +- [x] BLS12-377 +- [x] BLS12-381 +- [x] BW6-761 -To use GPUs, add the `icicle` buildtag to your build/run commands, e.g. `go run -tags=icicle main.go`. - -You can then toggle on or off icicle acceleration by providing the `WithIcicleAcceleration` backend ProverOption: - -```go - // toggle on - proofIci, err := groth16.Prove(ccs, pk, secretWitness, backend.WithIcicleAcceleration()) - - // toggle off - proof, err := groth16.Prove(ccs, pk, secretWitness) -``` - -For more information about prerequisites see the [ICICLE repo](https://github.com/ingonyama-zk/icicle-gnark). +For usage instructions see [accelerated backend documentation](backend/accelerated/icicle/doc.go) and [ICICLE repo](https://github.com/ingonyama-zk/icicle-gnark). ## Citing diff --git a/backend/accelerated/icicle/doc.go b/backend/accelerated/icicle/doc.go new file mode 100644 index 0000000000..7bf8160be8 --- /dev/null +++ b/backend/accelerated/icicle/doc.go @@ -0,0 +1,46 @@ +// Package icicle implements backends using ICICLE library. +// +// This backend depends on the MIT-licensed [ICICLE] library. We currently +// support Groth16 proving system on the following curves: +// - BLS12-377 +// - BLS12-381 +// - BN254 +// - BW6-761 +// +// To initialize the ICICILE backend, follow the instructions in the [ICICLE] +// repository. Namely, first you should install the ICICLE library: +// +// git clone github.com/ingonyama-zk/icicle-gnark +// cd icicle-gnark/wrappers/golang +// sudo ./build.sh -curve=all +// +// After that, the libraries are installed in `/usr/local/lib“ and backend in +// `/usr/local/lib/backend`. +// +// Now set the environment variables: +// +// export CGO_LDFLAGS="-L/usr/local/lib -licicle_device -lstdc++ -lm -Wl,-rpath=/usr/local/lib" +// export ICICLE_BACKEND_INSTALL_DIR="/usr/local/lib/backend/" +// +// To use the ICICLE backend in your code, you should use the `icicle_groth16` +// package and use it for proving: +// +// import icicle_groth "github.com/consensys/gnark/backend/accelerated/icicle/groth16" +// ... +// pk := icicle_groth.NewProvingKey(curve) +// n, err = pk.ReadFrom(r) +// ... +// proof, err := icicle_groth.Prove(ccs, pk, witness) +// +// Finally, to build the application, use the `icicle` build tag to ensure the ICICLE integration is built: +// +// go build -tags=icicle main.go +// +// Keep in mind that the definitions of ICICLE and native gnark proving keys are +// different, so you cannot directly use the native gnark proving key with the +// ICICLE backend. However, the serialization is compatible, so you can use the +// `ReadFrom` and `WriteTo` methods to read/write the proving keys in binary +// format and use the same proving key for both backends. +// +// [ICICLE]: https://github.com/ingonyama-zk/icicle-gnark +package icicle diff --git a/backend/accelerated/icicle/groth16/bls12-377/icicle.go b/backend/accelerated/icicle/groth16/bls12-377/icicle.go index 4dac6eb286..da035d654e 100644 --- a/backend/accelerated/icicle/groth16/bls12-377/icicle.go +++ b/backend/accelerated/icicle/groth16/bls12-377/icicle.go @@ -17,6 +17,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/fft" "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/hash_to_field" "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/accelerated/icicle" groth16_bls12377 "github.com/consensys/gnark/backend/groth16/bls12-377" "github.com/consensys/gnark/backend/witness" "github.com/consensys/gnark/constraint" @@ -230,8 +231,8 @@ func g2ProjectiveToG2Jac(p *icicle_g2.G2Projective) curve.G2Jac { } // Prove generates the proof of knowledge of a r1cs with full witness (secret + public part). -func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (*groth16_bls12377.Proof, error) { - opt, err := backend.NewProverConfig(opts...) +func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, cfg *icicle.Config) (*groth16_bls12377.Proof, error) { + opt, err := backend.NewProverConfig(cfg.ProverOpts...) if err != nil { return nil, fmt.Errorf("new prover config: %w", err) } @@ -240,7 +241,7 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b } 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) + device := icicle_runtime.CreateDevice(cfg.Backend.String(), cfg.DeviceID) if pk.deviceInfo == nil { log.Debug().Msg("precomputing proving key in GPU") diff --git a/backend/accelerated/icicle/groth16/bls12-381/icicle.go b/backend/accelerated/icicle/groth16/bls12-381/icicle.go index 67e7c402e6..4b98a3b5f3 100644 --- a/backend/accelerated/icicle/groth16/bls12-381/icicle.go +++ b/backend/accelerated/icicle/groth16/bls12-381/icicle.go @@ -17,6 +17,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/fft" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/hash_to_field" "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/accelerated/icicle" groth16_bls12381 "github.com/consensys/gnark/backend/groth16/bls12-381" "github.com/consensys/gnark/backend/witness" "github.com/consensys/gnark/constraint" @@ -230,8 +231,8 @@ func g2ProjectiveToG2Jac(p *icicle_g2.G2Projective) curve.G2Jac { } // Prove generates the proof of knowledge of a r1cs with full witness (secret + public part). -func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (*groth16_bls12381.Proof, error) { - opt, err := backend.NewProverConfig(opts...) +func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, cfg *icicle.Config) (*groth16_bls12381.Proof, error) { + opt, err := backend.NewProverConfig(cfg.ProverOpts...) if err != nil { return nil, fmt.Errorf("new prover config: %w", err) } @@ -240,7 +241,7 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b } 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) + device := icicle_runtime.CreateDevice(cfg.Backend.String(), cfg.DeviceID) if pk.deviceInfo == nil { log.Debug().Msg("precomputing proving key in GPU") diff --git a/backend/accelerated/icicle/groth16/bn254/icicle.go b/backend/accelerated/icicle/groth16/bn254/icicle.go index 30b9a32e64..8c1b6ddc80 100644 --- a/backend/accelerated/icicle/groth16/bn254/icicle.go +++ b/backend/accelerated/icicle/groth16/bn254/icicle.go @@ -17,6 +17,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bn254/fr/fft" "github.com/consensys/gnark-crypto/ecc/bn254/fr/hash_to_field" "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/accelerated/icicle" groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" "github.com/consensys/gnark/backend/witness" "github.com/consensys/gnark/constraint" @@ -230,8 +231,8 @@ func g2ProjectiveToG2Jac(p *icicle_g2.G2Projective) curve.G2Jac { } // Prove generates the proof of knowledge of a r1cs with full witness (secret + public part). -func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (*groth16_bn254.Proof, error) { - opt, err := backend.NewProverConfig(opts...) +func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, cfg *icicle.Config) (*groth16_bn254.Proof, error) { + opt, err := backend.NewProverConfig(cfg.ProverOpts...) if err != nil { return nil, fmt.Errorf("new prover config: %w", err) } @@ -240,7 +241,7 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b } 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) + device := icicle_runtime.CreateDevice(cfg.Backend.String(), cfg.DeviceID) if pk.deviceInfo == nil { log.Debug().Msg("precomputing proving key in GPU") diff --git a/backend/accelerated/icicle/groth16/bw6-761/icicle.go b/backend/accelerated/icicle/groth16/bw6-761/icicle.go index 0d2c157e30..f76bf103bf 100644 --- a/backend/accelerated/icicle/groth16/bw6-761/icicle.go +++ b/backend/accelerated/icicle/groth16/bw6-761/icicle.go @@ -17,6 +17,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/fft" "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/hash_to_field" "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/accelerated/icicle" groth16_bw6761 "github.com/consensys/gnark/backend/groth16/bw6-761" "github.com/consensys/gnark/backend/witness" "github.com/consensys/gnark/constraint" @@ -219,8 +220,8 @@ func g2ProjectiveToG2Jac(p *icicle_g2.G2Projective) curve.G2Jac { } // Prove generates the proof of knowledge of a r1cs with full witness (secret + public part). -func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (*groth16_bw6761.Proof, error) { - opt, err := backend.NewProverConfig(opts...) +func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, cfg *icicle.Config) (*groth16_bw6761.Proof, error) { + opt, err := backend.NewProverConfig(cfg.ProverOpts...) if err != nil { return nil, fmt.Errorf("new prover config: %w", err) } @@ -229,7 +230,7 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, fullWitness witness.Witness, opts ...b } 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) + device := icicle_runtime.CreateDevice(cfg.Backend.String(), cfg.DeviceID) if pk.deviceInfo == nil { log.Debug().Msg("precomputing proving key in GPU") diff --git a/backend/accelerated/icicle/groth16/groth16_all.go b/backend/accelerated/icicle/groth16/groth16_all.go index 556f04b433..5ac309dd5a 100644 --- a/backend/accelerated/icicle/groth16/groth16_all.go +++ b/backend/accelerated/icicle/groth16/groth16_all.go @@ -1,3 +1,4 @@ +// Package groth16 implements Groth16 proof system with ICICLE acceleration. package groth16 import ( @@ -8,18 +9,22 @@ import ( "github.com/consensys/gnark/constraint" ) +// Verify verifies Groth16 proof. It wraps [groth16.Verify] function, but is provided for completeness. func Verify(proof groth16.Proof, vk groth16.VerifyingKey, publicWitness witness.Witness, opts ...backend.VerifierOption) error { return groth16.Verify(proof, vk, publicWitness, opts...) } +// NewVerifyingKey creates a new empty verifying key for deserializing into. It is compatible with [groth16.NewVerifyingKey]. func NewVerifyingKey(curveID ecc.ID) groth16.VerifyingKey { return groth16.NewVerifyingKey(curveID) } +// NewProof creates a new empty proof for deserializing into. It is compatible with [groth16.NewProof]. func NewProof(curveID ecc.ID) groth16.Proof { return groth16.NewProof(curveID) } +// NewProvingKey creates a new empty proving key for deserializing into. It is compatible with [groth16.NewProvingKey]. func NewCS(curveID ecc.ID) constraint.ConstraintSystem { return groth16.NewCS(curveID) } diff --git a/backend/accelerated/icicle/groth16/groth16_icicle.go b/backend/accelerated/icicle/groth16/groth16_icicle.go index 2365fd06d9..d74b6cbcfc 100644 --- a/backend/accelerated/icicle/groth16/groth16_icicle.go +++ b/backend/accelerated/icicle/groth16/groth16_icicle.go @@ -7,7 +7,6 @@ import ( "sync" "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark/backend" "github.com/consensys/gnark/backend/groth16" groth16_bls12377 "github.com/consensys/gnark/backend/groth16/bls12-377" groth16_bls12381 "github.com/consensys/gnark/backend/groth16/bls12-381" @@ -27,50 +26,82 @@ import ( icicle_bw6761 "github.com/consensys/gnark/backend/accelerated/icicle/groth16/bw6-761" icicle_runtime "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/runtime" + + "github.com/consensys/gnark/backend/accelerated/icicle" ) var onceWarmUpDevice sync.Once -func warmUpDevice() { +// warmUpDevice performs one-time initialization of the ICICLE backend and warms up all available devices. +// This function is called at the beginning of the Prove function to ensure that the devices are ready for use. +// It is safe to call this function multiple times; the initialization will only occur once. +func warmUpDevice(config *icicle.Config) { 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) { - stream, err := icicle_runtime.CreateStream() + if config.BackendLibs != "" { + err := icicle_runtime.LoadBackend(config.BackendLibs, false) if err != icicle_runtime.Success { - panic(fmt.Sprintf("ICICLE create stream error: %s", err.AsString())) + panic(fmt.Sprintf("custom ICICLE backend loading error: %s", err.AsString())) } - err = icicle_runtime.WarmUpDevice(stream) + } else { + err := icicle_runtime.LoadBackendFromEnvOrDefault() if err != icicle_runtime.Success { - panic(fmt.Sprintf("ICICLE device warmup error: %s", err.AsString())) + panic(fmt.Sprintf("default ICICLE backend loading error: %s", err.AsString())) } - }) + } + nbDev, err := icicle_runtime.GetDeviceCount() + if err != icicle_runtime.Success { + panic(fmt.Sprintf("ICICLE get device count error: %s", err.AsString())) + } + log.Info().Int("nbDev", nbDev).Msg("ICICLE devices detected") + for id := 0; id < nbDev; id++ { + device := icicle_runtime.CreateDevice(config.Backend.String(), id) + log.Debug().Int32("id", device.Id).Str("type", device.GetDeviceType()).Msg("ICICLE device created") + icicle_runtime.RunOnDevice(&device, func(args ...any) { + stream, err := icicle_runtime.CreateStream() + if err != icicle_runtime.Success { + panic(fmt.Sprintf("ICICLE create stream error: %s", err.AsString())) + } + err = icicle_runtime.WarmUpDevice(stream) + if err != icicle_runtime.Success { + panic(fmt.Sprintf("ICICLE device warmup error: %s", err.AsString())) + } + }) + } }) } -func Prove(r1cs constraint.ConstraintSystem, pk groth16.ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (groth16.Proof, error) { - warmUpDevice() +// Prove generates the proof of knowledge of a r1cs with full witness (secret + public part). +// +// NB! the provided proving key must contain the device pointers required for +// the acceleration. Initialize and deserialize the proving key using +// [NewProvingKey] and the serialization methods. +func Prove(r1cs constraint.ConstraintSystem, pk groth16.ProvingKey, fullWitness witness.Witness, opts ...icicle.Option) (groth16.Proof, error) { + config, err := icicle.NewConfig(opts...) + if err != nil { + return nil, fmt.Errorf("initializing config: %w", err) + } + warmUpDevice(config) switch _r1cs := r1cs.(type) { case *cs_bls12377.R1CS: - return icicle_bls12377.Prove(_r1cs, pk.(*icicle_bls12377.ProvingKey), fullWitness, opts...) + return icicle_bls12377.Prove(_r1cs, pk.(*icicle_bls12377.ProvingKey), fullWitness, config) case *cs_bls12381.R1CS: - return icicle_bls12381.Prove(_r1cs, pk.(*icicle_bls12381.ProvingKey), fullWitness, opts...) + return icicle_bls12381.Prove(_r1cs, pk.(*icicle_bls12381.ProvingKey), fullWitness, config) case *cs_bn254.R1CS: - return icicle_bn254.Prove(_r1cs, pk.(*icicle_bn254.ProvingKey), fullWitness, opts...) + return icicle_bn254.Prove(_r1cs, pk.(*icicle_bn254.ProvingKey), fullWitness, config) case *cs_bw6761.R1CS: - return icicle_bw6761.Prove(_r1cs, pk.(*icicle_bw6761.ProvingKey), fullWitness, opts...) + return icicle_bw6761.Prove(_r1cs, pk.(*icicle_bw6761.ProvingKey), fullWitness, config) default: panic("icicle backend requested but r1cs is not of a supported curve") } } +// Setup generates a proving and verifying key for a given r1cs. +// +// The method wraps the [groth16.Setup] method, but the returned proving key +// contains device pointers for acceleration. To convert the key to a standard +// Groth16 proving key, use the serialization methods. func Setup(r1cs constraint.ConstraintSystem) (groth16.ProvingKey, groth16.VerifyingKey, error) { - warmUpDevice() switch _r1cs := r1cs.(type) { case *cs_bls12377.R1CS: var pk icicle_bls12377.ProvingKey @@ -105,8 +136,14 @@ func Setup(r1cs constraint.ConstraintSystem) (groth16.ProvingKey, groth16.Verify } } +// DummySetup generates a dummy proving key for a given circuit. It doesn't perform +// the precomputations and thus the returned proving key cannot be used to generate +// proofs. The method is useful for development and testing purposes. +// +// The method wraps the [groth16.DummySetup] method, but the returned proving key +// contains device pointers for acceleration. To convert the key to a standard +// Groth16 proving key, use the serialization methods. func DummySetup(r1cs constraint.ConstraintSystem) (groth16.ProvingKey, error) { - warmUpDevice() switch _r1cs := r1cs.(type) { case *cs_bls12377.R1CS: var pk icicle_bls12377.ProvingKey @@ -137,8 +174,11 @@ func DummySetup(r1cs constraint.ConstraintSystem) (groth16.ProvingKey, error) { } } +// NewProvingKey creates a new empty proving key for deserializing into. +// +// The method is compatible with [groth16.NewProvingKey], but returns an +// ICICLE proving key with device pointers for acceleration. func NewProvingKey(curveID ecc.ID) groth16.ProvingKey { - warmUpDevice() switch curveID { case ecc.BLS12_377: return &icicle_bls12377.ProvingKey{} diff --git a/backend/accelerated/icicle/groth16/groth16_noicicle.go b/backend/accelerated/icicle/groth16/groth16_noicicle.go index 9a3be8f98a..21033b7a0d 100644 --- a/backend/accelerated/icicle/groth16/groth16_noicicle.go +++ b/backend/accelerated/icicle/groth16/groth16_noicicle.go @@ -4,24 +4,45 @@ package groth16 import ( "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/accelerated/icicle" "github.com/consensys/gnark/backend/groth16" "github.com/consensys/gnark/backend/witness" "github.com/consensys/gnark/constraint" ) -func Prove(r1cs constraint.ConstraintSystem, pk groth16.ProvingKey, fullWitness witness.Witness, opts ...backend.ProverOption) (groth16.Proof, error) { +// Prove generates the proof of knowledge of a r1cs with full witness (secret + public part). +// +// NB! the provided proving key must contain the device pointers required for +// the acceleration. Initialize and deserialize the proving key using +// [NewProvingKey] and the serialization methods. +func Prove(r1cs constraint.ConstraintSystem, pk groth16.ProvingKey, fullWitness witness.Witness, opts ...icicle.Option) (groth16.Proof, error) { panic("icicle backend requested but program compiled without 'icicle' build tag") } +// Setup generates a proving and verifying key for a given r1cs. +// +// The method wraps the [groth16.Setup] method, but the returned proving key +// contains device pointers for acceleration. To convert the key to a standard +// Groth16 proving key, use the serialization methods. func Setup(r1cs constraint.ConstraintSystem) (groth16.ProvingKey, groth16.VerifyingKey, error) { panic("icicle backend requested but program compiled without 'icicle' build tag") } +// DummySetup generates a dummy proving key for a given circuit. It doesn't perform +// the precomputations and thus the returned proving key cannot be used to generate +// proofs. The method is useful for development and testing purposes. +// +// The method wraps the [groth16.DummySetup] method, but the returned proving key +// contains device pointers for acceleration. To convert the key to a standard +// Groth16 proving key, use the serialization methods. func DummySetup(r1cs constraint.ConstraintSystem) (groth16.ProvingKey, error) { panic("icicle backend requested but program compiled without 'icicle' build tag") } +// NewProvingKey creates a new empty proving key for deserializing into. +// +// The method is compatible with [groth16.NewProvingKey], but returns an +// ICICLE proving key with device pointers for acceleration. func NewProvingKey(curveID ecc.ID) groth16.ProvingKey { panic("icicle backend requested but program compiled without 'icicle' build tag") } diff --git a/backend/accelerated/icicle/opts.go b/backend/accelerated/icicle/opts.go new file mode 100644 index 0000000000..170fd910de --- /dev/null +++ b/backend/accelerated/icicle/opts.go @@ -0,0 +1,106 @@ +package icicle + +import ( + "fmt" + + "github.com/consensys/gnark/backend" +) + +// Config is the configuration for the ICICLE backend. +type Config struct { + DeviceID int + Backend Backend + BackendLibs string + ProverOpts []backend.ProverOption +} + +// NewConfig creates a new IcicleConfig with the given options. If no options +// are provided, it uses sensible defaults. +func NewConfig(opts ...Option) (*Config, error) { + cfg := Config{ + DeviceID: 0, + Backend: CUDA, + } + for _, o := range opts { + if o != nil { + if err := o(&cfg); err != nil { + return nil, err + } + } + } + return &cfg, nil +} + +// Option is an option for the ICICLE backend. If no options are set, then +// sensible defaults are used (acceleration CUDA, device id 0). +type Option func(*Config) error + +// Backend defines the type of backend to use for ICICLE acceleration. +type Backend int + +const ( + CUDA Backend = iota + CPU + maxBackend +) + +func (b Backend) String() string { + switch b { + case CUDA: + return "CUDA" + case CPU: + return "CPU" + default: + return "unknown" + } +} + +// WithDeviceID sets the device IDs to be used by the ICICLE backend. When +// defining this option, then at least one device is required and other IDs are +// optional. If this option is not set then device ID 0 is used. +func WithDeviceID(id int) Option { + return func(c *Config) error { + if id < 0 { + return fmt.Errorf("invalid device id %d", id) + } + c.DeviceID = id + return nil + } +} + +// WithBackend sets the backend to be used by ICICLE frontend. If this option +// is not set then CUDA backend is used. +func WithBackend(backend Backend) Option { + return func(c *Config) error { + if backend < 0 || backend >= maxBackend { + return fmt.Errorf("invalid backend %d", backend) + } + c.Backend = backend + return nil + } +} + +// WithProverOptions sets prover options. See [backend.ProverOption] for details. +func WithProverOptions(opts ...backend.ProverOption) Option { + return func(c *Config) error { + if len(opts) == 0 { + return fmt.Errorf("no prover options provided") + } + c.ProverOpts = opts + return nil + } +} + +// WithBackendLibrary sets the location of the backend library. This overrides +// the environment variable `ICICLE_BACKEND_INSTALL_DIR`. If this option is not +// set, then the environment variable is used first and if the variable is not +// set, then the default search location is used. +func WithBackendLibrary(libs string) Option { + return func(c *Config) error { + if libs == "" { + return fmt.Errorf("no backend libs provided") + } + c.BackendLibs = libs + return nil + } +} From 7a629428409fe3ad4f789c5b70be63eec96a668b Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 2 Oct 2025 10:06:51 +0000 Subject: [PATCH 06/24] docs: reserve enum and docs --- backend/accelerated/icicle/doc.go | 19 +++++++++++++++++++ backend/accelerated/icicle/opts.go | 6 ++++++ backend/backend.go | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/backend/accelerated/icicle/doc.go b/backend/accelerated/icicle/doc.go index 7bf8160be8..9269261178 100644 --- a/backend/accelerated/icicle/doc.go +++ b/backend/accelerated/icicle/doc.go @@ -7,6 +7,8 @@ // - BN254 // - BW6-761 // +// # Setup +// // To initialize the ICICILE backend, follow the instructions in the [ICICLE] // repository. Namely, first you should install the ICICLE library: // @@ -22,6 +24,8 @@ // export CGO_LDFLAGS="-L/usr/local/lib -licicle_device -lstdc++ -lm -Wl,-rpath=/usr/local/lib" // export ICICLE_BACKEND_INSTALL_DIR="/usr/local/lib/backend/" // +// # Usage +// // To use the ICICLE backend in your code, you should use the `icicle_groth16` // package and use it for proving: // @@ -36,11 +40,26 @@ // // go build -tags=icicle main.go // +// # Proving key +// // Keep in mind that the definitions of ICICLE and native gnark proving keys are // different, so you cannot directly use the native gnark proving key with the // ICICLE backend. However, the serialization is compatible, so you can use the // `ReadFrom` and `WriteTo` methods to read/write the proving keys in binary // format and use the same proving key for both backends. // +// # Non-free backends +// +// gnark by default depends on the MIT-licensed ICICLE backend library. However, ICICLE +// can be used with non-free backends (newer CUDA and Metal), but this is not tested +// and we do not provide support for this. +// +// # Future compatibility +// +// Keep in mind that the accelerated backends are not automatically tested in +// the CI, so we cannot guarantee that future changes in gnark will not break +// the ICICLE integration. We also may change interfaces in the sub-packages to +// align with the external dependency changes. +// // [ICICLE]: https://github.com/ingonyama-zk/icicle-gnark package icicle diff --git a/backend/accelerated/icicle/opts.go b/backend/accelerated/icicle/opts.go index 170fd910de..53ec36aacb 100644 --- a/backend/accelerated/icicle/opts.go +++ b/backend/accelerated/icicle/opts.go @@ -39,8 +39,14 @@ type Option func(*Config) error type Backend int const ( + // CUDA supported with free version CUDA Backend = iota + // CPU supported, but not included in the current dependency. Kept for future use and to reserve + // the enum value. CPU + // METAL not supported with free version. Kept for future use and to reserve + // the enum value. + METAL maxBackend ) diff --git a/backend/backend.go b/backend/backend.go index 89157bfb9a..5bc86fd142 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -126,7 +126,7 @@ func WithProverKZGFoldingHashFunction(hFunc hash.Hash) ProverOption { // // DEPRECATED: we don't switch to ICICLE automatically anymore, the user has to // explicitly use methods in the [github.com/consensys/gnark/backend/accelerated/icicle] -// package to use ICICLE acceleration. This option will be remove in a future release, +// package to use ICICLE acceleration. This option will be removed in a future release, // but kept for now for API backward compatibility. It will error at runtime instead. // // [ICICLE]: https://github.com/ingonyama-zk/icicle-gnark From 9f406149e25fe7b75448fa9db600233fbede7da3 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 2 Oct 2025 10:31:08 +0000 Subject: [PATCH 07/24] docs: add example --- .../accelerated_gpu_example_test.go | 154 ++++++++++++++++++ examples/accelerated_gpu/doc.go | 14 ++ 2 files changed, 168 insertions(+) create mode 100644 examples/accelerated_gpu/accelerated_gpu_example_test.go create mode 100644 examples/accelerated_gpu/doc.go diff --git a/examples/accelerated_gpu/accelerated_gpu_example_test.go b/examples/accelerated_gpu/accelerated_gpu_example_test.go new file mode 100644 index 0000000000..64a4f867a4 --- /dev/null +++ b/examples/accelerated_gpu/accelerated_gpu_example_test.go @@ -0,0 +1,154 @@ +//go:build icicle + +package accelerated_gpu + +import ( + "bytes" + "fmt" + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + fr_bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" + "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" + "github.com/consensys/gnark/test" + + "github.com/consensys/gnark/backend/accelerated/icicle" + icicle_groth "github.com/consensys/gnark/backend/accelerated/icicle/groth16" + "github.com/consensys/gnark/backend/solidity" + + "github.com/consensys/gnark/backend/groth16" +) + +type ExampleCircuit struct { + A [2]sw_bls12381.G1Affine + B [2]sw_bls12381.G2Affine + Res sw_bls12381.GTEl `gnark:",public"` +} + +func (c *ExampleCircuit) Define(api frontend.API) error { + pr, err := sw_bls12381.NewPairing(api) + if err != nil { + return err + } + res, err := pr.Pair([]*sw_bls12381.G1Affine{&c.A[0], &c.A[1]}, []*sw_bls12381.G2Affine{&c.B[0], &c.B[1]}) + if err != nil { + return err + } + pr.AssertIsEqual(res, &c.Res) + + // we have commented out another option for the circuit implementation for + // faster testing. Comment the previous part and uncomment the following to + // use it. + + // api.AssertIsDifferent(c.Res.A0.Limbs[0], 0) // dummy constraint to avoid empty circuit + // api.AssertIsDifferent(c.Res.A0.Limbs[1], 0) + // api.AssertIsDifferent(c.Res.A0.Limbs[2], 0) + // api.AssertIsDifferent(c.Res.A0.Limbs[3], 0) + return nil +} + +func Example() { + // generate random data + var a [2]bls12381.G1Affine + var b [2]bls12381.G2Affine + + var s1, s2, s3, s4 fr_bls12381.Element + s1.MustSetRandom() + s2.MustSetRandom() + s3.MustSetRandom() + s4.MustSetRandom() + + a[0].ScalarMultiplicationBase(s1.BigInt(new(big.Int))) + a[1].ScalarMultiplicationBase(s2.BigInt(new(big.Int))) + + b[0].ScalarMultiplicationBase(s3.BigInt(new(big.Int))) + b[1].ScalarMultiplicationBase(s4.BigInt(new(big.Int))) + + res, err := bls12381.Pair([]bls12381.G1Affine{a[0], a[1]}, []bls12381.G2Affine{b[0], b[1]}) + if err != nil { + panic(err) + } + + for _, curve := range []ecc.ID{ecc.BN254, ecc.BLS12_377, ecc.BLS12_381, ecc.BW6_761} { + fmt.Println("testing curve", curve.String()) + + // define assignment + assignment := ExampleCircuit{ + A: [2]sw_bls12381.G1Affine{ + sw_bls12381.NewG1Affine(a[0]), + sw_bls12381.NewG1Affine(a[1]), + }, + B: [2]sw_bls12381.G2Affine{ + sw_bls12381.NewG2Affine(b[0]), + sw_bls12381.NewG2Affine(b[1]), + }, + Res: sw_bls12381.NewGTEl(res), + } + + // run sanity check to see if the solution is valid + err = test.IsSolved(&ExampleCircuit{}, &assignment, curve.ScalarField()) + if err != nil { + panic(err) + } + + // compile the circuit over given curve + ccs, err := frontend.Compile(curve.ScalarField(), r1cs.NewBuilder, &ExampleCircuit{}) + if err != nil { + panic(err) + } + + // setup the keys. NB! Unsafe, should use MPC in production. + pk, vk, err := groth16.Setup(ccs) + if err != nil { + panic(err) + } + // create ICICLE proving key by initializing it from serialized data + pkAcc := icicle_groth.NewProvingKey(curve) + buf := new(bytes.Buffer) + pk.WriteTo(buf) + _, err = pkAcc.ReadFrom(buf) + if err != nil { + panic(err) + } + // create the witness + wit, err := frontend.NewWitness(&assignment, curve.ScalarField()) + if err != nil { + panic(err) + } + + // prove natively + proof, err := groth16.Prove(ccs, pk, wit) + if err != nil { + panic(err) + } + // prove using acceleration. We have commented out possible options for the backend. + proofAcc, err := icicle_groth.Prove(ccs, pkAcc, wit, + // icicle.WithBackendLibrary("/usr/local/lib/backend/"), + // icicle.WithBackend(icicle.CUDA), + // icicle.WithDeviceID(0), + icicle.WithProverOptions(solidity.WithProverTargetSolidityVerifier(backend.GROTH16)), + ) + if err != nil { + panic(err) + } + + // create public part of the witness + pubwit, err := wit.Public() + if err != nil { + panic(err) + } + // ensure that both proofs verify + err = groth16.Verify(proof, vk, pubwit) + if err != nil { + panic(err) + } + err = groth16.Verify(proofAcc, vk, pubwit) + if err != nil { + panic(err) + } + } +} diff --git a/examples/accelerated_gpu/doc.go b/examples/accelerated_gpu/doc.go new file mode 100644 index 0000000000..c68f08e707 --- /dev/null +++ b/examples/accelerated_gpu/doc.go @@ -0,0 +1,14 @@ +// Package accelerated_gpu provides examples on how to use gnark with GPU acceleration. +// +// NB! This example requires a compatible GPU and acceleration library installed. See +// the [icicle] package documentation for details. The example can only be run +// +// To setup: +// +// export CGO_LDFLAGS="-L/usr/local/lib -licicle_device -lstdc++ -lm -Wl,-rpath=/usr/local/lib" +// export ICICLE_BACKEND_INSTALL_DIR="/usr/local/lib/backend/" +// +// To run: +// +// go test -timeout 0m -tags debug,icicle -run ^TestExampleCircuit$ github.com/consensys/gnark/examples/accelerated_gpu -v -count=1 +package accelerated_gpu From cbf26378dac45224ebb997dfe5cc9d55f70e8b8f Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 2 Oct 2025 10:36:14 +0000 Subject: [PATCH 08/24] docs: example --- examples/accelerated_gpu/doc.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/accelerated_gpu/doc.go b/examples/accelerated_gpu/doc.go index c68f08e707..d2d0ab2b11 100644 --- a/examples/accelerated_gpu/doc.go +++ b/examples/accelerated_gpu/doc.go @@ -1,7 +1,9 @@ // Package accelerated_gpu provides examples on how to use gnark with GPU acceleration. // -// NB! This example requires a compatible GPU and acceleration library installed. See -// the [icicle] package documentation for details. The example can only be run +// NB! This example requires a compatible GPU and acceleration library +// installed. See the [github.com/consensys/gnark/accelerated/icicle] package +// documentation for details. The example can only be run when built with +// `icicle` build tag. // // To setup: // @@ -10,5 +12,5 @@ // // To run: // -// go test -timeout 0m -tags debug,icicle -run ^TestExampleCircuit$ github.com/consensys/gnark/examples/accelerated_gpu -v -count=1 +// go test -timeout 0m -tags debug,icicle -run ^Example$ github.com/consensys/gnark/examples/accelerated_gpu -short -v -count=1 package accelerated_gpu From 638932a815e2ed6633c3c0dd360a88da5f6a2763 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 2 Oct 2025 10:37:09 +0000 Subject: [PATCH 09/24] fix: load recursively --- backend/accelerated/icicle/groth16/groth16_icicle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/accelerated/icicle/groth16/groth16_icicle.go b/backend/accelerated/icicle/groth16/groth16_icicle.go index d74b6cbcfc..efb95e8c23 100644 --- a/backend/accelerated/icicle/groth16/groth16_icicle.go +++ b/backend/accelerated/icicle/groth16/groth16_icicle.go @@ -39,7 +39,7 @@ func warmUpDevice(config *icicle.Config) { onceWarmUpDevice.Do(func() { log := logger.Logger() if config.BackendLibs != "" { - err := icicle_runtime.LoadBackend(config.BackendLibs, false) + err := icicle_runtime.LoadBackend(config.BackendLibs, true) if err != icicle_runtime.Success { panic(fmt.Sprintf("custom ICICLE backend loading error: %s", err.AsString())) } From 6871daa9b65bf4a9681399ebecefb134a22bb946 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 2 Oct 2025 10:38:24 +0000 Subject: [PATCH 10/24] chore: log level --- backend/accelerated/icicle/groth16/groth16_icicle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/accelerated/icicle/groth16/groth16_icicle.go b/backend/accelerated/icicle/groth16/groth16_icicle.go index efb95e8c23..06e046b378 100644 --- a/backend/accelerated/icicle/groth16/groth16_icicle.go +++ b/backend/accelerated/icicle/groth16/groth16_icicle.go @@ -53,7 +53,7 @@ func warmUpDevice(config *icicle.Config) { if err != icicle_runtime.Success { panic(fmt.Sprintf("ICICLE get device count error: %s", err.AsString())) } - log.Info().Int("nbDev", nbDev).Msg("ICICLE devices detected") + log.Debug().Int("nbDev", nbDev).Msg("ICICLE devices detected") for id := 0; id < nbDev; id++ { device := icicle_runtime.CreateDevice(config.Backend.String(), id) log.Debug().Int32("id", device.Id).Str("type", device.GetDeviceType()).Msg("ICICLE device created") From 1bd5d1a100f98cd6a5116d1e80e978ce2b883ca3 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 2 Oct 2025 10:38:31 +0000 Subject: [PATCH 11/24] docs: output --- examples/accelerated_gpu/accelerated_gpu_example_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/accelerated_gpu/accelerated_gpu_example_test.go b/examples/accelerated_gpu/accelerated_gpu_example_test.go index 64a4f867a4..790b308738 100644 --- a/examples/accelerated_gpu/accelerated_gpu_example_test.go +++ b/examples/accelerated_gpu/accelerated_gpu_example_test.go @@ -151,4 +151,9 @@ func Example() { panic(err) } } + // Output: + // testing curve bn254 + // testing curve bls12_377 + // testing curve bls12_381 + // testing curve bw6_761 } From d7f7c436f5a282977b3447865e09447e05f77b8b Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 23 Oct 2025 12:19:38 +0000 Subject: [PATCH 12/24] docs: typo fix --- backend/accelerated/icicle/doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/accelerated/icicle/doc.go b/backend/accelerated/icicle/doc.go index 9269261178..14439c85e0 100644 --- a/backend/accelerated/icicle/doc.go +++ b/backend/accelerated/icicle/doc.go @@ -9,7 +9,7 @@ // // # Setup // -// To initialize the ICICILE backend, follow the instructions in the [ICICLE] +// To initialize the ICICLE backend, follow the instructions in the [ICICLE] // repository. Namely, first you should install the ICICLE library: // // git clone github.com/ingonyama-zk/icicle-gnark From 9b790a4ad9cd51beeb2a63fa635af438b577ed5b Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 23 Oct 2025 12:20:10 +0000 Subject: [PATCH 13/24] test: use bw curve --- .../accelerated/icicle/groth16/bw6-761/marshal_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/accelerated/icicle/groth16/bw6-761/marshal_test.go b/backend/accelerated/icicle/groth16/bw6-761/marshal_test.go index c5dd609acb..a91ce3d263 100644 --- a/backend/accelerated/icicle/groth16/bw6-761/marshal_test.go +++ b/backend/accelerated/icicle/groth16/bw6-761/marshal_test.go @@ -29,7 +29,7 @@ func (c *circuit) Define(api frontend.API) error { func TestMarshal(t *testing.T) { assert := test.NewAssert(t) - ccs, err := frontend.Compile(ecc.BLS12_381.ScalarField(), r1cs.NewBuilder, &circuit{}) + ccs, err := frontend.Compile(ecc.BW6_761.ScalarField(), r1cs.NewBuilder, &circuit{}) assert.NoError(err) tCcs := ccs.(*cs_bw6761.R1CS) nativePK := groth16_bw6761.ProvingKey{} @@ -37,7 +37,7 @@ func TestMarshal(t *testing.T) { err = groth16_bw6761.Setup(tCcs, &nativePK, &nativeVK) assert.NoError(err) - pk := groth16.NewProvingKey(ecc.BLS12_381) + pk := groth16.NewProvingKey(ecc.BW6_761) buf := new(bytes.Buffer) _, err = nativePK.WriteTo(buf) assert.NoError(err) @@ -48,7 +48,7 @@ func TestMarshal(t *testing.T) { } assignment := circuit{A: 3, B: 5, Res: 15} - w, err := frontend.NewWitness(&assignment, ecc.BLS12_381.ScalarField()) + w, err := frontend.NewWitness(&assignment, ecc.BW6_761.ScalarField()) assert.NoError(err) pw, err := w.Public() assert.NoError(err) @@ -64,7 +64,7 @@ func TestMarshal(t *testing.T) { func TestMarshal2(t *testing.T) { assert := test.NewAssert(t) - ccs, err := frontend.Compile(ecc.BLS12_381.ScalarField(), r1cs.NewBuilder, &circuit{}) + ccs, err := frontend.Compile(ecc.BW6_761.ScalarField(), r1cs.NewBuilder, &circuit{}) assert.NoError(err) tCcs := ccs.(*cs_bw6761.R1CS) iciPK := icicle_bw6761.ProvingKey{} @@ -83,7 +83,7 @@ func TestMarshal2(t *testing.T) { } assignment := circuit{A: 3, B: 5, Res: 15} - w, err := frontend.NewWitness(&assignment, ecc.BLS12_381.ScalarField()) + w, err := frontend.NewWitness(&assignment, ecc.BW6_761.ScalarField()) assert.NoError(err) pw, err := w.Public() assert.NoError(err) From e3472b9be9913a616a1a52f275609a316562a739 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 23 Oct 2025 12:42:30 +0000 Subject: [PATCH 14/24] docs: typofix --- backend/accelerated/icicle/groth16/bls12-377/icicle.go | 2 +- backend/accelerated/icicle/groth16/bls12-381/icicle.go | 2 +- backend/accelerated/icicle/groth16/bn254/icicle.go | 2 +- backend/accelerated/icicle/groth16/bw6-761/icicle.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/accelerated/icicle/groth16/bls12-377/icicle.go b/backend/accelerated/icicle/groth16/bls12-377/icicle.go index da035d654e..d729282506 100644 --- a/backend/accelerated/icicle/groth16/bls12-377/icicle.go +++ b/backend/accelerated/icicle/groth16/bls12-377/icicle.go @@ -166,7 +166,7 @@ func (pk *ProvingKey) setupDevicePointers(device *icicle_runtime.Device) error { }) /************************* End Commitment Keys Device Setup ***************************/ - /************************* Wait for all data tranfsers ***************************/ + /************************* Wait for all data transfers ***************************/ <-initDomain <-copyDenDone <-copyADone diff --git a/backend/accelerated/icicle/groth16/bls12-381/icicle.go b/backend/accelerated/icicle/groth16/bls12-381/icicle.go index 4b98a3b5f3..315ac69138 100644 --- a/backend/accelerated/icicle/groth16/bls12-381/icicle.go +++ b/backend/accelerated/icicle/groth16/bls12-381/icicle.go @@ -166,7 +166,7 @@ func (pk *ProvingKey) setupDevicePointers(device *icicle_runtime.Device) error { }) /************************* End Commitment Keys Device Setup ***************************/ - /************************* Wait for all data tranfsers ***************************/ + /************************* Wait for all data transfers ***************************/ <-initDomain <-copyDenDone <-copyADone diff --git a/backend/accelerated/icicle/groth16/bn254/icicle.go b/backend/accelerated/icicle/groth16/bn254/icicle.go index 8c1b6ddc80..8fa7aaf153 100644 --- a/backend/accelerated/icicle/groth16/bn254/icicle.go +++ b/backend/accelerated/icicle/groth16/bn254/icicle.go @@ -166,7 +166,7 @@ func (pk *ProvingKey) setupDevicePointers(device *icicle_runtime.Device) error { }) /************************* End Commitment Keys Device Setup ***************************/ - /************************* Wait for all data tranfsers ***************************/ + /************************* Wait for all data transfers ***************************/ <-initDomain <-copyDenDone <-copyADone diff --git a/backend/accelerated/icicle/groth16/bw6-761/icicle.go b/backend/accelerated/icicle/groth16/bw6-761/icicle.go index f76bf103bf..48b26ed0f4 100644 --- a/backend/accelerated/icicle/groth16/bw6-761/icicle.go +++ b/backend/accelerated/icicle/groth16/bw6-761/icicle.go @@ -166,7 +166,7 @@ func (pk *ProvingKey) setupDevicePointers(device *icicle_runtime.Device) error { }) /************************* End Commitment Keys Device Setup ***************************/ - /************************* Wait for all data tranfsers ***************************/ + /************************* Wait for all data transfers ***************************/ <-initDomain <-copyDenDone <-copyADone From 15a2b390f28403660805d0f5c6ba9a8232ea8520 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 23 Oct 2025 12:46:52 +0000 Subject: [PATCH 15/24] docs: fix method doc --- backend/accelerated/icicle/groth16/groth16_all.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/accelerated/icicle/groth16/groth16_all.go b/backend/accelerated/icicle/groth16/groth16_all.go index 5ac309dd5a..9322c3fdbf 100644 --- a/backend/accelerated/icicle/groth16/groth16_all.go +++ b/backend/accelerated/icicle/groth16/groth16_all.go @@ -24,7 +24,8 @@ func NewProof(curveID ecc.ID) groth16.Proof { return groth16.NewProof(curveID) } -// NewProvingKey creates a new empty proving key for deserializing into. It is compatible with [groth16.NewProvingKey]. +// NewCS creates new typed R1CS constraint system for the given curve. It is compatible with [groth16.NewCS]. +// It is used for deserializing R1CS constraint systems. func NewCS(curveID ecc.ID) constraint.ConstraintSystem { return groth16.NewCS(curveID) } From 5659f5ae51316a5c8a0bce9ac12938015eab0954 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 23 Oct 2025 13:40:35 +0000 Subject: [PATCH 16/24] test: refactor marshal test --- .../icicle/groth16/bls12-377/marshal_test.go | 98 ---------------- .../icicle/groth16/bls12-381/marshal_test.go | 98 ---------------- .../icicle/groth16/bn254/marshal_test.go | 98 ---------------- .../icicle/groth16/bw6-761/marshal_test.go | 98 ---------------- .../icicle/groth16/marshal_test.go | 105 ++++++++++++++++++ 5 files changed, 105 insertions(+), 392 deletions(-) delete mode 100644 backend/accelerated/icicle/groth16/bls12-377/marshal_test.go delete mode 100644 backend/accelerated/icicle/groth16/bls12-381/marshal_test.go delete mode 100644 backend/accelerated/icicle/groth16/bn254/marshal_test.go delete mode 100644 backend/accelerated/icicle/groth16/bw6-761/marshal_test.go create mode 100644 backend/accelerated/icicle/groth16/marshal_test.go diff --git a/backend/accelerated/icicle/groth16/bls12-377/marshal_test.go b/backend/accelerated/icicle/groth16/bls12-377/marshal_test.go deleted file mode 100644 index cc67d71f9d..0000000000 --- a/backend/accelerated/icicle/groth16/bls12-377/marshal_test.go +++ /dev/null @@ -1,98 +0,0 @@ -//go:build icicle - -package bls12377_test - -import ( - "bytes" - "testing" - - "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark/backend" - icicle_bls12377 "github.com/consensys/gnark/backend/accelerated/icicle/groth16/bls12-377" - "github.com/consensys/gnark/backend/groth16" - groth16_bls12377 "github.com/consensys/gnark/backend/groth16/bls12-377" - cs_bls12377 "github.com/consensys/gnark/constraint/bls12-377" - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/frontend/cs/r1cs" - "github.com/consensys/gnark/test" -) - -type circuit struct { - A, B frontend.Variable `gnark:",public"` - Res frontend.Variable -} - -func (c *circuit) Define(api frontend.API) error { - api.AssertIsEqual(api.Mul(c.A, c.B), c.Res) - return nil -} - -func TestMarshal(t *testing.T) { - assert := test.NewAssert(t) - ccs, err := frontend.Compile(ecc.BLS12_377.ScalarField(), r1cs.NewBuilder, &circuit{}) - assert.NoError(err) - tCcs := ccs.(*cs_bls12377.R1CS) - nativePK := groth16_bls12377.ProvingKey{} - nativeVK := groth16_bls12377.VerifyingKey{} - err = groth16_bls12377.Setup(tCcs, &nativePK, &nativeVK) - assert.NoError(err) - - pk := groth16.NewProvingKey(ecc.BLS12_377) - buf := new(bytes.Buffer) - _, err = nativePK.WriteTo(buf) - assert.NoError(err) - _, err = pk.ReadFrom(buf) - assert.NoError(err) - if pk.IsDifferent(&nativePK) { - t.Error("marshal output difference") - } - - assignment := circuit{A: 3, B: 5, Res: 15} - w, err := frontend.NewWitness(&assignment, ecc.BLS12_377.ScalarField()) - assert.NoError(err) - pw, err := w.Public() - assert.NoError(err) - proofNative, err := groth16_bls12377.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) { - assert := test.NewAssert(t) - ccs, err := frontend.Compile(ecc.BLS12_377.ScalarField(), r1cs.NewBuilder, &circuit{}) - assert.NoError(err) - tCcs := ccs.(*cs_bls12377.R1CS) - iciPK := icicle_bls12377.ProvingKey{} - iciVK := groth16_bls12377.VerifyingKey{} - err = groth16_bls12377.Setup(tCcs, &iciPK.ProvingKey, &iciVK) - assert.NoError(err) - - nativePK := groth16_bls12377.ProvingKey{} - buf := new(bytes.Buffer) - _, err = iciPK.WriteTo(buf) - assert.NoError(err) - _, err = nativePK.ReadFrom(buf) - assert.NoError(err) - if iciPK.IsDifferent(&nativePK) { - t.Error("marshal output difference") - } - - assignment := circuit{A: 3, B: 5, Res: 15} - w, err := frontend.NewWitness(&assignment, ecc.BLS12_377.ScalarField()) - assert.NoError(err) - pw, err := w.Public() - assert.NoError(err) - proofNative, err := groth16_bls12377.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/accelerated/icicle/groth16/bls12-381/marshal_test.go b/backend/accelerated/icicle/groth16/bls12-381/marshal_test.go deleted file mode 100644 index a7ce3784d4..0000000000 --- a/backend/accelerated/icicle/groth16/bls12-381/marshal_test.go +++ /dev/null @@ -1,98 +0,0 @@ -//go:build icicle - -package bls12381_test - -import ( - "bytes" - "testing" - - "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark/backend" - icicle_bls12381 "github.com/consensys/gnark/backend/accelerated/icicle/groth16/bls12-381" - "github.com/consensys/gnark/backend/groth16" - groth16_bls12381 "github.com/consensys/gnark/backend/groth16/bls12-381" - cs_bls12381 "github.com/consensys/gnark/constraint/bls12-381" - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/frontend/cs/r1cs" - "github.com/consensys/gnark/test" -) - -type circuit struct { - A, B frontend.Variable `gnark:",public"` - Res frontend.Variable -} - -func (c *circuit) Define(api frontend.API) error { - api.AssertIsEqual(api.Mul(c.A, c.B), c.Res) - return nil -} - -func TestMarshal(t *testing.T) { - assert := test.NewAssert(t) - ccs, err := frontend.Compile(ecc.BLS12_381.ScalarField(), r1cs.NewBuilder, &circuit{}) - assert.NoError(err) - tCcs := ccs.(*cs_bls12381.R1CS) - nativePK := groth16_bls12381.ProvingKey{} - nativeVK := groth16_bls12381.VerifyingKey{} - err = groth16_bls12381.Setup(tCcs, &nativePK, &nativeVK) - assert.NoError(err) - - pk := groth16.NewProvingKey(ecc.BLS12_381) - buf := new(bytes.Buffer) - _, err = nativePK.WriteTo(buf) - assert.NoError(err) - _, err = pk.ReadFrom(buf) - assert.NoError(err) - if pk.IsDifferent(&nativePK) { - t.Error("marshal output difference") - } - - assignment := circuit{A: 3, B: 5, Res: 15} - w, err := frontend.NewWitness(&assignment, ecc.BLS12_381.ScalarField()) - assert.NoError(err) - pw, err := w.Public() - assert.NoError(err) - proofNative, err := groth16_bls12381.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) { - assert := test.NewAssert(t) - ccs, err := frontend.Compile(ecc.BLS12_381.ScalarField(), r1cs.NewBuilder, &circuit{}) - assert.NoError(err) - tCcs := ccs.(*cs_bls12381.R1CS) - iciPK := icicle_bls12381.ProvingKey{} - iciVK := groth16_bls12381.VerifyingKey{} - err = groth16_bls12381.Setup(tCcs, &iciPK.ProvingKey, &iciVK) - assert.NoError(err) - - nativePK := groth16_bls12381.ProvingKey{} - buf := new(bytes.Buffer) - _, err = iciPK.WriteTo(buf) - assert.NoError(err) - _, err = nativePK.ReadFrom(buf) - assert.NoError(err) - if iciPK.IsDifferent(&nativePK) { - t.Error("marshal output difference") - } - - assignment := circuit{A: 3, B: 5, Res: 15} - w, err := frontend.NewWitness(&assignment, ecc.BLS12_381.ScalarField()) - assert.NoError(err) - pw, err := w.Public() - assert.NoError(err) - proofNative, err := groth16_bls12381.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/accelerated/icicle/groth16/bn254/marshal_test.go b/backend/accelerated/icicle/groth16/bn254/marshal_test.go deleted file mode 100644 index eda2183226..0000000000 --- a/backend/accelerated/icicle/groth16/bn254/marshal_test.go +++ /dev/null @@ -1,98 +0,0 @@ -//go:build icicle - -package bn254_test - -import ( - "bytes" - "testing" - - "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark/backend" - icicle_bn254 "github.com/consensys/gnark/backend/accelerated/icicle/groth16/bn254" - "github.com/consensys/gnark/backend/groth16" - groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254" - cs_bn254 "github.com/consensys/gnark/constraint/bn254" - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/frontend/cs/r1cs" - "github.com/consensys/gnark/test" -) - -type circuit struct { - A, B frontend.Variable `gnark:",public"` - Res frontend.Variable -} - -func (c *circuit) Define(api frontend.API) error { - api.AssertIsEqual(api.Mul(c.A, c.B), c.Res) - return nil -} - -func TestMarshal(t *testing.T) { - assert := test.NewAssert(t) - ccs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit{}) - assert.NoError(err) - tCcs := ccs.(*cs_bn254.R1CS) - nativePK := groth16_bn254.ProvingKey{} - nativeVK := groth16_bn254.VerifyingKey{} - err = groth16_bn254.Setup(tCcs, &nativePK, &nativeVK) - assert.NoError(err) - - pk := groth16.NewProvingKey(ecc.BN254) - buf := new(bytes.Buffer) - _, err = nativePK.WriteTo(buf) - assert.NoError(err) - _, err = pk.ReadFrom(buf) - assert.NoError(err) - 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) { - assert := test.NewAssert(t) - ccs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit{}) - assert.NoError(err) - tCcs := ccs.(*cs_bn254.R1CS) - iciPK := icicle_bn254.ProvingKey{} - iciVK := groth16_bn254.VerifyingKey{} - err = groth16_bn254.Setup(tCcs, &iciPK.ProvingKey, &iciVK) - assert.NoError(err) - - nativePK := groth16_bn254.ProvingKey{} - buf := new(bytes.Buffer) - _, err = iciPK.WriteTo(buf) - assert.NoError(err) - _, err = nativePK.ReadFrom(buf) - assert.NoError(err) - 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/accelerated/icicle/groth16/bw6-761/marshal_test.go b/backend/accelerated/icicle/groth16/bw6-761/marshal_test.go deleted file mode 100644 index a91ce3d263..0000000000 --- a/backend/accelerated/icicle/groth16/bw6-761/marshal_test.go +++ /dev/null @@ -1,98 +0,0 @@ -//go:build icicle - -package bw6761_test - -import ( - "bytes" - "testing" - - "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark/backend" - icicle_bw6761 "github.com/consensys/gnark/backend/accelerated/icicle/groth16/bw6-761" - "github.com/consensys/gnark/backend/groth16" - groth16_bw6761 "github.com/consensys/gnark/backend/groth16/bw6-761" - cs_bw6761 "github.com/consensys/gnark/constraint/bw6-761" - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/frontend/cs/r1cs" - "github.com/consensys/gnark/test" -) - -type circuit struct { - A, B frontend.Variable `gnark:",public"` - Res frontend.Variable -} - -func (c *circuit) Define(api frontend.API) error { - api.AssertIsEqual(api.Mul(c.A, c.B), c.Res) - return nil -} - -func TestMarshal(t *testing.T) { - assert := test.NewAssert(t) - ccs, err := frontend.Compile(ecc.BW6_761.ScalarField(), r1cs.NewBuilder, &circuit{}) - assert.NoError(err) - tCcs := ccs.(*cs_bw6761.R1CS) - nativePK := groth16_bw6761.ProvingKey{} - nativeVK := groth16_bw6761.VerifyingKey{} - err = groth16_bw6761.Setup(tCcs, &nativePK, &nativeVK) - assert.NoError(err) - - pk := groth16.NewProvingKey(ecc.BW6_761) - buf := new(bytes.Buffer) - _, err = nativePK.WriteTo(buf) - assert.NoError(err) - _, err = pk.ReadFrom(buf) - assert.NoError(err) - if pk.IsDifferent(&nativePK) { - t.Error("marshal output difference") - } - - assignment := circuit{A: 3, B: 5, Res: 15} - w, err := frontend.NewWitness(&assignment, ecc.BW6_761.ScalarField()) - assert.NoError(err) - pw, err := w.Public() - assert.NoError(err) - proofNative, err := groth16_bw6761.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) { - assert := test.NewAssert(t) - ccs, err := frontend.Compile(ecc.BW6_761.ScalarField(), r1cs.NewBuilder, &circuit{}) - assert.NoError(err) - tCcs := ccs.(*cs_bw6761.R1CS) - iciPK := icicle_bw6761.ProvingKey{} - iciVK := groth16_bw6761.VerifyingKey{} - err = groth16_bw6761.Setup(tCcs, &iciPK.ProvingKey, &iciVK) - assert.NoError(err) - - nativePK := groth16_bw6761.ProvingKey{} - buf := new(bytes.Buffer) - _, err = iciPK.WriteTo(buf) - assert.NoError(err) - _, err = nativePK.ReadFrom(buf) - assert.NoError(err) - if iciPK.IsDifferent(&nativePK) { - t.Error("marshal output difference") - } - - assignment := circuit{A: 3, B: 5, Res: 15} - w, err := frontend.NewWitness(&assignment, ecc.BW6_761.ScalarField()) - assert.NoError(err) - pw, err := w.Public() - assert.NoError(err) - proofNative, err := groth16_bw6761.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/accelerated/icicle/groth16/marshal_test.go b/backend/accelerated/icicle/groth16/marshal_test.go new file mode 100644 index 0000000000..663dcd89ea --- /dev/null +++ b/backend/accelerated/icicle/groth16/marshal_test.go @@ -0,0 +1,105 @@ +//go:build icicle + +package groth16_test + +import ( + "bytes" + "fmt" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend/accelerated/icicle/groth16" + icicle_groth16 "github.com/consensys/gnark/backend/accelerated/icicle/groth16" + native_groth16 "github.com/consensys/gnark/backend/groth16" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" + "github.com/consensys/gnark/test" +) + +type circuit struct { + A, B frontend.Variable `gnark:",public"` + Res frontend.Variable +} + +func (c *circuit) Define(api frontend.API) error { + api.AssertIsEqual(api.Mul(c.A, c.B), c.Res) + return nil +} + +func testMarshalNativeToIcicle(t *testing.T, curve ecc.ID) { + assert := test.NewAssert(t) + ccs, err := frontend.Compile(curve.ScalarField(), r1cs.NewBuilder, &circuit{}) + assert.NoError(err) + nativePK, vk, err := native_groth16.Setup(ccs) + assert.NoError(err) + iciPK := icicle_groth16.NewProvingKey(curve) + buf := new(bytes.Buffer) + _, err = nativePK.WriteTo(buf) + assert.NoError(err) + _, err = iciPK.ReadFrom(buf) + assert.NoError(err) + if iciPK.IsDifferent(nativePK) { + t.Error("marshal output difference") + } + + assignment := circuit{A: 3, B: 5, Res: 15} + w, err := frontend.NewWitness(&assignment, curve.ScalarField()) + assert.NoError(err) + pw, err := w.Public() + assert.NoError(err) + proofNative, err := native_groth16.Prove(ccs, nativePK, w) + assert.NoError(err) + proofIcicle, err := icicle_groth16.Prove(ccs, iciPK, w) + assert.NoError(err) + err = groth16.Verify(proofNative, vk, pw) + assert.NoError(err) + err = groth16.Verify(proofIcicle, vk, pw) + assert.NoError(err) +} + +func testMarshalIcicleToNative(t *testing.T, curve ecc.ID) { + assert := test.NewAssert(t) + ccs, err := frontend.Compile(curve.ScalarField(), r1cs.NewBuilder, &circuit{}) + assert.NoError(err) + iciPK, vk, err := icicle_groth16.Setup(ccs) + assert.NoError(err) + nativePK := native_groth16.NewProvingKey(curve) + buf := new(bytes.Buffer) + _, err = iciPK.WriteTo(buf) + assert.NoError(err) + _, err = nativePK.ReadFrom(buf) + assert.NoError(err) + if iciPK.IsDifferent(nativePK) { + t.Error("marshal output difference") + } + + assignment := circuit{A: 3, B: 5, Res: 15} + w, err := frontend.NewWitness(&assignment, curve.ScalarField()) + assert.NoError(err) + pw, err := w.Public() + assert.NoError(err) + proofNative, err := native_groth16.Prove(ccs, nativePK, w) + assert.NoError(err) + proofIcicle, err := icicle_groth16.Prove(ccs, iciPK, w) + assert.NoError(err) + err = groth16.Verify(proofNative, vk, pw) + assert.NoError(err) + err = groth16.Verify(proofIcicle, vk, pw) + assert.NoError(err) +} + +func TestMarshalNativeToIcicle(t *testing.T) { + for _, curve := range []ecc.ID{ecc.BLS12_377, ecc.BLS12_381, ecc.BN254, ecc.BW6_761} { + t.Run(fmt.Sprintf("curve=%s", curve.String()), func(t *testing.T) { + testMarshalNativeToIcicle(t, curve) + }) + } +} + +func TestMarshalIcicleToNative(t *testing.T) { + for _, curve := range []ecc.ID{ecc.BLS12_377, ecc.BLS12_381, ecc.BN254, ecc.BW6_761} { + t.Run(fmt.Sprintf("curve=%s", curve.String()), func(t *testing.T) { + testMarshalIcicleToNative(t, curve) + }) + } +} From 0834221e37e4361eb84c013a1e56a955b9a838fd Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 23 Oct 2025 15:51:03 +0200 Subject: [PATCH 17/24] Update examples/accelerated_gpu/doc.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- examples/accelerated_gpu/doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/accelerated_gpu/doc.go b/examples/accelerated_gpu/doc.go index d2d0ab2b11..70a6237e8c 100644 --- a/examples/accelerated_gpu/doc.go +++ b/examples/accelerated_gpu/doc.go @@ -1,7 +1,7 @@ // Package accelerated_gpu provides examples on how to use gnark with GPU acceleration. // // NB! This example requires a compatible GPU and acceleration library -// installed. See the [github.com/consensys/gnark/accelerated/icicle] package +// installed. See the [github.com/consensys/gnark/backend/accelerated/icicle] package // documentation for details. The example can only be run when built with // `icicle` build tag. // From f8322aef0d2b990bc36e9da20ff11da37e66855b Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Thu, 23 Oct 2025 15:51:12 +0200 Subject: [PATCH 18/24] Update backend/accelerated/icicle/doc.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backend/accelerated/icicle/doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/accelerated/icicle/doc.go b/backend/accelerated/icicle/doc.go index 14439c85e0..6ea73505f9 100644 --- a/backend/accelerated/icicle/doc.go +++ b/backend/accelerated/icicle/doc.go @@ -16,7 +16,7 @@ // cd icicle-gnark/wrappers/golang // sudo ./build.sh -curve=all // -// After that, the libraries are installed in `/usr/local/lib“ and backend in +// After that, the libraries are installed in `/usr/local/lib` and backend in // `/usr/local/lib/backend`. // // Now set the environment variables: From c2f41a4cd4538ce31707ebdf04b2439476fa84a9 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 27 Oct 2025 17:50:11 +0000 Subject: [PATCH 19/24] fix: target solidity options everywhere --- examples/accelerated_gpu/accelerated_gpu_example_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/accelerated_gpu/accelerated_gpu_example_test.go b/examples/accelerated_gpu/accelerated_gpu_example_test.go index 790b308738..3a48d1c8e2 100644 --- a/examples/accelerated_gpu/accelerated_gpu_example_test.go +++ b/examples/accelerated_gpu/accelerated_gpu_example_test.go @@ -121,7 +121,7 @@ func Example() { } // prove natively - proof, err := groth16.Prove(ccs, pk, wit) + proof, err := groth16.Prove(ccs, pk, wit, solidity.WithProverTargetSolidityVerifier(backend.GROTH16)) if err != nil { panic(err) } @@ -142,11 +142,11 @@ func Example() { panic(err) } // ensure that both proofs verify - err = groth16.Verify(proof, vk, pubwit) + err = groth16.Verify(proof, vk, pubwit, solidity.WithVerifierTargetSolidityVerifier(backend.GROTH16)) if err != nil { panic(err) } - err = groth16.Verify(proofAcc, vk, pubwit) + err = groth16.Verify(proofAcc, vk, pubwit, solidity.WithVerifierTargetSolidityVerifier(backend.GROTH16)) if err != nil { panic(err) } From 327612c4cf2c42e4db747fc1dab3d93aaf0b79eb Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 28 Oct 2025 09:00:51 +0000 Subject: [PATCH 20/24] feat: use code generation --- .../icicle/internal/generator/main.go | 80 ++ .../templates/groth16.icicle.doc.go.tmpl | 2 + .../templates/groth16.icicle.go.tmpl | 745 ++++++++++++++++++ .../groth16.icicle.provingkey.go.tmpl | 28 + 4 files changed, 855 insertions(+) create mode 100644 backend/accelerated/icicle/internal/generator/main.go create mode 100644 backend/accelerated/icicle/internal/generator/templates/groth16.icicle.doc.go.tmpl create mode 100644 backend/accelerated/icicle/internal/generator/templates/groth16.icicle.go.tmpl create mode 100644 backend/accelerated/icicle/internal/generator/templates/groth16.icicle.provingkey.go.tmpl diff --git a/backend/accelerated/icicle/internal/generator/main.go b/backend/accelerated/icicle/internal/generator/main.go new file mode 100644 index 0000000000..bf1d6d1af7 --- /dev/null +++ b/backend/accelerated/icicle/internal/generator/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/consensys/bavard" +) + +type templateData struct { + Curve string + CurveID string + RootPath string + CurvePkg string + + G2ExtensionDegree int +} + +//go:generate go run main.go +func main() { + bn254 := templateData{ + Curve: "BN254", + CurveID: "BN254", + RootPath: "../../groth16/bn254", + CurvePkg: "bn254", + G2ExtensionDegree: 6, + } + bls12_377 := templateData{ + Curve: "BLS12-377", + CurveID: "BLS12_377", + RootPath: "../../groth16/bls12-377", + CurvePkg: "bls12377", + G2ExtensionDegree: 6, + } + bls12_381 := templateData{ + Curve: "BLS12-381", + CurveID: "BLS12_381", + RootPath: "../../groth16/bls12-381", + CurvePkg: "bls12381", + G2ExtensionDegree: 6, + } + bw6_761 := templateData{ + Curve: "BW6-761", + CurveID: "BW6_761", + RootPath: "../../groth16/bw6-761", + CurvePkg: "bw6761", + G2ExtensionDegree: 3, + } + data := []templateData{bn254, bls12_377, bls12_381, bw6_761} + + const copyrightHolder = "Consensys Software Inc." + var bgen = bavard.NewBatchGenerator(copyrightHolder, 2025, "gnark") + + for _, d := range data { + entries := []bavard.Entry{ + {File: filepath.Join(d.RootPath, "doc.go"), Templates: []string{"groth16.icicle.doc.go.tmpl"}}, + {File: filepath.Join(d.RootPath, "icicle.go"), Templates: []string{"groth16.icicle.go.tmpl"}}, + {File: filepath.Join(d.RootPath, "provingkey.go"), Templates: []string{"groth16.icicle.provingkey.go.tmpl"}}, + } + if err := bgen.Generate(d, d.CurvePkg, "./templates/", entries...); err != nil { + panic(err) + } + } + + runCmd("gofmt", "-w", "../../groth16") + runCmd("goimports", "-w", "../../groth16") +} + +func runCmd(name string, arg ...string) { + fmt.Println(name, strings.Join(arg, " ")) + cmd := exec.Command(name, arg...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + panic(err) + } +} diff --git a/backend/accelerated/icicle/internal/generator/templates/groth16.icicle.doc.go.tmpl b/backend/accelerated/icicle/internal/generator/templates/groth16.icicle.doc.go.tmpl new file mode 100644 index 0000000000..14f4d886ec --- /dev/null +++ b/backend/accelerated/icicle/internal/generator/templates/groth16.icicle.doc.go.tmpl @@ -0,0 +1,2 @@ +// Package {{ .CurvePkg }} implements ICICLE acceleration for {{ .Curve }} Groth16 backend. +package {{ .CurvePkg }} \ No newline at end of file diff --git a/backend/accelerated/icicle/internal/generator/templates/groth16.icicle.go.tmpl b/backend/accelerated/icicle/internal/generator/templates/groth16.icicle.go.tmpl new file mode 100644 index 0000000000..d09f1e166c --- /dev/null +++ b/backend/accelerated/icicle/internal/generator/templates/groth16.icicle.go.tmpl @@ -0,0 +1,745 @@ +//go:build icicle + +import ( + "fmt" + "math/big" + "math/bits" + "os" + "slices" + "time" + + "github.com/consensys/gnark-crypto/ecc" + curve "github.com/consensys/gnark-crypto/ecc/{{ toLower .Curve }}" + "github.com/consensys/gnark-crypto/ecc/{{ toLower .Curve }}/fp" + "github.com/consensys/gnark-crypto/ecc/{{ toLower .Curve }}/fr" + "github.com/consensys/gnark-crypto/ecc/{{ toLower .Curve }}/fr/fft" + "github.com/consensys/gnark-crypto/ecc/{{ toLower .Curve }}/fr/hash_to_field" + "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/accelerated/icicle" + groth16_{{ .CurvePkg }} "github.com/consensys/gnark/backend/groth16/{{ toLower .Curve }}" + "github.com/consensys/gnark/backend/witness" + "github.com/consensys/gnark/constraint" + cs "github.com/consensys/gnark/constraint/{{ toLower .Curve }}" + "github.com/consensys/gnark/constraint/solver" + "github.com/consensys/gnark/internal/utils" + "github.com/consensys/gnark/logger" + + icicle_core "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/core" + icicle_{{ .CurvePkg }} "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/{{ .CurvePkg }}" + icicle_g2 "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/{{ .CurvePkg }}/g2" + icicle_msm "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/{{ .CurvePkg }}/msm" + icicle_ntt "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/{{ .CurvePkg }}/ntt" + icicle_vecops "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/curves/{{ .CurvePkg }}/vecOps" + icicle_runtime "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/runtime" + + fcs "github.com/consensys/gnark/frontend/cs" +) + +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{} + 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(gen, big.NewInt(int64(pk.Domain.Cardinality))) + denI.Sub(&denI, &oneI).Inverse(&denI) + + log2SizeFloor := bits.Len(uint(n)) - 1 + denIcicleArr := []fr.Element{denI} + for i := 0; i < log2SizeFloor; i++ { + denIcicleArr = append(denIcicleArr, denIcicleArr...) + } + pow2Remainder := n - 1< 0 { + startPoKBatch := time.Now() + poksIcicle := make([]icicle_core.HostSlice[icicle_{{ .CurvePkg }}.Projective], numCommitmentKeys) + for i := range poksIcicle { + poksIcicle[i] = make(icicle_core.HostSlice[icicle_{{ .CurvePkg }}.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 { + 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) + + solution.A = nil + solution.B = nil + solution.C = nil + 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 icicle_core.DeviceSlice + chWireValuesA, chWireValuesB := make(chan struct{}), make(chan struct{}) + + 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] { + continue + } + wireValuesA[j] = wireValues[i] + j++ + } + + // Copy scalars to the device and retain ptr to them + wireValuesAHost := (icicle_core.HostSlice[fr.Element])(wireValuesA) + wireValuesAHost.CopyToDevice(&wireValuesADevice, true) + if err := icicle_{{ .CurvePkg }}.FromMontgomery(wireValuesADevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("copy A to device: %s", err.AsString())) + } + + close(chWireValuesA) + }) + + 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] { + continue + } + wireValuesB[j] = wireValues[i] + j++ + } + + // Copy scalars to the device and retain ptr to them + wireValuesBHost := (icicle_core.HostSlice[fr.Element])(wireValuesB) + wireValuesBHost.CopyToDevice(&wireValuesBDevice, true) + if err := icicle_{{ .CurvePkg }}.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 + var _r, _s, _kr fr.Element + if _, err := _r.SetRandom(); err != nil { + return nil, err + } + if _, err := _s.SetRandom(); err != nil { + return nil, err + } + _kr.Mul(&_r, &_s).Neg(&_kr) + + _r.BigInt(&r) + _s.BigInt(&s) + + // computes r[δ], s[δ], kr[δ] + 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_{{ .CurvePkg }}.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 + + cfg := icicle_msm.GetDefaultMSMConfig() + res := make(icicle_core.HostSlice[icicle_{{ .CurvePkg }}.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) + + cfg := icicle_msm.GetDefaultMSMConfig() + resKrs2 := make(icicle_core.HostSlice[icicle_{{ .CurvePkg }}.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()) + _wireValues := filterHeap(wireValues[r1cs.GetNbPublicVariables():], r1cs.GetNbPublicVariables(), slices.Concat(toRemove...)) + _wireValuesHost := (icicle_core.HostSlice[fr.Element])(_wireValues) + resKrs := make(icicle_core.HostSlice[icicle_{{ .CurvePkg }}.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())) + } + 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) + + p1.ScalarMultiplication(&bs1, &r) + krs.AddAssign(&p1) + + proof.Krs.FromJacobian(&krs) + + return nil + } + + computeBS2 := func() error { + // Bs2 (1 multi exp G2 - size = len(wires)) + 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") + } + Bs = g2ProjectiveToG2Jac(&res[0]) + + deltaS.FromAffine(&pk.G2.Delta) + deltaS.ScalarMultiplication(&deltaS, &s) + Bs.AddAssign(&deltaS) + Bs.AddMixed(&pk.G2.Beta) + + proof.Bs.FromJacobian(&Bs) + 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 + + 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()) + } + }) + + return proof, nil +} + +// if len(toRemove) == 0, returns slice +// else, returns a new slice without the indexes in toRemove. The first value in the slice is taken as indexes as sliceFirstIndex +// this assumes len(slice) > len(toRemove) +// filterHeap modifies toRemove +func filterHeap(slice []fr.Element, sliceFirstIndex int, toRemove []int) (r []fr.Element) { + + if len(toRemove) == 0 { + return slice + } + + heap := utils.IntHeap(toRemove) + heap.Heapify() + + r = make([]fr.Element, 0, len(slice)) + + // note: we can optimize that for the likely case where len(slice) >>> len(toRemove) + for i := 0; i < len(slice); i++ { + if len(heap) > 0 && i+sliceFirstIndex == heap[0] { + for len(heap) > 0 && i+sliceFirstIndex == heap[0] { + heap.Pop() + } + continue + } + r = append(r, slice[i]) + } + + return +} + +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 + padding := make([]fr.Element, int(pk.Domain.Cardinality)-n) + a = append(a, padding...) + b = append(b, padding...) + 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) + } + + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, a, computeADone) + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, b, computeBDone) + icicle_runtime.RunOnDevice(device, computeInttNttOnDevice, c, computeCDone) + + aDevice := <-computeADone + bDevice := <-computeBDone + cDevice := <-computeCDone + + // 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_{{ .CurvePkg }}.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_{{ .CurvePkg }}.FromMontgomery(aDevice); err != icicle_runtime.Success { + panic(fmt.Sprintf("fromMontgomery a in computeH: %s", err.AsString())) + } + + if isProfileMode { + log.Debug().Dur("took", time.Since(startTotal)).Msg("computeH: Total") + } + return aDevice +} diff --git a/backend/accelerated/icicle/internal/generator/templates/groth16.icicle.provingkey.go.tmpl b/backend/accelerated/icicle/internal/generator/templates/groth16.icicle.provingkey.go.tmpl new file mode 100644 index 0000000000..8014cd3ea9 --- /dev/null +++ b/backend/accelerated/icicle/internal/generator/templates/groth16.icicle.provingkey.go.tmpl @@ -0,0 +1,28 @@ +//go:build icicle + +import ( + "github.com/consensys/gnark-crypto/ecc/{{ toLower .Curve }}/fr" + groth16_{{ .CurvePkg }} "github.com/consensys/gnark/backend/groth16/{{ toLower .Curve }}" + icicle_core "github.com/ingonyama-zk/icicle-gnark/v3/wrappers/golang/core" +) + +type deviceInfo struct { + CosetGenerator [fr.Limbs * 2]uint32 + G1Device struct { + A, B, K, Z icicle_core.DeviceSlice + } + G2Device struct { + B icicle_core.DeviceSlice + } + DenDevice icicle_core.DeviceSlice + + CommitmentKeysDevice struct { + Basis []icicle_core.DeviceSlice + BasisExpSigma []icicle_core.DeviceSlice // we compute in batch + } +} + +type ProvingKey struct { + groth16_{{ .CurvePkg }}.ProvingKey + *deviceInfo +} From 8b3c2f08e56170ef7fa2744db2038e060fa2b2ce Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 28 Oct 2025 09:01:18 +0000 Subject: [PATCH 21/24] chore: go generate --- backend/accelerated/icicle/groth16/bls12-377/doc.go | 7 ++++++- backend/accelerated/icicle/groth16/bls12-377/icicle.go | 5 +++++ backend/accelerated/icicle/groth16/bls12-377/provingkey.go | 5 +++++ backend/accelerated/icicle/groth16/bls12-381/doc.go | 7 ++++++- backend/accelerated/icicle/groth16/bls12-381/icicle.go | 5 +++++ backend/accelerated/icicle/groth16/bls12-381/provingkey.go | 5 +++++ backend/accelerated/icicle/groth16/bn254/doc.go | 7 ++++++- backend/accelerated/icicle/groth16/bn254/icicle.go | 5 +++++ backend/accelerated/icicle/groth16/bn254/provingkey.go | 5 +++++ backend/accelerated/icicle/groth16/bw6-761/doc.go | 7 ++++++- backend/accelerated/icicle/groth16/bw6-761/icicle.go | 5 +++++ backend/accelerated/icicle/groth16/bw6-761/provingkey.go | 5 +++++ 12 files changed, 64 insertions(+), 4 deletions(-) diff --git a/backend/accelerated/icicle/groth16/bls12-377/doc.go b/backend/accelerated/icicle/groth16/bls12-377/doc.go index bf2f0b65ac..f130580601 100644 --- a/backend/accelerated/icicle/groth16/bls12-377/doc.go +++ b/backend/accelerated/icicle/groth16/bls12-377/doc.go @@ -1,2 +1,7 @@ -// Package icicle_bls12377 implements ICICLE acceleration for BLS12-377 Groth16 backend. +// Copyright 2025 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by gnark DO NOT EDIT + +// Package bls12377 implements ICICLE acceleration for BLS12-377 Groth16 backend. package bls12377 diff --git a/backend/accelerated/icicle/groth16/bls12-377/icicle.go b/backend/accelerated/icicle/groth16/bls12-377/icicle.go index d729282506..89f88729fa 100644 --- a/backend/accelerated/icicle/groth16/bls12-377/icicle.go +++ b/backend/accelerated/icicle/groth16/bls12-377/icicle.go @@ -1,3 +1,8 @@ +// Copyright 2025 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by gnark DO NOT EDIT + //go:build icicle package bls12377 diff --git a/backend/accelerated/icicle/groth16/bls12-377/provingkey.go b/backend/accelerated/icicle/groth16/bls12-377/provingkey.go index 4ab23308e1..a92534304f 100644 --- a/backend/accelerated/icicle/groth16/bls12-377/provingkey.go +++ b/backend/accelerated/icicle/groth16/bls12-377/provingkey.go @@ -1,3 +1,8 @@ +// Copyright 2025 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by gnark DO NOT EDIT + //go:build icicle package bls12377 diff --git a/backend/accelerated/icicle/groth16/bls12-381/doc.go b/backend/accelerated/icicle/groth16/bls12-381/doc.go index 7a264af031..72b4e78ca3 100644 --- a/backend/accelerated/icicle/groth16/bls12-381/doc.go +++ b/backend/accelerated/icicle/groth16/bls12-381/doc.go @@ -1,2 +1,7 @@ -// Package icicle_bls12381 implements ICICLE acceleration for BLS12-381 Groth16 backend. +// Copyright 2025 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by gnark DO NOT EDIT + +// Package bls12381 implements ICICLE acceleration for BLS12-381 Groth16 backend. package bls12381 diff --git a/backend/accelerated/icicle/groth16/bls12-381/icicle.go b/backend/accelerated/icicle/groth16/bls12-381/icicle.go index 315ac69138..0064e39c56 100644 --- a/backend/accelerated/icicle/groth16/bls12-381/icicle.go +++ b/backend/accelerated/icicle/groth16/bls12-381/icicle.go @@ -1,3 +1,8 @@ +// Copyright 2025 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by gnark DO NOT EDIT + //go:build icicle package bls12381 diff --git a/backend/accelerated/icicle/groth16/bls12-381/provingkey.go b/backend/accelerated/icicle/groth16/bls12-381/provingkey.go index d6a9ed4525..43f12115f4 100644 --- a/backend/accelerated/icicle/groth16/bls12-381/provingkey.go +++ b/backend/accelerated/icicle/groth16/bls12-381/provingkey.go @@ -1,3 +1,8 @@ +// Copyright 2025 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by gnark DO NOT EDIT + //go:build icicle package bls12381 diff --git a/backend/accelerated/icicle/groth16/bn254/doc.go b/backend/accelerated/icicle/groth16/bn254/doc.go index dd4d21076b..51a5ea6963 100644 --- a/backend/accelerated/icicle/groth16/bn254/doc.go +++ b/backend/accelerated/icicle/groth16/bn254/doc.go @@ -1,2 +1,7 @@ -// Package icicle_bn254 implements ICICLE acceleration for BN254 Groth16 backend. +// Copyright 2025 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by gnark DO NOT EDIT + +// Package bn254 implements ICICLE acceleration for BN254 Groth16 backend. package bn254 diff --git a/backend/accelerated/icicle/groth16/bn254/icicle.go b/backend/accelerated/icicle/groth16/bn254/icicle.go index 8fa7aaf153..04771848a4 100644 --- a/backend/accelerated/icicle/groth16/bn254/icicle.go +++ b/backend/accelerated/icicle/groth16/bn254/icicle.go @@ -1,3 +1,8 @@ +// Copyright 2025 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by gnark DO NOT EDIT + //go:build icicle package bn254 diff --git a/backend/accelerated/icicle/groth16/bn254/provingkey.go b/backend/accelerated/icicle/groth16/bn254/provingkey.go index c1894e23ed..907bafa60f 100644 --- a/backend/accelerated/icicle/groth16/bn254/provingkey.go +++ b/backend/accelerated/icicle/groth16/bn254/provingkey.go @@ -1,3 +1,8 @@ +// Copyright 2025 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by gnark DO NOT EDIT + //go:build icicle package bn254 diff --git a/backend/accelerated/icicle/groth16/bw6-761/doc.go b/backend/accelerated/icicle/groth16/bw6-761/doc.go index 511803213d..c36ee4af57 100644 --- a/backend/accelerated/icicle/groth16/bw6-761/doc.go +++ b/backend/accelerated/icicle/groth16/bw6-761/doc.go @@ -1,2 +1,7 @@ -// Package icicle_bw6761 implements ICICLE acceleration for BW6-761 Groth16 backend. +// Copyright 2025 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by gnark DO NOT EDIT + +// Package bw6761 implements ICICLE acceleration for BW6-761 Groth16 backend. package bw6761 diff --git a/backend/accelerated/icicle/groth16/bw6-761/icicle.go b/backend/accelerated/icicle/groth16/bw6-761/icicle.go index 48b26ed0f4..837291044c 100644 --- a/backend/accelerated/icicle/groth16/bw6-761/icicle.go +++ b/backend/accelerated/icicle/groth16/bw6-761/icicle.go @@ -1,3 +1,8 @@ +// Copyright 2025 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by gnark DO NOT EDIT + //go:build icicle package bw6761 diff --git a/backend/accelerated/icicle/groth16/bw6-761/provingkey.go b/backend/accelerated/icicle/groth16/bw6-761/provingkey.go index c5c9f08c2d..05d9a24188 100644 --- a/backend/accelerated/icicle/groth16/bw6-761/provingkey.go +++ b/backend/accelerated/icicle/groth16/bw6-761/provingkey.go @@ -1,3 +1,8 @@ +// Copyright 2025 Consensys Software Inc. +// Licensed under the Apache License, Version 2.0. See the LICENSE file for details. + +// Code generated by gnark DO NOT EDIT + //go:build icicle package bw6761 From 5e3da8eb819ee116c414955f7ae85dae2668ecbb Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 28 Oct 2025 09:05:56 +0000 Subject: [PATCH 22/24] docs: add CUDA reference --- backend/accelerated/icicle/doc.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/accelerated/icicle/doc.go b/backend/accelerated/icicle/doc.go index 6ea73505f9..079e67bf40 100644 --- a/backend/accelerated/icicle/doc.go +++ b/backend/accelerated/icicle/doc.go @@ -9,6 +9,11 @@ // // # Setup // +// Before using the GPU-acceleration for ICICLE backend, you must install the +// CUDA toolkit and have a compatible NVIDIA GPU. See [CUDA instructions] for +// more details. We have tested with CUDA 13 on Linux (Ubuntu 24.04), but other +// versions should work as well. +// // To initialize the ICICLE backend, follow the instructions in the [ICICLE] // repository. Namely, first you should install the ICICLE library: // @@ -62,4 +67,5 @@ // align with the external dependency changes. // // [ICICLE]: https://github.com/ingonyama-zk/icicle-gnark +// [CUDA instructions]: https://developer.nvidia.com/cuda-downloads?target_os=Linux package icicle From 56438484d5a445789d1806b50aebf12867490df8 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 28 Oct 2025 09:06:04 +0000 Subject: [PATCH 23/24] docs: fix git clone path --- backend/accelerated/icicle/doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/accelerated/icicle/doc.go b/backend/accelerated/icicle/doc.go index 079e67bf40..05a2168bfd 100644 --- a/backend/accelerated/icicle/doc.go +++ b/backend/accelerated/icicle/doc.go @@ -17,7 +17,7 @@ // To initialize the ICICLE backend, follow the instructions in the [ICICLE] // repository. Namely, first you should install the ICICLE library: // -// git clone github.com/ingonyama-zk/icicle-gnark +// git clone https://github.com/ingonyama-zk/icicle-gnark // cd icicle-gnark/wrappers/golang // sudo ./build.sh -curve=all // From c3319d80c075a186f0add22d8f92c0801221e1bb Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Tue, 28 Oct 2025 10:01:34 +0000 Subject: [PATCH 24/24] docs: add reference to clang --- backend/accelerated/icicle/doc.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/accelerated/icicle/doc.go b/backend/accelerated/icicle/doc.go index 05a2168bfd..bed889175b 100644 --- a/backend/accelerated/icicle/doc.go +++ b/backend/accelerated/icicle/doc.go @@ -14,6 +14,9 @@ // more details. We have tested with CUDA 13 on Linux (Ubuntu 24.04), but other // versions should work as well. // +// Additionally, for building ICICLE backend, you need to have a working clang +// toolchain. +// // To initialize the ICICLE backend, follow the instructions in the [ICICLE] // repository. Namely, first you should install the ICICLE library: //