From 282484a2c43c5120d592e2579ed82a0c5c5d3a29 Mon Sep 17 00:00:00 2001 From: Arya Tabaie <15056835+Tabaie@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:26:08 -0600 Subject: [PATCH] feat phase2 verification --- backend/groth16/bn254/mpcsetup/phase1.go | 24 +++----- backend/groth16/bn254/mpcsetup/phase2.go | 78 +++++++++++++++--------- backend/groth16/bn254/mpcsetup/utils.go | 29 ++++----- 3 files changed, 71 insertions(+), 60 deletions(-) diff --git a/backend/groth16/bn254/mpcsetup/phase1.go b/backend/groth16/bn254/mpcsetup/phase1.go index 90700abff..717d60069 100644 --- a/backend/groth16/bn254/mpcsetup/phase1.go +++ b/backend/groth16/bn254/mpcsetup/phase1.go @@ -47,14 +47,15 @@ func (p *Phase1) Contribute() { // SrsCommons are the circuit-independent components of the Groth16 SRS, // computed by the first phase. +// in all that follows, N is the domain size type SrsCommons struct { G1 struct { - Tau []curve.G1Affine // {[τ⁰]₁, [τ¹]₁, [τ²]₁, …, [τ²ⁿ⁻²]₁} - AlphaTau []curve.G1Affine // {α[τ⁰]₁, α[τ¹]₁, α[τ²]₁, …, α[τⁿ⁻¹]₁} - BetaTau []curve.G1Affine // {β[τ⁰]₁, β[τ¹]₁, β[τ²]₁, …, β[τⁿ⁻¹]₁} + Tau []curve.G1Affine // {[τ⁰]₁, [τ¹]₁, [τ²]₁, …, [τ²ᴺ⁻²]₁} + AlphaTau []curve.G1Affine // {α[τ⁰]₁, α[τ¹]₁, α[τ²]₁, …, α[τᴺ⁻¹]₁} + BetaTau []curve.G1Affine // {β[τ⁰]₁, β[τ¹]₁, β[τ²]₁, …, β[τᴺ⁻¹]₁} } G2 struct { - Tau []curve.G2Affine // {[τ⁰]₂, [τ¹]₂, [τ²]₂, …, [τⁿ⁻¹]₂} + Tau []curve.G2Affine // {[τ⁰]₂, [τ¹]₂, [τ²]₂, …, [τᴺ⁻¹]₂} Beta curve.G2Affine // [β]₂ } } @@ -181,22 +182,13 @@ func (p *Phase1) Verify(next *Phase1) error { return err } - if err := next.proofs.Tau.verify( - pair{p.parameters.G1.Tau[1], &p.parameters.G2.Tau[1]}, - pair{next.parameters.G1.Tau[1], &next.parameters.G2.Tau[1]}, - next.Challenge, 1); err != nil { + if err := next.proofs.Tau.verify(pair{p.parameters.G1.Tau[1], &p.parameters.G2.Tau[1]}, pair{next.parameters.G1.Tau[1], &next.parameters.G2.Tau[1]}, next.Challenge, 1); err != nil { return fmt.Errorf("failed to verify contribution to τ: %w", err) } - if err := next.proofs.Alpha.verify( // TODO Get ACTUAL updated tau - pair{taus, nil}, - pair{alphaTaus, nil}, - next.Challenge, 2); err != nil { + if err := next.proofs.Alpha.verify(pair{taus, nil}, pair{alphaTaus, nil}, next.Challenge, 2); err != nil { return fmt.Errorf("failed to verify contribution to α: %w", err) } - if err := next.proofs.Beta.verify( - pair{p.parameters.G1.BetaTau[0], &p.parameters.G2.Beta}, // TODO @Tabaie combine the verification of all βτⁱ - pair{next.parameters.G1.BetaTau[0], &next.parameters.G2.Beta}, - next.Challenge, 3); err != nil { + if err := next.proofs.Beta.verify(pair{p.parameters.G1.BetaTau[0], &p.parameters.G2.Beta}, pair{next.parameters.G1.BetaTau[0], &next.parameters.G2.Beta}, next.Challenge, 3); err != nil { return fmt.Errorf("failed to verify contribution to β: %w", err) } diff --git a/backend/groth16/bn254/mpcsetup/phase2.go b/backend/groth16/bn254/mpcsetup/phase2.go index 2f5d271de..6f1758b5f 100644 --- a/backend/groth16/bn254/mpcsetup/phase2.go +++ b/backend/groth16/bn254/mpcsetup/phase2.go @@ -20,7 +20,7 @@ import ( cs "github.com/consensys/gnark/constraint/bn254" ) -type Phase2Evaluations struct { +type Phase2Evaluations struct { // TODO @Tabaie rename G1 struct { A []curve.G1Affine // A are the left coefficient polynomials for each witness element, evaluated at τ B []curve.G1Affine // B are the right coefficient polynomials for each witness element, evaluated at τ @@ -36,13 +36,13 @@ type Phase2 struct { Parameters struct { G1 struct { Delta curve.G1Affine - Z []curve.G1Affine // Z are multiples of the domain vanishing polynomial - PKK []curve.G1Affine // PKK are the coefficients of the private witness - SigmaCKK [][]curve.G1Affine // Commitment bases + Z []curve.G1Affine // Z[i] = xⁱt(x)/δ where t is the domain vanishing polynomial 0 ≤ i ≤ N-2 + PKK []curve.G1Affine // PKK are the coefficients of the private witness, needed for the proving key. They have a denominator of δ + SigmaCKK [][]curve.G1Affine // Commitment proof bases: SigmaCKK[i][j] = Cᵢⱼσᵢ where Cᵢⱼ is the commitment basis for the jᵗʰ committed element from the iᵗʰ commitment } G2 struct { Delta curve.G2Affine - Sigma []curve.G2Affine + Sigma []curve.G2Affine // the secret σ value for each commitment } } @@ -55,43 +55,53 @@ type Phase2 struct { } func (c *Phase2) Verify(next *Phase2) error { - if challenge := c.hash(); len(next.Challenge) != 0 && !bytes.Equal(next.Challenge, challenge) { + challenge := c.hash() + if len(next.Challenge) != 0 && !bytes.Equal(next.Challenge, challenge) { return errors.New("the challenge does not match the previous phase's hash") } + next.Challenge = challenge - if err := next.Delta.verify( - pair{c.Parameters.G1.Delta, &c.Parameters.G2.Delta}, - pair{next.Parameters.G1.Delta, &next.Parameters.G2.Delta}, - next.Challenge, 1); err != nil { - return fmt.Errorf("failed to verify contribution to δ: %w", err) + if len(next.Parameters.G1.Z) != len(c.Parameters.G1.Z) || + len(next.Parameters.G1.PKK) != len(c.Parameters.G1.PKK) || + len(next.Parameters.G1.SigmaCKK) != len(c.Parameters.G1.SigmaCKK) || + len(next.Parameters.G2.Sigma) != len(c.Parameters.G2.Sigma) { + return errors.New("contribution size mismatch") } - for i := range c.Sigmas { - if err := next.Sigmas[i].verify( - pair{c.Parameters.G1.SigmaCKK[i][0], &c.Parameters.G2.Sigma[i]}, - pair{next.Parameters.G1.SigmaCKK[i][0], &next.Parameters.G2.Sigma[i]}, - next.Challenge, byte(2+i)); err != nil { - return fmt.Errorf("failed to verify contribution to σ[%d]: %w", i, err) - } - if !areInSubGroupG1(next.Parameters.G1.SigmaCKK[i][1:]) { + 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 + + 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) + } + + // verify proof of knowledge of contributions to the σᵢ + // and the correctness of updates to Parameters.G2.Sigma[i] and the Parameters.G1.SigmaCKK[i] + for i := range c.Sigmas { // match the first commitment basis elem against the contribution commitment + if !areInSubGroupG1(next.Parameters.G1.SigmaCKK[i]) { return errors.New("commitment proving key subgroup check failed") } + + if err := verifyContribution(&c.Sigmas[i], c.Parameters.G1.SigmaCKK[i], next.Parameters.G1.SigmaCKK[i], &c.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) + } } + // verify proof of knowledge of contribution to δ + // and the correctness of updates to Parameters.Gi.Delta, PKK[i], and Z[i] if !areInSubGroupG1(next.Parameters.G1.Z) || !areInSubGroupG1(next.Parameters.G1.PKK) { return errors.New("derived values 𝔾₁ subgroup check failed") } - r := linearCombCoeffs(len(next.Parameters.G1.Z)) - - for i := range c.Sigmas { - prevComb, nextComb := linearCombination(c.Parameters.G1.SigmaCKK[i], next.Parameters.G1.SigmaCKK[i], r) - if !sameRatioUnsafe(nextComb, prevComb, next.Parameters.G2.Sigma[i], c.Parameters.G2.Sigma[i]) { - return fmt.Errorf("failed to verify contribution to σ[%d]", i) - } + denom := cloneAppend([]curve.G1Affine{c.Parameters.G1.Delta}, next.Parameters.G1.Z, next.Parameters.G1.PKK) + num := cloneAppend([]curve.G1Affine{next.Parameters.G1.Delta}, c.Parameters.G1.Z, c.Parameters.G1.PKK) + if err := verifyContribution(&c.Delta, denom, num, &c.Parameters.G2.Delta, &next.Parameters.G2.Delta, 1); err != nil { + return fmt.Errorf("failed to verify contribution to δ: %w", err) } - linearCombination() + return nil } func (c *Phase2) Contribute() { @@ -315,7 +325,7 @@ func VerifyPhase2(c0, c1 *Phase2, c ...*Phase2) error { func verifyPhase2(current, contribution *Phase2) error { // Compute R for δ - deltaR := genR(contribution.PublicKey.SG, contribution.PublicKey.SXG, current.Challenge[:], 1) + deltaR := genR(contribution.PublicKey.SG, current.Challenge[:], 1) // Check for knowledge of δ if !sameRatio(contribution.PublicKey.SG, contribution.PublicKey.SXG, contribution.PublicKey.XR, deltaR) { @@ -348,3 +358,15 @@ func (c *Phase2) hash() []byte { c.writeTo(sha) return sha.Sum(nil) } + +func cloneAppend(s ...[]curve.G1Affine) []curve.G1Affine { + l := 0 + for _, s := range s { + l += len(s) + } + res := make([]curve.G1Affine, 0, l) + for _, s := range s { + res = append(res, s...) + } + return res +} diff --git a/backend/groth16/bn254/mpcsetup/utils.go b/backend/groth16/bn254/mpcsetup/utils.go index 49057f637..038482a36 100644 --- a/backend/groth16/bn254/mpcsetup/utils.go +++ b/backend/groth16/bn254/mpcsetup/utils.go @@ -41,7 +41,7 @@ func newPublicKey(x fr.Element, challenge []byte, dst byte) PublicKey { pk.SXG.ScalarMultiplication(&pk.SG, &xBi) // generate R based on sG1, sxG1, challenge, and domain separation tag (tau, alpha or beta) - R := genR(pk.SG, pk.SXG, challenge, dst) + R := genR(pk.SG, challenge, dst) // compute x*spG2 pk.XR.ScalarMultiplication(&R, &xBi) @@ -74,7 +74,7 @@ func powersI(a *big.Int, n int) []fr.Element { return powers(&aMont, n) } -// Returns [1, a, a², ..., aⁿ⁻¹ ] +// Returns [1, a, a², ..., aᴺ⁻¹ ] func powers(a *fr.Element, n int) []fr.Element { result := make([]fr.Element, n) @@ -144,20 +144,19 @@ func sameRatioUnsafe(n1, d1 curve.G1Affine, n2, d2 curve.G2Affine) bool { return res } -// returns a = ∑ rᵢAᵢ, b = ∑ rᵢBᵢ -func linearCombination(A, B []curve.G1Affine, r []fr.Element) (a, b curve.G1Affine) { +// returns ∑ rᵢAᵢ +func linearCombination(A []curve.G1Affine, r []fr.Element) curve.G1Affine { nc := runtime.NumCPU() - if _, err := a.MultiExp(A, r[:len(A)], ecc.MultiExpConfig{NbTasks: nc}); err != nil { + var res curve.G1Affine + if _, err := res.MultiExp(A, r[:len(A)], ecc.MultiExpConfig{NbTasks: nc}); err != nil { panic(err) } - if _, err := b.MultiExp(B, r[:len(B)], ecc.MultiExpConfig{NbTasks: nc}); err != nil { - panic(err) - } - return + return res } // linearCombinationsG1 assumes, and does not check, that rPowers[i+1] = rPowers[1].rPowers[i] for all applicable i // Also assumed that 3 ≤ N ≔ len(A) ≤ len(rPowers) +// the results are truncated = ∑_{i=0}^{N-2} rⁱAᵢ, shifted = ∑_{i=1}^{N-1} rⁱAᵢ func linearCombinationsG1(A []curve.G1Affine, rPowers []fr.Element) (truncated, shifted curve.G1Affine) { // the common section, 1 to N-2 var common curve.G1Affine @@ -176,6 +175,7 @@ func linearCombinationsG1(A []curve.G1Affine, rPowers []fr.Element) (truncated, // linearCombinationsG2 assumes, and does not check, that rPowers[i+1] = rPowers[1].rPowers[i] for all applicable i // Also assumed that 3 ≤ N ≔ len(A) ≤ len(rPowers) +// the results are truncated = ∑_{i=0}^{N-2} rⁱAᵢ, shifted = ∑_{i=1}^{N-1} rⁱAᵢ func linearCombinationsG2(A []curve.G2Affine, rPowers []fr.Element) (truncated, shifted curve.G2Affine) { // the common section, 1 to N-2 var common curve.G2Affine @@ -195,11 +195,10 @@ func linearCombinationsG2(A []curve.G2Affine, rPowers []fr.Element) (truncated, // Generate R∈𝔾₂ as Hash(gˢ, gˢˣ, challenge, dst) // it is to be used as a challenge for generating a proof of knowledge to x // π ≔ x.r; e([1]₁, π) =﹖ e([x]₁, r) -func genR(sG1, sxG1 curve.G1Affine, challenge []byte, dst byte) curve.G2Affine { +func genR(sG1 curve.G1Affine, challenge []byte, dst byte) curve.G2Affine { var buf bytes.Buffer buf.Grow(len(challenge) + curve.SizeOfG1AffineUncompressed*2) buf.Write(sG1.Marshal()) - buf.Write(sxG1.Marshal()) buf.Write(challenge) spG2, err := curve.HashToG2(buf.Bytes(), []byte{dst}) if err != nil { @@ -227,7 +226,6 @@ func (p *pair) validUpdate() bool { type valueUpdate struct { contributionCommitment curve.G1Affine // x or [Xⱼ]₁ contributionPok curve.G2Affine // π ≔ x.r ∈ 𝔾₂ - //updatedCommitment pair // [X₁..Xⱼ] } // updateValue produces values associated with contribution to an existing value. @@ -245,7 +243,7 @@ func updateValue(value *curve.G1Affine, challenge []byte, dst byte) (proof value value.ScalarMultiplication(value, &contributionValueI) // proof of knowledge to commitment. Algorithm 3 from section 3.7 - pokBase := genR(proof.contributionCommitment, *value, challenge, dst) // r + pokBase := genR(proof.contributionCommitment, challenge, dst) // r proof.contributionPok.ScalarMultiplication(&pokBase, &contributionValueI) return @@ -255,8 +253,7 @@ func updateValue(value *curve.G1Affine, challenge []byte, dst byte) (proof value // 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. // prevCommitment is assumed to be valid. No subgroup check and the like. -// challengePoint is normally equal to [denom] -func (x *valueUpdate) verify(denom, num pair, challengePoint curve.G1Affine, challenge []byte, dst byte) error { +func (x *valueUpdate) verify(denom, num pair, challenge []byte, dst byte) error { noG2 := denom.g2 == nil if noG2 != (num.g2 == nil) { return errors.New("erasing or creating g2 values") @@ -267,7 +264,7 @@ func (x *valueUpdate) verify(denom, num pair, challengePoint curve.G1Affine, cha } // verify commitment proof of knowledge. CheckPOK, algorithm 4 from section 3.7 - r := genR(x.contributionCommitment, challengePoint, challenge, dst) // verification challenge in the form of a g2 base + r := genR(x.contributionCommitment, challenge, dst) // verification challenge in the form of a g2 base _, _, g1, _ := curve.Generators() if !sameRatioUnsafe(x.contributionCommitment, g1, x.contributionPok, r) { // π =? x.r i.e. x/g1 =? π/r return errors.New("contribution proof of knowledge verification failed")