diff --git a/backend/groth16/bn254/mpcsetup/marshal.go b/backend/groth16/bn254/mpcsetup/marshal.go index 4c1ab916c..3511a1a87 100644 --- a/backend/groth16/bn254/mpcsetup/marshal.go +++ b/backend/groth16/bn254/mpcsetup/marshal.go @@ -150,12 +150,11 @@ func (p *Phase2) ReadFrom(reader io.Reader) (int64, error) { func (c *Phase2Evaluations) refsSlice() []any { N := uint64(len(c.G1.A)) - expectedLen := 3*N + 4 - refs := make([]any, 4, expectedLen) + expectedLen := 3*N + 3 + refs := make([]any, 3, expectedLen) refs[0] = &c.G1.CKK refs[1] = &c.G1.VKK refs[2] = &c.PublicAndCommitmentCommitted - refs[3] = &c.NbConstraints refs = appendRefs(refs, c.G1.A) refs = appendRefs(refs, c.G1.B) refs = appendRefs(refs, c.G2.B) diff --git a/backend/groth16/bn254/mpcsetup/phase1.go b/backend/groth16/bn254/mpcsetup/phase1.go index 7097a7e07..81dd54779 100644 --- a/backend/groth16/bn254/mpcsetup/phase1.go +++ b/backend/groth16/bn254/mpcsetup/phase1.go @@ -44,8 +44,6 @@ type Phase1 struct { Challenge []byte // Hash of the transcript PRIOR to this participant } -// TODO @Tabaie use batch scalar multiplication whenever applicable - // Contribute contributes randomness to the Phase1 object. This mutates Phase1. // p is trusted to be well-formed. The ReadFrom function performs such basic sanity checks. func (p *Phase1) Contribute() { @@ -56,9 +54,9 @@ func (p *Phase1) Contribute() { tauContrib, alphaContrib, betaContrib fr.Element ) - p.proofs.Tau, tauContrib = updateValue(p.parameters.G1.Tau[1], p.Challenge, 1) - p.proofs.Alpha, alphaContrib = updateValue(p.parameters.G1.AlphaTau[0], p.Challenge, 2) - p.proofs.Beta, betaContrib = updateValue(p.parameters.G1.BetaTau[0], p.Challenge, 3) + p.proofs.Tau, tauContrib = newValueUpdate(p.Challenge, 1) + p.proofs.Alpha, alphaContrib = newValueUpdate(p.Challenge, 2) + p.proofs.Beta, betaContrib = newValueUpdate(p.Challenge, 3) p.parameters.update(&tauContrib, &alphaContrib, &betaContrib) } @@ -158,7 +156,7 @@ func (p *Phase1) Verify(next *Phase1) error { challenge := p.hash() if len(next.Challenge) != 0 && !bytes.Equal(next.Challenge, challenge) { - return errors.New("the challenge does not match the previous phase's hash") + return errors.New("the challenge does not match the previous contribution's hash") } next.Challenge = challenge diff --git a/backend/groth16/bn254/mpcsetup/phase2.go b/backend/groth16/bn254/mpcsetup/phase2.go index 957a3f70a..f826561ce 100644 --- a/backend/groth16/bn254/mpcsetup/phase2.go +++ b/backend/groth16/bn254/mpcsetup/phase2.go @@ -34,7 +34,6 @@ type Phase2Evaluations struct { // TODO @Tabaie rename B []curve.G2Affine // B are the right coefficient polynomials for each witness element, evaluated at τ } PublicAndCommitmentCommitted [][]int - NbConstraints uint64 // TODO unnecessary. len(Z) has that information (domain size) } type Phase2 struct { @@ -59,12 +58,10 @@ type Phase2 struct { Challenge []byte } -// TODO @Tabaie use batch scalar multiplication whenever applicable - func (p *Phase2) Verify(next *Phase2) error { challenge := p.hash() if len(next.Challenge) != 0 && !bytes.Equal(next.Challenge, challenge) { - return errors.New("the challenge does not match the previous phase's hash") + return errors.New("the challenge does not match the previous contribution's hash") } next.Challenge = challenge @@ -75,13 +72,13 @@ func (p *Phase2) Verify(next *Phase2) error { return errors.New("contribution size mismatch") } - r := linearCombCoeffs(len(next.Parameters.G1.Z) + len(next.Parameters.G1.PKK) + 1) // TODO @Tabaie If all contributions are being verified in one go, we could reuse r + r := linearCombCoeffs(len(next.Parameters.G1.Z) + len(next.Parameters.G1.PKK) + 1) verifyContribution := func(update *valueUpdate, g1Denominator, g1Numerator []curve.G1Affine, g2Denominator, g2Numerator *curve.G2Affine, dst byte) error { g1Num := linearCombination(g1Numerator, r) g1Denom := linearCombination(g1Denominator, r) - return update.verify(pair{g1Denom, g2Denominator}, pair{g1Num, g2Denominator}, challenge, dst) + return update.verify(pair{g1Denom, g2Denominator}, pair{g1Num, g2Numerator}, challenge, dst) } // verify proof of knowledge of contributions to the σᵢ @@ -91,7 +88,7 @@ func (p *Phase2) Verify(next *Phase2) error { return errors.New("commitment proving key subgroup check failed") } - if err := verifyContribution(&p.Sigmas[i], p.Parameters.G1.SigmaCKK[i], next.Parameters.G1.SigmaCKK[i], &p.Parameters.G2.Sigma[i], &next.Parameters.G2.Sigma[i], 2+byte(i)); err != nil { + if err := verifyContribution(&next.Sigmas[i], p.Parameters.G1.SigmaCKK[i], next.Parameters.G1.SigmaCKK[i], &p.Parameters.G2.Sigma[i], &next.Parameters.G2.Sigma[i], 2+byte(i)); err != nil { return fmt.Errorf("failed to verify contribution to σ[%d]: %w", i, err) } } @@ -104,7 +101,7 @@ func (p *Phase2) Verify(next *Phase2) error { denom := cloneAppend([]curve.G1Affine{p.Parameters.G1.Delta}, next.Parameters.G1.Z, next.Parameters.G1.PKK) num := cloneAppend([]curve.G1Affine{next.Parameters.G1.Delta}, p.Parameters.G1.Z, p.Parameters.G1.PKK) - if err := verifyContribution(&p.Delta, denom, num, &p.Parameters.G2.Delta, &next.Parameters.G2.Delta, 1); err != nil { + if err := verifyContribution(&next.Delta, denom, num, &p.Parameters.G2.Delta, &next.Parameters.G2.Delta, 1); err != nil { return fmt.Errorf("failed to verify contribution to δ: %w", err) } @@ -132,8 +129,7 @@ func (p *Phase2) update(delta *fr.Element, sigma []fr.Element) { for j := range s { scale(&s[j]) } - point := &p.Parameters.G2.Sigma[i] - point.ScalarMultiplicationBase(&I) + scale(&p.Parameters.G2.Sigma[i]) } delta.BigInt(&I) @@ -155,14 +151,14 @@ func (p *Phase2) Contribute() { // sample value contributions and provide correctness proofs var delta fr.Element - p.Delta, delta = updateValue(p.Parameters.G1.Delta, p.Challenge, 1) + p.Delta, delta = newValueUpdate(p.Challenge, 1) sigma := make([]fr.Element, len(p.Parameters.G1.SigmaCKK)) if len(sigma) > 255 { panic("too many commitments") // DST collision } for i := range sigma { - p.Sigmas[i], sigma[i] = updateValue(p.Parameters.G1.SigmaCKK[i][0], p.Challenge, byte(2+i)) + p.Sigmas[i], sigma[i] = newValueUpdate(p.Challenge, byte(2+i)) } p.update(&delta, sigma) @@ -230,7 +226,6 @@ func (p *Phase2) Initialize(r1cs *cs.R1CS, commons *SrsCommons) Phase2Evaluation var evals Phase2Evaluations commitmentInfo := r1cs.CommitmentInfo.(constraint.Groth16Commitments) evals.PublicAndCommitmentCommitted = commitmentInfo.GetPublicAndCommitmentCommitted(commitmentInfo.CommitmentIndexes(), nbPublic) - evals.NbConstraints = uint64(r1cs.GetNbConstraints()) evals.G1.A = make([]curve.G1Affine, nWires) // recall: A are the left coefficients in DIZK parlance evals.G1.B = make([]curve.G1Affine, nWires) // recall: B are the right coefficients in DIZK parlance evals.G2.B = make([]curve.G2Affine, nWires) // recall: A only appears in 𝔾₁ elements in the proof, but B needs to appear in a 𝔾₂ element so the verifier can compute something resembling (A.x).(B.x) via pairings @@ -272,7 +267,7 @@ func (p *Phase2) Initialize(r1cs *cs.R1CS, commons *SrsCommons) Phase2Evaluation // τⁱ(τⁿ - 1) = τ⁽ⁱ⁺ⁿ⁾ - τⁱ for i ∈ [0, n-2] n := len(commons.G1.AlphaTau) p.Parameters.G1.Z = make([]curve.G1Affine, n) - for i := 0; i < n-1; i++ { // TODO @Tabaie why is the last element always 0? + for i := range n - 1 { p.Parameters.G1.Z[i].Sub(&commons.G1.Tau[i+n], &commons.G1.Tau[i]) } bitReverse(p.Parameters.G1.Z) diff --git a/backend/groth16/bn254/mpcsetup/setup.go b/backend/groth16/bn254/mpcsetup/setup.go index 715ac7fc3..4c575abe2 100644 --- a/backend/groth16/bn254/mpcsetup/setup.go +++ b/backend/groth16/bn254/mpcsetup/setup.go @@ -13,8 +13,6 @@ import ( groth16Impl "github.com/consensys/gnark/backend/groth16/bn254" ) -// TODO @Tabaie use batch scalar multiplication whenever applicable - // Seal performs the final contribution and outputs the proving and verifying keys. // No randomization is performed at this step. // A verifier should simply re-run this and check @@ -36,7 +34,7 @@ func (p *Phase2) Seal(commons *SrsCommons, evals *Phase2Evaluations, beaconChall ) // Initialize PK - pk.Domain = *fft.NewDomain(evals.NbConstraints) // TODO @Tabaie replace with len(Z)+1 + pk.Domain = *fft.NewDomain(uint64(len(commons.G1.AlphaTau))) pk.G1.Alpha.Set(&commons.G1.AlphaTau[0]) pk.G1.Beta.Set(&commons.G1.BetaTau[0]) pk.G1.Delta.Set(&p.Parameters.G1.Delta) diff --git a/backend/groth16/bn254/mpcsetup/setup_test.go b/backend/groth16/bn254/mpcsetup/setup_test.go index d5714bca9..f5ddda2c2 100644 --- a/backend/groth16/bn254/mpcsetup/setup_test.go +++ b/backend/groth16/bn254/mpcsetup/setup_test.go @@ -182,22 +182,21 @@ func (circuit *Circuit) Define(api frontend.API) error { } func assignCircuit() frontend.Circuit { - return sync.OnceValue(func() frontend.Circuit { - // Build the witness - var preImage, hash fr.Element - { - m := native_mimc.NewMiMC() - m.Write(preImage.Marshal()) - hash.SetBytes(m.Sum(nil)) - } - return &Circuit{PreImage: preImage, Hash: hash} - })() + // Build the witness + var preImage, hash fr.Element + + m := native_mimc.NewMiMC() + m.Write(preImage.Marshal()) + hash.SetBytes(m.Sum(nil)) + + return &Circuit{PreImage: preImage, Hash: hash} + } func getTestCircuit(t *testing.T) *cs.R1CS { return sync.OnceValue(func() *cs.R1CS { - ccs, err := frontend.Compile(curve.ID.ScalarField(), r1cs.NewBuilder, assignCircuit()) + ccs, err := frontend.Compile(curve.ID.ScalarField(), r1cs.NewBuilder, &Circuit{}) require.NoError(t, err) return ccs.(*cs.R1CS) })() diff --git a/backend/groth16/bn254/mpcsetup/unit_test.go b/backend/groth16/bn254/mpcsetup/unit_test.go index 35cae3d23..b7d24bb80 100644 --- a/backend/groth16/bn254/mpcsetup/unit_test.go +++ b/backend/groth16/bn254/mpcsetup/unit_test.go @@ -12,6 +12,7 @@ import ( cs "github.com/consensys/gnark/constraint/bn254" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/frontend/cs/r1cs" + "github.com/consensys/gnark/internal/utils/test_utils" gnarkio "github.com/consensys/gnark/io" "github.com/stretchr/testify/require" "math/big" @@ -29,7 +30,7 @@ func TestContributionPok(t *testing.T) { ) x0, err := curve.HashToG1([]byte("contribution test"), nil) require.NoError(t, err) - proof, d := updateValue(x0, []byte(pokChallenge), pokDst) + proof, d := newValueUpdate([]byte(pokChallenge), pokDst) var ( x1 curve.G1Affine dI big.Int @@ -438,7 +439,7 @@ func frs(x ...int) []fr.Element { return res } -func TestSerialization(t *testing.T) { +func TestPhase2Serialization(t *testing.T) { testRoundtrip := func(_cs constraint.ConstraintSystem) { var ( @@ -453,15 +454,7 @@ func TestSerialization(t *testing.T) { require.NoError(t, gnarkio.RoundTripCheck(&p2, func() interface{} { return new(Phase2) })) } - /*var p Phase2 - const b64 = "AACNaN0mCOtKUAD0aEvRP0h7pXctaB+w5Mwsb+skm2yDuPzlwTs+qCFf/3INR+fP/lHY6BLnqXyBjAIgCoPxOcSIEG0tcty/TAiaCN3lHCRacU+upLP+WpngByrrxbN9KrhmQLY3mhOHaV5Jo3W9pI2lTpLK9ZjkQpYKd92YCRKkJ9LyX3wqeYR4jQFf1mxtfJSNgluSZUUn3AoUSDmvh8m87TRh/JRcRZnq40BgnhkJ5nHs9siMSmhWGFjGgW/mOqpyrFoZEoK2rP+AT6ylkNGYxMmOBUj0meoeI2FB7RDqcuSxQOL1XK+Pm1dhxND33cykwpTF4oCrqQzSonxQGn+wFNzaYREOmkjCS9i12NbpXNyN2b9YpmujAL/GSD5LAwKNaN0mCOtKUAD0aEvRP0h7pXctaB+w5Mwsb+skm2yDuJ8HrqP1uckhSJCcTOeHMHyh0VqJtnoMhkRAWRPEWcsqIP3sH81riS5ARP1Pv172lVAmfoXnCzwFPNFPnvdSGFk=" - b, err := base64.StdEncoding.DecodeString(b64) - require.NoError(t, err) - n, err := p.ReadFrom(bytes.NewReader(b)) - require.NoError(t, err) - require.Equal(t, int64(len(b)), n)*/ - - _cs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &tinyCircuit{}) + _cs, err := frontend.Compile(curve.ID.ScalarField(), r1cs.NewBuilder, &tinyCircuit{}) require.NoError(t, err) testRoundtrip(_cs) @@ -515,3 +508,24 @@ func sliceSliceEqual[T comparable](a, b [][]T) bool { } return true } + +func getSimplePhase2(t *testing.T, circuit frontend.Circuit) Phase2 { + _cs, err := frontend.Compile(curve.ID.ScalarField(), r1cs.NewBuilder, circuit) + require.NoError(t, err) + cs := _cs.(*cs.R1CS) + var commons SrsCommons + commons.setOne(ecc.NextPowerOfTwo(uint64(cs.GetNbConstraints()))) + var p Phase2 + p.Initialize(cs, &commons) + return p +} + +func TestPhase2(t *testing.T) { + p0 := getSimplePhase2(t, &Circuit{}) + + var p1 Phase2 + test_utils.CopyThruSerialization(t, &p1, &p0) + p1.Contribute() + + require.NoError(t, p0.Verify(&p1)) +} diff --git a/backend/groth16/bn254/mpcsetup/utils.go b/backend/groth16/bn254/mpcsetup/utils.go index bac412c89..18d628a9f 100644 --- a/backend/groth16/bn254/mpcsetup/utils.go +++ b/backend/groth16/bn254/mpcsetup/utils.go @@ -17,8 +17,6 @@ import ( "runtime" ) -// TODO @Tabaie use batch scalar multiplication whenever applicable - func bitReverse[T any](a []T) { n := uint64(len(a)) nn := uint64(64 - bits.TrailingZeros64(n)) @@ -229,18 +227,17 @@ type valueUpdate struct { contributionPok curve.G2Affine // π ≔ x.r ∈ 𝔾₂ } -// updateValue produces values associated with contribution to an existing value. +// newValueUpdate produces values associated with contribution to an existing value. // the second output is toxic waste. It is the caller's responsibility to safely "dispose" of it. -func updateValue(value curve.G1Affine, challenge []byte, dst byte) (proof valueUpdate, contributionValue fr.Element) { +func newValueUpdate(challenge []byte, dst byte) (proof valueUpdate, contributionValue fr.Element) { if _, err := contributionValue.SetRandom(); err != nil { panic(err) } var contributionValueI big.Int contributionValue.BigInt(&contributionValueI) - _, _, g1, _ := curve.Generators() - proof.contributionCommitment.ScalarMultiplication(&g1, &contributionValueI) - value.ScalarMultiplication(&value, &contributionValueI) + _, _, gen1, _ := curve.Generators() + proof.contributionCommitment.ScalarMultiplication(&gen1, &contributionValueI) // proof of knowledge to commitment. Algorithm 3 from section 3.7 pokBase := genR(proof.contributionCommitment, challenge, dst) // r @@ -249,6 +246,9 @@ func updateValue(value curve.G1Affine, challenge []byte, dst byte) (proof valueU return } +// TODO @Tabaie batchVerify(denomG1, numG1 []G1Affine, denomG2, numG2 []G2Affine, challenge, dst) +// option for linear combination vector + // verify corresponds with verification steps {i, i+3} with 1 ≤ i ≤ 3 in section 7.1 of Bowe-Gabizon17 // it checks the proof of knowledge of the contribution, and the fact that the product of the contribution // and previous commitment makes the new commitment.