func dummyInfinityCount(r1cs *cs.R1CS) (nbZeroesA, nbZeroesB int) {
	nbWires := r1cs.NbInternalVariables + r1cs.NbPublicVariables + r1cs.NbSecretVariables
	A := make([]bool, nbWires)
	B := make([]bool, nbWires)
	for _, c := range r1cs.Constraints {
		for _, t := range c.L {
			A[t.VariableID()] = true
		}
		for _, t := range c.R {
			B[t.VariableID()] = true
		}
	}
	for i := 0; i < nbWires; i++ {
		if !A[i] {
			nbZeroesA++
		}
		if !B[i] {
			nbZeroesB++
		}
	}
	return
}
dummyInfinityCount helps us simulate the number of infinity points we have with the given R1CS
in A and B as it directly impacts prover performance
package groth16
import (
	"github.com/consensys/gnark-crypto/ecc/bn254/fr"
	curve "github.com/consensys/gnark-crypto/ecc/bn254"
	"github.com/consensys/gnark/internal/backend/bn254/cs"
	"github.com/consensys/gnark-crypto/ecc"
	"github.com/consensys/gnark-crypto/ecc/bn254/fr/fft"
	"github.com/consensys/gnark/internal/backend/compiled"
	"math/big"
	"math/bits"
)
type ProvingKey struct {
	Domain fft.Domain
	G1 struct {
		Alpha, Beta, Delta curve.G1Affine
		A, B, Z            []curve.G1Affine
		K                  []curve.G1Affine  
	}
	G2 struct {
		Beta, Delta curve.G2Affine
		B           []curve.G2Affine
	}
	InfinityA, InfinityB     []bool
	NbInfinityA, NbInfinityB uint64
}
type VerifyingKey struct {
	G1 struct {
		Alpha       curve.G1Affine
		Beta, Delta curve.G1Affine    
		K           []curve.G1Affine  
	}
	G2 struct {
		Beta, Delta, Gamma curve.G2Affine
		deltaNeg, gammaNeg curve.G2Affine  
	}
	e curve.GT  
}
func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error {
	nbWires := r1cs.NbInternalVariables + r1cs.NbPublicVariables + r1cs.NbSecretVariables
	nbPublicWires := int(r1cs.NbPublicVariables)
	nbPrivateWires := r1cs.NbSecretVariables + r1cs.NbInternalVariables
	domain := fft.NewDomain(uint64(len(r1cs.Constraints)), 1, true)
	toxicWaste, err := sampleToxicWaste()
	if err != nil {
		return err
	}
	A, B, C := setupABC(r1cs, domain, toxicWaste)
	_, _, g1, g2 := curve.Generators()
	pkK := make([]fr.Element, nbPrivateWires)
	vkK := make([]fr.Element, nbPublicWires)
	var t0, t1 fr.Element
	for i := 0; i < nbPublicWires; i++ {
		t1.Mul(&A[i], &toxicWaste.beta)
		t0.Mul(&B[i], &toxicWaste.alpha)
		t1.Add(&t1, &t0).
			Add(&t1, &C[i]).
			Mul(&t1, &toxicWaste.gammaInv)
		vkK[i] = t1.ToRegular()
	}
	for i := 0; i < nbPrivateWires; i++ {
		t1.Mul(&A[i+nbPublicWires], &toxicWaste.beta)
		t0.Mul(&B[i+nbPublicWires], &toxicWaste.alpha)
		t1.Add(&t1, &t0).
			Add(&t1, &C[i+nbPublicWires]).
			Mul(&t1, &toxicWaste.deltaInv)
		pkK[i] = t1.ToRegular()
	}
	for i := 0; i < int(nbWires); i++ {
		A[i].FromMont()
	}
	for i := 0; i < int(nbWires); i++ {
		B[i].FromMont()
	}
	Z := make([]fr.Element, domain.Cardinality)
	one := fr.One()
	var zdt fr.Element
	zdt.Exp(toxicWaste.t, new(big.Int).SetUint64(domain.Cardinality)).
		Sub(&zdt, &one).
		Mul(&zdt, &toxicWaste.deltaInv)  
	for i := 0; i < int(domain.Cardinality); i++ {
		Z[i] = zdt.ToRegular()
		zdt.Mul(&zdt, &toxicWaste.t)
	}
	pk.InfinityA = make([]bool, len(A))
	pk.InfinityB = make([]bool, len(B))
	n := 0
	for i, e := range A {
		if e.IsZero() {
			pk.InfinityA[i] = true
			continue
		}
		A[n] = A[i]
		n++
	}
	A = A[:n]
	pk.NbInfinityA = uint64(nbWires - n)
	n = 0
	for i, e := range B {
		if e.IsZero() {
			pk.InfinityB[i] = true
			continue
		}
		B[n] = B[i]
		n++
	}
	B = B[:n]
	pk.NbInfinityB = uint64(nbWires - n)
	g1Scalars := make([]fr.Element, 0, (nbWires*3)+int(domain.Cardinality)+3)
	g1Scalars = append(g1Scalars, toxicWaste.alphaReg, toxicWaste.betaReg, toxicWaste.deltaReg)
	g1Scalars = append(g1Scalars, A...)
	g1Scalars = append(g1Scalars, B...)
	g1Scalars = append(g1Scalars, pkK...)
	g1Scalars = append(g1Scalars, Z...)
	g1Scalars = append(g1Scalars, vkK...)
	g1PointsAff := curve.BatchScalarMultiplicationG1(&g1, g1Scalars)
	pk.G1.Alpha = g1PointsAff[0]
	pk.G1.Beta = g1PointsAff[1]
	pk.G1.Delta = g1PointsAff[2]
	offset := 3
	pk.G1.A = g1PointsAff[offset : offset+len(A)]
	offset += len(A)
	pk.G1.B = g1PointsAff[offset : offset+len(B)]
	offset += len(B)
	pk.G1.K = g1PointsAff[offset : offset+nbPrivateWires]
	offset += nbPrivateWires
	pk.G1.Z = g1PointsAff[offset : offset+int(domain.Cardinality)]
	bitReverse(pk.G1.Z)
	offset += int(domain.Cardinality)
	vk.G1.K = g1PointsAff[offset:]
	g2Scalars := append(B, toxicWaste.betaReg, toxicWaste.deltaReg, toxicWaste.gammaReg)
	g2PointsAff := curve.BatchScalarMultiplicationG2(&g2, g2Scalars)
	pk.G2.B = g2PointsAff[:len(B)]
	pk.G2.Beta = g2PointsAff[len(B)+0]
	pk.G2.Delta = g2PointsAff[len(B)+1]
	vk.G2.Delta = g2PointsAff[len(B)+1]
	vk.G2.Gamma = g2PointsAff[len(B)+2]
	vk.G2.deltaNeg.Neg(&vk.G2.Delta)
	vk.G2.gammaNeg.Neg(&vk.G2.Gamma)
	vk.G1.Alpha = pk.G1.Alpha
	vk.G2.Beta = pk.G2.Beta
	vk.G1.Beta = pk.G1.Beta
	vk.G1.Delta = pk.G1.Delta
	vk.e, err = curve.Pair([]curve.G1Affine{pk.G1.Alpha}, []curve.G2Affine{pk.G2.Beta})
	if err != nil {
		return err
	}
	pk.Domain = *domain
	return nil
}
func setupABC(r1cs *cs.R1CS, domain *fft.Domain, toxicWaste toxicWaste) (A []fr.Element, B []fr.Element, C []fr.Element) {
	nbWires := r1cs.NbInternalVariables + r1cs.NbPublicVariables + r1cs.NbSecretVariables
	A = make([]fr.Element, nbWires)
	B = make([]fr.Element, nbWires)
	C = make([]fr.Element, nbWires)
	one := fr.One()
	var w fr.Element
	w.Set(&domain.Generator)
	wi := fr.One()
	t := make([]fr.Element, len(r1cs.Constraints)+1)
	for i := 0; i < len(t); i++ {
		t[i].Sub(&toxicWaste.t, &wi)
		wi.Mul(&wi, &w)  
	}
	tInv := fr.BatchInvert(t)
	var L fr.Element
	L.Exp(toxicWaste.t, new(big.Int).SetUint64(uint64(domain.Cardinality))).
		Sub(&L, &one)
	L.Mul(&L, &tInv[0]).
		Mul(&L, &domain.CardinalityInv)
	accumulate := func(res *fr.Element, t compiled.Term, value *fr.Element) {
		cID := t.CoeffID()
		switch cID {
		case compiled.CoeffIdZero:
			return
		case compiled.CoeffIdOne:
			res.Add(res, value)
		case compiled.CoeffIdMinusOne:
			res.Sub(res, value)
		case compiled.CoeffIdTwo:
			var buffer fr.Element
			buffer.Double(value)
			res.Add(res, &buffer)
		default:
			var buffer fr.Element
			buffer.Mul(&r1cs.Coefficients[cID], value)
			res.Add(res, &buffer)
		}
	}
	for i, c := range r1cs.Constraints {
		for _, t := range c.L {
			accumulate(&A[t.VariableID()], t, &L)
		}
		for _, t := range c.R {
			accumulate(&B[t.VariableID()], t, &L)
		}
		for _, t := range c.O {
			accumulate(&C[t.VariableID()], t, &L)
		}
		L.Mul(&L, &w)
		L.Mul(&L, &t[i])
		L.Mul(&L, &tInv[i+1])
	}
	return
}
type toxicWaste struct {
	t, alpha, beta, gamma, delta fr.Element
	gammaInv, deltaInv           fr.Element
	alphaReg, betaReg, gammaReg, deltaReg fr.Element
}
func sampleToxicWaste() (toxicWaste, error) {
	res := toxicWaste{}
	for res.t.IsZero() {
		if _, err := res.t.SetRandom(); err != nil {
			return res, err
		}
	}
	for res.alpha.IsZero() {
		if _, err := res.alpha.SetRandom(); err != nil {
			return res, err
		}
	}
	for res.beta.IsZero() {
		if _, err := res.beta.SetRandom(); err != nil {
			return res, err
		}
	}
	for res.gamma.IsZero() {
		if _, err := res.gamma.SetRandom(); err != nil {
			return res, err
		}
	}
	for res.delta.IsZero() {
		if _, err := res.delta.SetRandom(); err != nil {
			return res, err
		}
	}
	res.gammaInv.Inverse(&res.gamma)
	res.deltaInv.Inverse(&res.delta)
	res.alphaReg = res.alpha.ToRegular()
	res.betaReg = res.beta.ToRegular()
	res.gammaReg = res.gamma.ToRegular()
	res.deltaReg = res.delta.ToRegular()
	return res, nil
}
func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error {
	nbWires := r1cs.NbInternalVariables + r1cs.NbPublicVariables + r1cs.NbSecretVariables
	nbConstraints := len(r1cs.Constraints)
	domain := fft.NewDomain(uint64(nbConstraints), 1, true)
	nbZeroesA, nbZeroesB := dummyInfinityCount(r1cs)
	pk.G1.A = make([]curve.G1Affine, nbWires-nbZeroesA)
	pk.G1.B = make([]curve.G1Affine, nbWires-nbZeroesB)
	pk.G1.K = make([]curve.G1Affine, nbWires-r1cs.NbPublicVariables)
	pk.G1.Z = make([]curve.G1Affine, domain.Cardinality)
	pk.G2.B = make([]curve.G2Affine, nbWires-nbZeroesB)
	pk.InfinityA = make([]bool, nbWires)
	pk.InfinityB = make([]bool, nbWires)
	pk.NbInfinityA = uint64(nbZeroesA)
	pk.NbInfinityB = uint64(nbZeroesB)
	for i := 0; i < nbZeroesA; i++ {
		pk.InfinityA[i] = true
	}
	for i := 0; i < nbZeroesB; i++ {
		pk.InfinityB[i] = true
	}
	toxicWaste, err := sampleToxicWaste()
	if err != nil {
		return err
	}
	var r1Jac curve.G1Jac
	var r1Aff curve.G1Affine
	var b big.Int
	g1, g2, _, _ := curve.Generators()
	r1Jac.ScalarMultiplication(&g1, toxicWaste.alphaReg.ToBigInt(&b))
	r1Aff.FromJacobian(&r1Jac)
	var r2Jac curve.G2Jac
	var r2Aff curve.G2Affine
	r2Jac.ScalarMultiplication(&g2, &b)
	r2Aff.FromJacobian(&r2Jac)
	for i := 0; i < len(pk.G1.A); i++ {
		pk.G1.A[i] = r1Aff
	}
	for i := 0; i < len(pk.G1.B); i++ {
		pk.G1.B[i] = r1Aff
	}
	for i := 0; i < len(pk.G2.B); i++ {
		pk.G2.B[i] = r2Aff
	}
	for i := 0; i < len(pk.G1.Z); i++ {
		pk.G1.Z[i] = r1Aff
	}
	for i := 0; i < len(pk.G1.K); i++ {
		pk.G1.K[i] = r1Aff
	}
	pk.G1.Alpha = r1Aff
	pk.G1.Beta = r1Aff
	pk.G1.Delta = r1Aff
	pk.G2.Beta = r2Aff
	pk.G2.Delta = r2Aff
	pk.Domain = *domain
	return nil
}