diff --git a/backend/groth16/groth16.go b/backend/groth16/groth16.go index 3c1676421..51b0ca818 100644 --- a/backend/groth16/groth16.go +++ b/backend/groth16/groth16.go @@ -181,8 +181,10 @@ func ReadAndVerify(proof Proof, vk VerifyingKey, publicWitness io.Reader) error // Prove runs the groth16.Prove algorithm. // -// If force flag is set, executes all the prover computations, even if the witness is invalid -// (in which case it will produce an invalid proof) +// if the force flag is set: +// will executes all the prover computations, even if the witness is invalid +// will produce an invalid proof +// internally, the solution vector to the R1CS will be filled with random values which may impact benchmarking func Prove(r1cs frontend.CompiledConstraintSystem, pk ProvingKey, witness frontend.Circuit, force ...bool) (Proof, error) { _force := false diff --git a/frontend/cs.go b/frontend/cs.go index b851f0039..d6ce5392b 100644 --- a/frontend/cs.go +++ b/frontend/cs.go @@ -152,6 +152,17 @@ func newR1C(l, r, o Variable, s ...compiled.SolvingMethod) compiled.R1C { if len(s) > 0 { solver = s[0] } + + // interestingly, this is key to groth16 performance. + // l * r == r * l == o + // but the "l" linear expression is going to end up in the A matrix + // the "r" linear expression is going to end up in the B matrix + // the less variable we have appearing in the B matrix, the more likely groth16.Setup + // is going to produce infinity points in pk.G1.B and pk.G2.B, which will speed up proving time + if solver == compiled.SingleOutput && len(l.linExp) > len(r.linExp) { + l, r = r, l + } + return compiled.R1C{L: l.linExp.Clone(), R: r.linExp.Clone(), O: o.linExp.Clone(), Solver: solver} } diff --git a/frontend/cs_api.go b/frontend/cs_api.go index 214207301..1447f6a87 100644 --- a/frontend/cs_api.go +++ b/frontend/cs_api.go @@ -244,7 +244,7 @@ func (cs *ConstraintSystem) Div(i1, i2 interface{}) Variable { cs.constraints = append(cs.constraints, newR1C(t2, res, t1)) default: tmp := cs.Constant(t2) - cs.constraints = append(cs.constraints, newR1C(tmp, res, t1)) + cs.constraints = append(cs.constraints, newR1C(res, tmp, t1)) } default: switch t2 := i2.(type) { @@ -255,7 +255,7 @@ func (cs *ConstraintSystem) Div(i1, i2 interface{}) Variable { default: tmp1 := cs.Constant(t1) tmp2 := cs.Constant(t2) - cs.constraints = append(cs.constraints, newR1C(tmp2, res, tmp1)) + cs.constraints = append(cs.constraints, newR1C(res, tmp2, tmp1)) } } @@ -438,7 +438,7 @@ func (cs *ConstraintSystem) Select(b Variable, i1, i2 interface{}) Variable { v := cs.Sub(t1, i2) // no constraint is recorded w := cs.Sub(res, i2) // no constraint is recorded //cs.Println("u-v: ", v) - cs.constraints = append(cs.constraints, newR1C(b, v, w)) + cs.constraints = append(cs.constraints, newR1C(v, b, w)) return res default: switch t2 := i2.(type) { @@ -447,7 +447,7 @@ func (cs *ConstraintSystem) Select(b Variable, i1, i2 interface{}) Variable { res = cs.newInternalVariable() v := cs.Sub(t1, t2) // no constraint is recorded w := cs.Sub(res, t2) // no constraint is recorded - cs.constraints = append(cs.constraints, newR1C(b, v, w)) + cs.constraints = append(cs.constraints, newR1C(v, b, w)) return res default: // in this case, no constraint is recorded @@ -567,7 +567,7 @@ func (cs *ConstraintSystem) markBoolean(v Variable) bool { return true } -// AssertIsBoolean adds an assertion in the constraint system (v == 0 || v == 1) +// AssertIsBoolean adds an assertion in the constraint system (v == 0 || v == 1) func (cs *ConstraintSystem) AssertIsBoolean(v Variable) { v.assertIsSet() @@ -576,6 +576,8 @@ func (cs *ConstraintSystem) AssertIsBoolean(v Variable) { return // variable is already constrained } + // ensure v * (1 - v) == 0 + _v := cs.Sub(1, v) // no variable is recorded in the cs o := cs.Constant(0) // no variable is recorded in the cs diff --git a/internal/backend/bls12-377/groth16/prove.go b/internal/backend/bls12-377/groth16/prove.go index 99d30c3be..235040bb1 100644 --- a/internal/backend/bls12-377/groth16/prove.go +++ b/internal/backend/bls12-377/groth16/prove.go @@ -64,8 +64,18 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls12_377witness.Witness, forc b := make([]fr.Element, r1cs.NbConstraints, pk.Domain.Cardinality) c := make([]fr.Element, r1cs.NbConstraints, pk.Domain.Cardinality) wireValues := make([]fr.Element, r1cs.NbInternalVariables+r1cs.NbPublicVariables+r1cs.NbSecretVariables) - if err := r1cs.Solve(witness, a, b, c, wireValues); err != nil && !force { - return nil, err + if err := r1cs.Solve(witness, a, b, c, wireValues); err != nil { + if !force { + return nil, err + } else { + // we need to fill wireValues with random values else multi exps don't do much + var r fr.Element + _, _ = r.SetRandom() + for i := r1cs.NbPublicVariables + r1cs.NbSecretVariables; i < len(wireValues); i++ { + wireValues[i] = r + r.Double(&r) + } + } } // set the wire values in regular form @@ -86,6 +96,34 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls12_377witness.Witness, forc chHDone <- struct{}{} }() + // we need to copy and filter the wireValues for each multi exp + // as pk.G1.A, pk.G1.B and pk.G2.B may have (a significant) number of point at infinity + var wireValuesA, wireValuesB []fr.Element + chWireValuesA, chWireValuesB := make(chan struct{}, 1), make(chan struct{}, 1) + + go func() { + wireValuesA = make([]fr.Element, len(wireValues)-pk.NbInfinityA) + for i, j := 0, 0; j < len(wireValuesA); i++ { + if pk.InfinityA[i] { + continue + } + wireValuesA[j] = wireValues[i] + j++ + } + close(chWireValuesA) + }() + go func() { + wireValuesB = make([]fr.Element, len(wireValues)-pk.NbInfinityB) + for i, j := 0, 0; j < len(wireValuesB); i++ { + if pk.InfinityB[i] { + continue + } + wireValuesB[j] = wireValues[i] + j++ + } + close(chWireValuesB) + }() + // sample random r and s var r, s big.Int var _r, _s, _kr fr.Element @@ -113,7 +151,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls12_377witness.Witness, forc chBs1Done := make(chan error, 1) computeBS1 := func() { - if _, err := bs1.MultiExp(pk.G1.B, wireValues, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { + <-chWireValuesB + if _, err := bs1.MultiExp(pk.G1.B, wireValuesB, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { chBs1Done <- err close(chBs1Done) return @@ -125,7 +164,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls12_377witness.Witness, forc chArDone := make(chan error, 1) computeAR1 := func() { - if _, err := ar.MultiExp(pk.G1.A, wireValues, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { + <-chWireValuesA + if _, err := ar.MultiExp(pk.G1.A, wireValuesA, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { chArDone <- err close(chArDone) return @@ -192,7 +232,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls12_377witness.Witness, forc // if we don't have a lot of CPUs, this may artificially split the MSM nbTasks *= 2 } - if _, err := Bs.MultiExp(pk.G2.B, wireValues, ecc.MultiExpConfig{NbTasks: nbTasks}); err != nil { + <-chWireValuesB + if _, err := Bs.MultiExp(pk.G2.B, wireValuesB, ecc.MultiExpConfig{NbTasks: nbTasks}); err != nil { return err } diff --git a/internal/backend/bls12-377/groth16/setup.go b/internal/backend/bls12-377/groth16/setup.go index 6700f7016..62035463f 100644 --- a/internal/backend/bls12-377/groth16/setup.go +++ b/internal/backend/bls12-377/groth16/setup.go @@ -48,6 +48,10 @@ type ProvingKey struct { Beta, Delta curve.G2Affine B []curve.G2Affine } + + // if InfinityA[i] == true, the point G1.A[i] == infinity + InfinityA, InfinityB []bool + NbInfinityA, NbInfinityB int } // VerifyingKey is used by a Groth16 verifier to verify the validity of a proof and a statement @@ -167,6 +171,33 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { zdt.Mul(&zdt, &toxicWaste.t) } + // mark points at infinity and filter them + 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 = 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 = nbWires - n + // compute our batch scalar multiplication with g1 elements g1Scalars := make([]fr.Element, 0, (nbWires*3)+int(domain.Cardinality)+3) g1Scalars = append(g1Scalars, toxicWaste.alphaReg, toxicWaste.betaReg, toxicWaste.deltaReg) @@ -184,11 +215,11 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { pk.G1.Delta = g1PointsAff[2] offset := 3 - pk.G1.A = g1PointsAff[offset : offset+nbWires] - offset += nbWires + pk.G1.A = g1PointsAff[offset : offset+len(A)] + offset += len(A) - pk.G1.B = g1PointsAff[offset : offset+nbWires] - offset += nbWires + pk.G1.B = g1PointsAff[offset : offset+len(B)] + offset += len(B) pk.G1.K = g1PointsAff[offset : offset+nbPrivateWires] offset += nbPrivateWires @@ -213,15 +244,15 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { g2PointsAff := curve.BatchScalarMultiplicationG2(&g2, g2Scalars) - pk.G2.B = g2PointsAff[:nbWires] + pk.G2.B = g2PointsAff[:len(B)] // sets pk: [β]2, [δ]2 - pk.G2.Beta = g2PointsAff[nbWires+0] - pk.G2.Delta = g2PointsAff[nbWires+1] + pk.G2.Beta = g2PointsAff[len(B)+0] + pk.G2.Delta = g2PointsAff[len(B)+1] // sets vk: [δ]2, [γ]2, -[δ]2, -[γ]2 - vk.G2.Delta = g2PointsAff[nbWires+1] - vk.G2.Gamma = g2PointsAff[nbWires+2] + 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) @@ -270,13 +301,18 @@ func setupABC(r1cs *cs.R1CS, domain *fft.Domain, toxicWaste toxicWaste) (A []fr. // L = 1/n*(t^n-1)/(t-1), Li+1 = w*Li*(t-w^i)/(t-w^(i+1)) - // Setting L + // Setting L0 L.Exp(toxicWaste.t, new(big.Int).SetUint64(uint64(domain.Cardinality))). Sub(&L, &one) L.Mul(&L, &tInv[0]). Mul(&L, &domain.CardinalityInv) - // Constraints + // each constraint is in the form + // L * R == O + // L, R and O being linear expressions + // for each term appearing in the linear expression, + // we compute term.Coefficient * L, and cumulate it in + // A, B or C at the indice of the variable for i, c := range r1cs.Constraints { for _, t := range c.L { @@ -360,12 +396,28 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { // Setting group for fft domain := fft.NewDomain(uint64(nbConstraints), 1, true) + // count number of infinity points we would have had we a normal setup + // in pk.G1.A, pk.G1.B, and pk.G2.B + nbZeroesA, nbZeroesB := dummyInfinityCount(r1cs) + // initialize proving key - pk.G1.A = make([]curve.G1Affine, nbWires) - pk.G1.B = make([]curve.G1Affine, nbWires) + 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) + pk.G2.B = make([]curve.G2Affine, nbWires-nbZeroesB) + + // set infinity markers + pk.InfinityA = make([]bool, nbWires) + pk.InfinityB = make([]bool, nbWires) + pk.NbInfinityA = nbZeroesA + pk.NbInfinityB = nbZeroesB + for i := 0; i < nbZeroesA; i++ { + pk.InfinityA[i] = true + } + for i := 0; i < nbZeroesB; i++ { + pk.InfinityB[i] = true + } // samples toxic waste toxicWaste, err := sampleToxicWaste() @@ -383,9 +435,13 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { var r2Aff curve.G2Affine r2Jac.ScalarMultiplication(&g2, &b) r2Aff.FromJacobian(&r2Jac) - for i := 0; i < int(nbWires); i++ { + 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++ { @@ -405,6 +461,34 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { return nil } +// 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 +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 + +} + // IsDifferent returns true if provided vk is different than self // this is used by groth16.Assert to ensure random sampling func (vk *VerifyingKey) IsDifferent(_other interface{}) bool { diff --git a/internal/backend/bls12-381/groth16/prove.go b/internal/backend/bls12-381/groth16/prove.go index 89258c725..f3278c32e 100644 --- a/internal/backend/bls12-381/groth16/prove.go +++ b/internal/backend/bls12-381/groth16/prove.go @@ -64,8 +64,18 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls12_381witness.Witness, forc b := make([]fr.Element, r1cs.NbConstraints, pk.Domain.Cardinality) c := make([]fr.Element, r1cs.NbConstraints, pk.Domain.Cardinality) wireValues := make([]fr.Element, r1cs.NbInternalVariables+r1cs.NbPublicVariables+r1cs.NbSecretVariables) - if err := r1cs.Solve(witness, a, b, c, wireValues); err != nil && !force { - return nil, err + if err := r1cs.Solve(witness, a, b, c, wireValues); err != nil { + if !force { + return nil, err + } else { + // we need to fill wireValues with random values else multi exps don't do much + var r fr.Element + _, _ = r.SetRandom() + for i := r1cs.NbPublicVariables + r1cs.NbSecretVariables; i < len(wireValues); i++ { + wireValues[i] = r + r.Double(&r) + } + } } // set the wire values in regular form @@ -86,6 +96,34 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls12_381witness.Witness, forc chHDone <- struct{}{} }() + // we need to copy and filter the wireValues for each multi exp + // as pk.G1.A, pk.G1.B and pk.G2.B may have (a significant) number of point at infinity + var wireValuesA, wireValuesB []fr.Element + chWireValuesA, chWireValuesB := make(chan struct{}, 1), make(chan struct{}, 1) + + go func() { + wireValuesA = make([]fr.Element, len(wireValues)-pk.NbInfinityA) + for i, j := 0, 0; j < len(wireValuesA); i++ { + if pk.InfinityA[i] { + continue + } + wireValuesA[j] = wireValues[i] + j++ + } + close(chWireValuesA) + }() + go func() { + wireValuesB = make([]fr.Element, len(wireValues)-pk.NbInfinityB) + for i, j := 0, 0; j < len(wireValuesB); i++ { + if pk.InfinityB[i] { + continue + } + wireValuesB[j] = wireValues[i] + j++ + } + close(chWireValuesB) + }() + // sample random r and s var r, s big.Int var _r, _s, _kr fr.Element @@ -113,7 +151,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls12_381witness.Witness, forc chBs1Done := make(chan error, 1) computeBS1 := func() { - if _, err := bs1.MultiExp(pk.G1.B, wireValues, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { + <-chWireValuesB + if _, err := bs1.MultiExp(pk.G1.B, wireValuesB, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { chBs1Done <- err close(chBs1Done) return @@ -125,7 +164,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls12_381witness.Witness, forc chArDone := make(chan error, 1) computeAR1 := func() { - if _, err := ar.MultiExp(pk.G1.A, wireValues, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { + <-chWireValuesA + if _, err := ar.MultiExp(pk.G1.A, wireValuesA, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { chArDone <- err close(chArDone) return @@ -192,7 +232,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls12_381witness.Witness, forc // if we don't have a lot of CPUs, this may artificially split the MSM nbTasks *= 2 } - if _, err := Bs.MultiExp(pk.G2.B, wireValues, ecc.MultiExpConfig{NbTasks: nbTasks}); err != nil { + <-chWireValuesB + if _, err := Bs.MultiExp(pk.G2.B, wireValuesB, ecc.MultiExpConfig{NbTasks: nbTasks}); err != nil { return err } diff --git a/internal/backend/bls12-381/groth16/setup.go b/internal/backend/bls12-381/groth16/setup.go index 5ebd323d2..f8111541c 100644 --- a/internal/backend/bls12-381/groth16/setup.go +++ b/internal/backend/bls12-381/groth16/setup.go @@ -48,6 +48,10 @@ type ProvingKey struct { Beta, Delta curve.G2Affine B []curve.G2Affine } + + // if InfinityA[i] == true, the point G1.A[i] == infinity + InfinityA, InfinityB []bool + NbInfinityA, NbInfinityB int } // VerifyingKey is used by a Groth16 verifier to verify the validity of a proof and a statement @@ -167,6 +171,33 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { zdt.Mul(&zdt, &toxicWaste.t) } + // mark points at infinity and filter them + 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 = 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 = nbWires - n + // compute our batch scalar multiplication with g1 elements g1Scalars := make([]fr.Element, 0, (nbWires*3)+int(domain.Cardinality)+3) g1Scalars = append(g1Scalars, toxicWaste.alphaReg, toxicWaste.betaReg, toxicWaste.deltaReg) @@ -184,11 +215,11 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { pk.G1.Delta = g1PointsAff[2] offset := 3 - pk.G1.A = g1PointsAff[offset : offset+nbWires] - offset += nbWires + pk.G1.A = g1PointsAff[offset : offset+len(A)] + offset += len(A) - pk.G1.B = g1PointsAff[offset : offset+nbWires] - offset += nbWires + pk.G1.B = g1PointsAff[offset : offset+len(B)] + offset += len(B) pk.G1.K = g1PointsAff[offset : offset+nbPrivateWires] offset += nbPrivateWires @@ -213,15 +244,15 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { g2PointsAff := curve.BatchScalarMultiplicationG2(&g2, g2Scalars) - pk.G2.B = g2PointsAff[:nbWires] + pk.G2.B = g2PointsAff[:len(B)] // sets pk: [β]2, [δ]2 - pk.G2.Beta = g2PointsAff[nbWires+0] - pk.G2.Delta = g2PointsAff[nbWires+1] + pk.G2.Beta = g2PointsAff[len(B)+0] + pk.G2.Delta = g2PointsAff[len(B)+1] // sets vk: [δ]2, [γ]2, -[δ]2, -[γ]2 - vk.G2.Delta = g2PointsAff[nbWires+1] - vk.G2.Gamma = g2PointsAff[nbWires+2] + 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) @@ -270,13 +301,18 @@ func setupABC(r1cs *cs.R1CS, domain *fft.Domain, toxicWaste toxicWaste) (A []fr. // L = 1/n*(t^n-1)/(t-1), Li+1 = w*Li*(t-w^i)/(t-w^(i+1)) - // Setting L + // Setting L0 L.Exp(toxicWaste.t, new(big.Int).SetUint64(uint64(domain.Cardinality))). Sub(&L, &one) L.Mul(&L, &tInv[0]). Mul(&L, &domain.CardinalityInv) - // Constraints + // each constraint is in the form + // L * R == O + // L, R and O being linear expressions + // for each term appearing in the linear expression, + // we compute term.Coefficient * L, and cumulate it in + // A, B or C at the indice of the variable for i, c := range r1cs.Constraints { for _, t := range c.L { @@ -360,12 +396,28 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { // Setting group for fft domain := fft.NewDomain(uint64(nbConstraints), 1, true) + // count number of infinity points we would have had we a normal setup + // in pk.G1.A, pk.G1.B, and pk.G2.B + nbZeroesA, nbZeroesB := dummyInfinityCount(r1cs) + // initialize proving key - pk.G1.A = make([]curve.G1Affine, nbWires) - pk.G1.B = make([]curve.G1Affine, nbWires) + 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) + pk.G2.B = make([]curve.G2Affine, nbWires-nbZeroesB) + + // set infinity markers + pk.InfinityA = make([]bool, nbWires) + pk.InfinityB = make([]bool, nbWires) + pk.NbInfinityA = nbZeroesA + pk.NbInfinityB = nbZeroesB + for i := 0; i < nbZeroesA; i++ { + pk.InfinityA[i] = true + } + for i := 0; i < nbZeroesB; i++ { + pk.InfinityB[i] = true + } // samples toxic waste toxicWaste, err := sampleToxicWaste() @@ -383,9 +435,13 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { var r2Aff curve.G2Affine r2Jac.ScalarMultiplication(&g2, &b) r2Aff.FromJacobian(&r2Jac) - for i := 0; i < int(nbWires); i++ { + 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++ { @@ -405,6 +461,34 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { return nil } +// 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 +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 + +} + // IsDifferent returns true if provided vk is different than self // this is used by groth16.Assert to ensure random sampling func (vk *VerifyingKey) IsDifferent(_other interface{}) bool { diff --git a/internal/backend/bls24-315/groth16/prove.go b/internal/backend/bls24-315/groth16/prove.go index 85ebf77d1..cb186c343 100644 --- a/internal/backend/bls24-315/groth16/prove.go +++ b/internal/backend/bls24-315/groth16/prove.go @@ -64,8 +64,18 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls24_315witness.Witness, forc b := make([]fr.Element, r1cs.NbConstraints, pk.Domain.Cardinality) c := make([]fr.Element, r1cs.NbConstraints, pk.Domain.Cardinality) wireValues := make([]fr.Element, r1cs.NbInternalVariables+r1cs.NbPublicVariables+r1cs.NbSecretVariables) - if err := r1cs.Solve(witness, a, b, c, wireValues); err != nil && !force { - return nil, err + if err := r1cs.Solve(witness, a, b, c, wireValues); err != nil { + if !force { + return nil, err + } else { + // we need to fill wireValues with random values else multi exps don't do much + var r fr.Element + _, _ = r.SetRandom() + for i := r1cs.NbPublicVariables + r1cs.NbSecretVariables; i < len(wireValues); i++ { + wireValues[i] = r + r.Double(&r) + } + } } // set the wire values in regular form @@ -86,6 +96,34 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls24_315witness.Witness, forc chHDone <- struct{}{} }() + // we need to copy and filter the wireValues for each multi exp + // as pk.G1.A, pk.G1.B and pk.G2.B may have (a significant) number of point at infinity + var wireValuesA, wireValuesB []fr.Element + chWireValuesA, chWireValuesB := make(chan struct{}, 1), make(chan struct{}, 1) + + go func() { + wireValuesA = make([]fr.Element, len(wireValues)-pk.NbInfinityA) + for i, j := 0, 0; j < len(wireValuesA); i++ { + if pk.InfinityA[i] { + continue + } + wireValuesA[j] = wireValues[i] + j++ + } + close(chWireValuesA) + }() + go func() { + wireValuesB = make([]fr.Element, len(wireValues)-pk.NbInfinityB) + for i, j := 0, 0; j < len(wireValuesB); i++ { + if pk.InfinityB[i] { + continue + } + wireValuesB[j] = wireValues[i] + j++ + } + close(chWireValuesB) + }() + // sample random r and s var r, s big.Int var _r, _s, _kr fr.Element @@ -113,7 +151,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls24_315witness.Witness, forc chBs1Done := make(chan error, 1) computeBS1 := func() { - if _, err := bs1.MultiExp(pk.G1.B, wireValues, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { + <-chWireValuesB + if _, err := bs1.MultiExp(pk.G1.B, wireValuesB, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { chBs1Done <- err close(chBs1Done) return @@ -125,7 +164,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls24_315witness.Witness, forc chArDone := make(chan error, 1) computeAR1 := func() { - if _, err := ar.MultiExp(pk.G1.A, wireValues, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { + <-chWireValuesA + if _, err := ar.MultiExp(pk.G1.A, wireValuesA, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { chArDone <- err close(chArDone) return @@ -192,7 +232,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bls24_315witness.Witness, forc // if we don't have a lot of CPUs, this may artificially split the MSM nbTasks *= 2 } - if _, err := Bs.MultiExp(pk.G2.B, wireValues, ecc.MultiExpConfig{NbTasks: nbTasks}); err != nil { + <-chWireValuesB + if _, err := Bs.MultiExp(pk.G2.B, wireValuesB, ecc.MultiExpConfig{NbTasks: nbTasks}); err != nil { return err } diff --git a/internal/backend/bls24-315/groth16/setup.go b/internal/backend/bls24-315/groth16/setup.go index 4274c7aba..9444b2007 100644 --- a/internal/backend/bls24-315/groth16/setup.go +++ b/internal/backend/bls24-315/groth16/setup.go @@ -48,6 +48,10 @@ type ProvingKey struct { Beta, Delta curve.G2Affine B []curve.G2Affine } + + // if InfinityA[i] == true, the point G1.A[i] == infinity + InfinityA, InfinityB []bool + NbInfinityA, NbInfinityB int } // VerifyingKey is used by a Groth16 verifier to verify the validity of a proof and a statement @@ -167,6 +171,33 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { zdt.Mul(&zdt, &toxicWaste.t) } + // mark points at infinity and filter them + 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 = 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 = nbWires - n + // compute our batch scalar multiplication with g1 elements g1Scalars := make([]fr.Element, 0, (nbWires*3)+int(domain.Cardinality)+3) g1Scalars = append(g1Scalars, toxicWaste.alphaReg, toxicWaste.betaReg, toxicWaste.deltaReg) @@ -184,11 +215,11 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { pk.G1.Delta = g1PointsAff[2] offset := 3 - pk.G1.A = g1PointsAff[offset : offset+nbWires] - offset += nbWires + pk.G1.A = g1PointsAff[offset : offset+len(A)] + offset += len(A) - pk.G1.B = g1PointsAff[offset : offset+nbWires] - offset += nbWires + pk.G1.B = g1PointsAff[offset : offset+len(B)] + offset += len(B) pk.G1.K = g1PointsAff[offset : offset+nbPrivateWires] offset += nbPrivateWires @@ -213,15 +244,15 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { g2PointsAff := curve.BatchScalarMultiplicationG2(&g2, g2Scalars) - pk.G2.B = g2PointsAff[:nbWires] + pk.G2.B = g2PointsAff[:len(B)] // sets pk: [β]2, [δ]2 - pk.G2.Beta = g2PointsAff[nbWires+0] - pk.G2.Delta = g2PointsAff[nbWires+1] + pk.G2.Beta = g2PointsAff[len(B)+0] + pk.G2.Delta = g2PointsAff[len(B)+1] // sets vk: [δ]2, [γ]2, -[δ]2, -[γ]2 - vk.G2.Delta = g2PointsAff[nbWires+1] - vk.G2.Gamma = g2PointsAff[nbWires+2] + 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) @@ -270,13 +301,18 @@ func setupABC(r1cs *cs.R1CS, domain *fft.Domain, toxicWaste toxicWaste) (A []fr. // L = 1/n*(t^n-1)/(t-1), Li+1 = w*Li*(t-w^i)/(t-w^(i+1)) - // Setting L + // Setting L0 L.Exp(toxicWaste.t, new(big.Int).SetUint64(uint64(domain.Cardinality))). Sub(&L, &one) L.Mul(&L, &tInv[0]). Mul(&L, &domain.CardinalityInv) - // Constraints + // each constraint is in the form + // L * R == O + // L, R and O being linear expressions + // for each term appearing in the linear expression, + // we compute term.Coefficient * L, and cumulate it in + // A, B or C at the indice of the variable for i, c := range r1cs.Constraints { for _, t := range c.L { @@ -360,12 +396,28 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { // Setting group for fft domain := fft.NewDomain(uint64(nbConstraints), 1, true) + // count number of infinity points we would have had we a normal setup + // in pk.G1.A, pk.G1.B, and pk.G2.B + nbZeroesA, nbZeroesB := dummyInfinityCount(r1cs) + // initialize proving key - pk.G1.A = make([]curve.G1Affine, nbWires) - pk.G1.B = make([]curve.G1Affine, nbWires) + 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) + pk.G2.B = make([]curve.G2Affine, nbWires-nbZeroesB) + + // set infinity markers + pk.InfinityA = make([]bool, nbWires) + pk.InfinityB = make([]bool, nbWires) + pk.NbInfinityA = nbZeroesA + pk.NbInfinityB = nbZeroesB + for i := 0; i < nbZeroesA; i++ { + pk.InfinityA[i] = true + } + for i := 0; i < nbZeroesB; i++ { + pk.InfinityB[i] = true + } // samples toxic waste toxicWaste, err := sampleToxicWaste() @@ -383,9 +435,13 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { var r2Aff curve.G2Affine r2Jac.ScalarMultiplication(&g2, &b) r2Aff.FromJacobian(&r2Jac) - for i := 0; i < int(nbWires); i++ { + 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++ { @@ -405,6 +461,34 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { return nil } +// 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 +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 + +} + // IsDifferent returns true if provided vk is different than self // this is used by groth16.Assert to ensure random sampling func (vk *VerifyingKey) IsDifferent(_other interface{}) bool { diff --git a/internal/backend/bn254/groth16/prove.go b/internal/backend/bn254/groth16/prove.go index 0ca13a7ac..bd6208372 100644 --- a/internal/backend/bn254/groth16/prove.go +++ b/internal/backend/bn254/groth16/prove.go @@ -64,8 +64,18 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bn254witness.Witness, force bo b := make([]fr.Element, r1cs.NbConstraints, pk.Domain.Cardinality) c := make([]fr.Element, r1cs.NbConstraints, pk.Domain.Cardinality) wireValues := make([]fr.Element, r1cs.NbInternalVariables+r1cs.NbPublicVariables+r1cs.NbSecretVariables) - if err := r1cs.Solve(witness, a, b, c, wireValues); err != nil && !force { - return nil, err + if err := r1cs.Solve(witness, a, b, c, wireValues); err != nil { + if !force { + return nil, err + } else { + // we need to fill wireValues with random values else multi exps don't do much + var r fr.Element + _, _ = r.SetRandom() + for i := r1cs.NbPublicVariables + r1cs.NbSecretVariables; i < len(wireValues); i++ { + wireValues[i] = r + r.Double(&r) + } + } } // set the wire values in regular form @@ -86,6 +96,34 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bn254witness.Witness, force bo chHDone <- struct{}{} }() + // we need to copy and filter the wireValues for each multi exp + // as pk.G1.A, pk.G1.B and pk.G2.B may have (a significant) number of point at infinity + var wireValuesA, wireValuesB []fr.Element + chWireValuesA, chWireValuesB := make(chan struct{}, 1), make(chan struct{}, 1) + + go func() { + wireValuesA = make([]fr.Element, len(wireValues)-pk.NbInfinityA) + for i, j := 0, 0; j < len(wireValuesA); i++ { + if pk.InfinityA[i] { + continue + } + wireValuesA[j] = wireValues[i] + j++ + } + close(chWireValuesA) + }() + go func() { + wireValuesB = make([]fr.Element, len(wireValues)-pk.NbInfinityB) + for i, j := 0, 0; j < len(wireValuesB); i++ { + if pk.InfinityB[i] { + continue + } + wireValuesB[j] = wireValues[i] + j++ + } + close(chWireValuesB) + }() + // sample random r and s var r, s big.Int var _r, _s, _kr fr.Element @@ -113,7 +151,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bn254witness.Witness, force bo chBs1Done := make(chan error, 1) computeBS1 := func() { - if _, err := bs1.MultiExp(pk.G1.B, wireValues, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { + <-chWireValuesB + if _, err := bs1.MultiExp(pk.G1.B, wireValuesB, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { chBs1Done <- err close(chBs1Done) return @@ -125,7 +164,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bn254witness.Witness, force bo chArDone := make(chan error, 1) computeAR1 := func() { - if _, err := ar.MultiExp(pk.G1.A, wireValues, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { + <-chWireValuesA + if _, err := ar.MultiExp(pk.G1.A, wireValuesA, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { chArDone <- err close(chArDone) return @@ -192,7 +232,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bn254witness.Witness, force bo // if we don't have a lot of CPUs, this may artificially split the MSM nbTasks *= 2 } - if _, err := Bs.MultiExp(pk.G2.B, wireValues, ecc.MultiExpConfig{NbTasks: nbTasks}); err != nil { + <-chWireValuesB + if _, err := Bs.MultiExp(pk.G2.B, wireValuesB, ecc.MultiExpConfig{NbTasks: nbTasks}); err != nil { return err } diff --git a/internal/backend/bn254/groth16/setup.go b/internal/backend/bn254/groth16/setup.go index c89ca351d..785974d62 100644 --- a/internal/backend/bn254/groth16/setup.go +++ b/internal/backend/bn254/groth16/setup.go @@ -48,6 +48,10 @@ type ProvingKey struct { Beta, Delta curve.G2Affine B []curve.G2Affine } + + // if InfinityA[i] == true, the point G1.A[i] == infinity + InfinityA, InfinityB []bool + NbInfinityA, NbInfinityB int } // VerifyingKey is used by a Groth16 verifier to verify the validity of a proof and a statement @@ -167,6 +171,33 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { zdt.Mul(&zdt, &toxicWaste.t) } + // mark points at infinity and filter them + 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 = 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 = nbWires - n + // compute our batch scalar multiplication with g1 elements g1Scalars := make([]fr.Element, 0, (nbWires*3)+int(domain.Cardinality)+3) g1Scalars = append(g1Scalars, toxicWaste.alphaReg, toxicWaste.betaReg, toxicWaste.deltaReg) @@ -184,11 +215,11 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { pk.G1.Delta = g1PointsAff[2] offset := 3 - pk.G1.A = g1PointsAff[offset : offset+nbWires] - offset += nbWires + pk.G1.A = g1PointsAff[offset : offset+len(A)] + offset += len(A) - pk.G1.B = g1PointsAff[offset : offset+nbWires] - offset += nbWires + pk.G1.B = g1PointsAff[offset : offset+len(B)] + offset += len(B) pk.G1.K = g1PointsAff[offset : offset+nbPrivateWires] offset += nbPrivateWires @@ -213,15 +244,15 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { g2PointsAff := curve.BatchScalarMultiplicationG2(&g2, g2Scalars) - pk.G2.B = g2PointsAff[:nbWires] + pk.G2.B = g2PointsAff[:len(B)] // sets pk: [β]2, [δ]2 - pk.G2.Beta = g2PointsAff[nbWires+0] - pk.G2.Delta = g2PointsAff[nbWires+1] + pk.G2.Beta = g2PointsAff[len(B)+0] + pk.G2.Delta = g2PointsAff[len(B)+1] // sets vk: [δ]2, [γ]2, -[δ]2, -[γ]2 - vk.G2.Delta = g2PointsAff[nbWires+1] - vk.G2.Gamma = g2PointsAff[nbWires+2] + 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) @@ -270,13 +301,18 @@ func setupABC(r1cs *cs.R1CS, domain *fft.Domain, toxicWaste toxicWaste) (A []fr. // L = 1/n*(t^n-1)/(t-1), Li+1 = w*Li*(t-w^i)/(t-w^(i+1)) - // Setting L + // Setting L0 L.Exp(toxicWaste.t, new(big.Int).SetUint64(uint64(domain.Cardinality))). Sub(&L, &one) L.Mul(&L, &tInv[0]). Mul(&L, &domain.CardinalityInv) - // Constraints + // each constraint is in the form + // L * R == O + // L, R and O being linear expressions + // for each term appearing in the linear expression, + // we compute term.Coefficient * L, and cumulate it in + // A, B or C at the indice of the variable for i, c := range r1cs.Constraints { for _, t := range c.L { @@ -360,12 +396,28 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { // Setting group for fft domain := fft.NewDomain(uint64(nbConstraints), 1, true) + // count number of infinity points we would have had we a normal setup + // in pk.G1.A, pk.G1.B, and pk.G2.B + nbZeroesA, nbZeroesB := dummyInfinityCount(r1cs) + // initialize proving key - pk.G1.A = make([]curve.G1Affine, nbWires) - pk.G1.B = make([]curve.G1Affine, nbWires) + 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) + pk.G2.B = make([]curve.G2Affine, nbWires-nbZeroesB) + + // set infinity markers + pk.InfinityA = make([]bool, nbWires) + pk.InfinityB = make([]bool, nbWires) + pk.NbInfinityA = nbZeroesA + pk.NbInfinityB = nbZeroesB + for i := 0; i < nbZeroesA; i++ { + pk.InfinityA[i] = true + } + for i := 0; i < nbZeroesB; i++ { + pk.InfinityB[i] = true + } // samples toxic waste toxicWaste, err := sampleToxicWaste() @@ -383,9 +435,13 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { var r2Aff curve.G2Affine r2Jac.ScalarMultiplication(&g2, &b) r2Aff.FromJacobian(&r2Jac) - for i := 0; i < int(nbWires); i++ { + 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++ { @@ -405,6 +461,34 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { return nil } +// 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 +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 + +} + // IsDifferent returns true if provided vk is different than self // this is used by groth16.Assert to ensure random sampling func (vk *VerifyingKey) IsDifferent(_other interface{}) bool { diff --git a/internal/backend/bw6-761/groth16/prove.go b/internal/backend/bw6-761/groth16/prove.go index af45351bb..322f0d63e 100644 --- a/internal/backend/bw6-761/groth16/prove.go +++ b/internal/backend/bw6-761/groth16/prove.go @@ -64,8 +64,18 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bw6_761witness.Witness, force b := make([]fr.Element, r1cs.NbConstraints, pk.Domain.Cardinality) c := make([]fr.Element, r1cs.NbConstraints, pk.Domain.Cardinality) wireValues := make([]fr.Element, r1cs.NbInternalVariables+r1cs.NbPublicVariables+r1cs.NbSecretVariables) - if err := r1cs.Solve(witness, a, b, c, wireValues); err != nil && !force { - return nil, err + if err := r1cs.Solve(witness, a, b, c, wireValues); err != nil { + if !force { + return nil, err + } else { + // we need to fill wireValues with random values else multi exps don't do much + var r fr.Element + _, _ = r.SetRandom() + for i := r1cs.NbPublicVariables + r1cs.NbSecretVariables; i < len(wireValues); i++ { + wireValues[i] = r + r.Double(&r) + } + } } // set the wire values in regular form @@ -86,6 +96,34 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bw6_761witness.Witness, force chHDone <- struct{}{} }() + // we need to copy and filter the wireValues for each multi exp + // as pk.G1.A, pk.G1.B and pk.G2.B may have (a significant) number of point at infinity + var wireValuesA, wireValuesB []fr.Element + chWireValuesA, chWireValuesB := make(chan struct{}, 1), make(chan struct{}, 1) + + go func() { + wireValuesA = make([]fr.Element, len(wireValues)-pk.NbInfinityA) + for i, j := 0, 0; j < len(wireValuesA); i++ { + if pk.InfinityA[i] { + continue + } + wireValuesA[j] = wireValues[i] + j++ + } + close(chWireValuesA) + }() + go func() { + wireValuesB = make([]fr.Element, len(wireValues)-pk.NbInfinityB) + for i, j := 0, 0; j < len(wireValuesB); i++ { + if pk.InfinityB[i] { + continue + } + wireValuesB[j] = wireValues[i] + j++ + } + close(chWireValuesB) + }() + // sample random r and s var r, s big.Int var _r, _s, _kr fr.Element @@ -113,7 +151,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bw6_761witness.Witness, force chBs1Done := make(chan error, 1) computeBS1 := func() { - if _, err := bs1.MultiExp(pk.G1.B, wireValues, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { + <-chWireValuesB + if _, err := bs1.MultiExp(pk.G1.B, wireValuesB, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { chBs1Done <- err close(chBs1Done) return @@ -125,7 +164,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bw6_761witness.Witness, force chArDone := make(chan error, 1) computeAR1 := func() { - if _, err := ar.MultiExp(pk.G1.A, wireValues, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { + <-chWireValuesA + if _, err := ar.MultiExp(pk.G1.A, wireValuesA, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { chArDone <- err close(chArDone) return @@ -192,7 +232,8 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness bw6_761witness.Witness, force // if we don't have a lot of CPUs, this may artificially split the MSM nbTasks *= 2 } - if _, err := Bs.MultiExp(pk.G2.B, wireValues, ecc.MultiExpConfig{NbTasks: nbTasks}); err != nil { + <-chWireValuesB + if _, err := Bs.MultiExp(pk.G2.B, wireValuesB, ecc.MultiExpConfig{NbTasks: nbTasks}); err != nil { return err } diff --git a/internal/backend/bw6-761/groth16/setup.go b/internal/backend/bw6-761/groth16/setup.go index e10c4331f..9ab64eb70 100644 --- a/internal/backend/bw6-761/groth16/setup.go +++ b/internal/backend/bw6-761/groth16/setup.go @@ -48,6 +48,10 @@ type ProvingKey struct { Beta, Delta curve.G2Affine B []curve.G2Affine } + + // if InfinityA[i] == true, the point G1.A[i] == infinity + InfinityA, InfinityB []bool + NbInfinityA, NbInfinityB int } // VerifyingKey is used by a Groth16 verifier to verify the validity of a proof and a statement @@ -167,6 +171,33 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { zdt.Mul(&zdt, &toxicWaste.t) } + // mark points at infinity and filter them + 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 = 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 = nbWires - n + // compute our batch scalar multiplication with g1 elements g1Scalars := make([]fr.Element, 0, (nbWires*3)+int(domain.Cardinality)+3) g1Scalars = append(g1Scalars, toxicWaste.alphaReg, toxicWaste.betaReg, toxicWaste.deltaReg) @@ -184,11 +215,11 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { pk.G1.Delta = g1PointsAff[2] offset := 3 - pk.G1.A = g1PointsAff[offset : offset+nbWires] - offset += nbWires + pk.G1.A = g1PointsAff[offset : offset+len(A)] + offset += len(A) - pk.G1.B = g1PointsAff[offset : offset+nbWires] - offset += nbWires + pk.G1.B = g1PointsAff[offset : offset+len(B)] + offset += len(B) pk.G1.K = g1PointsAff[offset : offset+nbPrivateWires] offset += nbPrivateWires @@ -213,15 +244,15 @@ func Setup(r1cs *cs.R1CS, pk *ProvingKey, vk *VerifyingKey) error { g2PointsAff := curve.BatchScalarMultiplicationG2(&g2, g2Scalars) - pk.G2.B = g2PointsAff[:nbWires] + pk.G2.B = g2PointsAff[:len(B)] // sets pk: [β]2, [δ]2 - pk.G2.Beta = g2PointsAff[nbWires+0] - pk.G2.Delta = g2PointsAff[nbWires+1] + pk.G2.Beta = g2PointsAff[len(B)+0] + pk.G2.Delta = g2PointsAff[len(B)+1] // sets vk: [δ]2, [γ]2, -[δ]2, -[γ]2 - vk.G2.Delta = g2PointsAff[nbWires+1] - vk.G2.Gamma = g2PointsAff[nbWires+2] + 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) @@ -270,13 +301,18 @@ func setupABC(r1cs *cs.R1CS, domain *fft.Domain, toxicWaste toxicWaste) (A []fr. // L = 1/n*(t^n-1)/(t-1), Li+1 = w*Li*(t-w^i)/(t-w^(i+1)) - // Setting L + // Setting L0 L.Exp(toxicWaste.t, new(big.Int).SetUint64(uint64(domain.Cardinality))). Sub(&L, &one) L.Mul(&L, &tInv[0]). Mul(&L, &domain.CardinalityInv) - // Constraints + // each constraint is in the form + // L * R == O + // L, R and O being linear expressions + // for each term appearing in the linear expression, + // we compute term.Coefficient * L, and cumulate it in + // A, B or C at the indice of the variable for i, c := range r1cs.Constraints { for _, t := range c.L { @@ -360,12 +396,28 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { // Setting group for fft domain := fft.NewDomain(uint64(nbConstraints), 1, true) + // count number of infinity points we would have had we a normal setup + // in pk.G1.A, pk.G1.B, and pk.G2.B + nbZeroesA, nbZeroesB := dummyInfinityCount(r1cs) + // initialize proving key - pk.G1.A = make([]curve.G1Affine, nbWires) - pk.G1.B = make([]curve.G1Affine, nbWires) + 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) + pk.G2.B = make([]curve.G2Affine, nbWires-nbZeroesB) + + // set infinity markers + pk.InfinityA = make([]bool, nbWires) + pk.InfinityB = make([]bool, nbWires) + pk.NbInfinityA = nbZeroesA + pk.NbInfinityB = nbZeroesB + for i := 0; i < nbZeroesA; i++ { + pk.InfinityA[i] = true + } + for i := 0; i < nbZeroesB; i++ { + pk.InfinityB[i] = true + } // samples toxic waste toxicWaste, err := sampleToxicWaste() @@ -383,9 +435,13 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { var r2Aff curve.G2Affine r2Jac.ScalarMultiplication(&g2, &b) r2Aff.FromJacobian(&r2Jac) - for i := 0; i < int(nbWires); i++ { + 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++ { @@ -405,6 +461,34 @@ func DummySetup(r1cs *cs.R1CS, pk *ProvingKey) error { return nil } +// 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 +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 + +} + // IsDifferent returns true if provided vk is different than self // this is used by groth16.Assert to ensure random sampling func (vk *VerifyingKey) IsDifferent(_other interface{}) bool { diff --git a/internal/generator/backend/template/zkpschemes/groth16/groth16.prove.go.tmpl b/internal/generator/backend/template/zkpschemes/groth16/groth16.prove.go.tmpl index 3d349d262..595f33179 100644 --- a/internal/generator/backend/template/zkpschemes/groth16/groth16.prove.go.tmpl +++ b/internal/generator/backend/template/zkpschemes/groth16/groth16.prove.go.tmpl @@ -42,8 +42,18 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness {{ toLower .CurveID }}witness. b := make([]fr.Element, r1cs.NbConstraints, pk.Domain.Cardinality) c := make([]fr.Element, r1cs.NbConstraints, pk.Domain.Cardinality) wireValues := make([]fr.Element, r1cs.NbInternalVariables+r1cs.NbPublicVariables+r1cs.NbSecretVariables) - if err := r1cs.Solve(witness, a, b, c, wireValues); err != nil && !force { - return nil, err + if err := r1cs.Solve(witness, a, b, c, wireValues); err != nil { + if !force { + return nil, err + } else { + // we need to fill wireValues with random values else multi exps don't do much + var r fr.Element + _, _ = r.SetRandom() + for i := r1cs.NbPublicVariables + r1cs.NbSecretVariables; i < len(wireValues); i++ { + wireValues[i] = r + r.Double(&r) + } + } } // set the wire values in regular form @@ -64,6 +74,34 @@ func Prove(r1cs *cs.R1CS, pk *ProvingKey, witness {{ toLower .CurveID }}witness. chHDone <- struct{}{} }() + // we need to copy and filter the wireValues for each multi exp + // as pk.G1.A, pk.G1.B and pk.G2.B may have (a significant) number of point at infinity + var wireValuesA, wireValuesB []fr.Element + chWireValuesA, chWireValuesB := make(chan struct{}, 1) , make(chan struct{}, 1) + + go func() { + wireValuesA = make([]fr.Element , len(wireValues) - pk.NbInfinityA) + for i,j :=0,0; j