github.com/consensys/gnark-crypto@v0.14.0/internal/generator/sumcheck/template/sumcheck.go.tmpl (about)

     1  import (
     2  	"fmt"
     3  	"{{.FieldPackagePath}}"
     4  	"{{.FieldPackagePath}}/polynomial"
     5  	fiatshamir "github.com/consensys/gnark-crypto/fiat-shamir"
     6  	"strconv"
     7  )
     8  
     9  // This does not make use of parallelism and represents polynomials as lists of coefficients
    10  // It is currently geared towards arithmetic hashes. Once we have a more unified hash function interface, this can be generified.
    11  
    12  // Claims to a multi-sumcheck statement. i.e. one of the form ∑_{0≤i<2ⁿ} fⱼ(i) = cⱼ for 1 ≤ j ≤ m.
    13  // Later evolving into a claim of the form gⱼ = ∑_{0≤i<2ⁿ⁻ʲ} g(r₁, r₂, ..., rⱼ₋₁, Xⱼ, i...)
    14  type Claims interface {
    15  	Combine(a {{.ElementType}}) polynomial.Polynomial // Combine into the 0ᵗʰ sumcheck subclaim. Create g := ∑_{1≤j≤m} aʲ⁻¹fⱼ for which now we seek to prove ∑_{0≤i<2ⁿ} g(i) = c := ∑_{1≤j≤m} aʲ⁻¹cⱼ. Return g₁.
    16  	Next({{.ElementType}}) polynomial.Polynomial      // Return the evaluations gⱼ(k) for 1 ≤ k < degⱼ(g). Update the claim to gⱼ₊₁ for the input value as rⱼ
    17  	VarsNum() int                               //number of variables
    18  	ClaimsNum() int                             //number of claims
    19  	ProveFinalEval(r []{{.ElementType}}) interface{}  //in case it is difficult for the verifier to compute g(r₁, ..., rₙ) on its own, the prover can provide the value and a proof
    20  }
    21  
    22  // LazyClaims is the Claims data structure on the verifier side. It is "lazy" in that it has to compute fewer things.
    23  type LazyClaims interface {
    24  	ClaimsNum() int                      // ClaimsNum = m
    25  	VarsNum() int                        // VarsNum = n
    26  	CombinedSum(a {{.ElementType}}) {{.ElementType}} // CombinedSum returns c = ∑_{1≤j≤m} aʲ⁻¹cⱼ
    27  	Degree(i int) int                    //Degree of the total claim in the i'th variable
    28  	VerifyFinalEval(r []{{.ElementType}}, combinationCoeff {{.ElementType}}, purportedValue {{.ElementType}}, proof interface{}) error
    29  }
    30  
    31  // Proof of a multi-sumcheck statement.
    32  type Proof struct {
    33  	PartialSumPolys []polynomial.Polynomial `json:"partialSumPolys"`
    34  	FinalEvalProof  interface{}             `json:"finalEvalProof"` //in case it is difficult for the verifier to compute g(r₁, ..., rₙ) on its own, the prover can provide the value and a proof
    35  }
    36  
    37  func setupTranscript(claimsNum int, varsNum int, settings *fiatshamir.Settings) (challengeNames []string, err error) {
    38  	numChallenges := varsNum
    39  	if claimsNum >= 2 {
    40  		numChallenges++
    41  	}
    42  	challengeNames = make([]string, numChallenges)
    43  	if claimsNum >= 2 {
    44  		challengeNames[0] = settings.Prefix + "comb"
    45  	}
    46  	prefix := settings.Prefix + "pSP."
    47  	for i := 0; i < varsNum; i++ {
    48  		challengeNames[i+numChallenges-varsNum] = prefix + strconv.Itoa(i)
    49  	}
    50  	if settings.Transcript == nil {
    51  		transcript := fiatshamir.NewTranscript(settings.Hash, challengeNames...)
    52  		settings.Transcript = transcript
    53  	}
    54  
    55  	for i := range settings.BaseChallenges {
    56  		if err = settings.Transcript.Bind(challengeNames[0], settings.BaseChallenges[i]); err != nil {
    57  			return
    58  		}
    59  	}
    60  	return
    61  }
    62  
    63  func next(transcript *fiatshamir.Transcript, bindings []{{.ElementType}}, remainingChallengeNames *[]string) ({{.ElementType}}, error) {
    64  	challengeName := (*remainingChallengeNames)[0]
    65  	for i := range bindings {
    66  		bytes := bindings[i].Bytes()
    67  		if err := transcript.Bind(challengeName, bytes[:]); err != nil {
    68  			return {{.ElementType}}{}, err
    69  		}
    70  	}
    71  	var res {{.ElementType}}
    72  	bytes, err := transcript.ComputeChallenge(challengeName)
    73  	res.SetBytes(bytes)
    74  
    75  	*remainingChallengeNames = (*remainingChallengeNames)[1:]
    76  
    77  	return res, err
    78  }
    79  
    80  // Prove create a non-interactive sumcheck proof
    81  func Prove(claims Claims, transcriptSettings fiatshamir.Settings) (Proof, error) {
    82  
    83  	var proof Proof
    84  	remainingChallengeNames, err := setupTranscript(claims.ClaimsNum(), claims.VarsNum(), &transcriptSettings)
    85  	transcript := transcriptSettings.Transcript
    86  	if err != nil {
    87  		return proof, err
    88  	}
    89  	
    90  	var combinationCoeff {{.ElementType}}
    91  	if claims.ClaimsNum() >= 2 {
    92  		if combinationCoeff, err = next(transcript, []{{.ElementType}}{}, &remainingChallengeNames); err != nil {
    93  			return proof, err
    94  		}
    95  	}
    96  
    97  	varsNum := claims.VarsNum()
    98  	proof.PartialSumPolys = make([]polynomial.Polynomial, varsNum)
    99  	proof.PartialSumPolys[0] = claims.Combine(combinationCoeff)
   100  	challenges := make([]{{.ElementType}}, varsNum)
   101  
   102  	for j := 0; j+1 < varsNum; j++ {
   103  		if challenges[j], err = next(transcript, proof.PartialSumPolys[j], &remainingChallengeNames); err != nil {
   104  			return proof, err
   105  		}
   106  		proof.PartialSumPolys[j+1] = claims.Next(challenges[j])
   107  	}
   108  
   109  	if challenges[varsNum-1], err = next(transcript, proof.PartialSumPolys[varsNum-1], &remainingChallengeNames); err != nil {
   110  		return proof, err
   111  	}
   112  
   113  	proof.FinalEvalProof = claims.ProveFinalEval(challenges)
   114  
   115  	return proof, nil
   116  }
   117  
   118  func Verify(claims LazyClaims, proof Proof, transcriptSettings fiatshamir.Settings) error {
   119  	remainingChallengeNames, err := setupTranscript(claims.ClaimsNum(), claims.VarsNum(), &transcriptSettings)
   120  	transcript := transcriptSettings.Transcript
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	var combinationCoeff {{.ElementType}}
   126  
   127  	if claims.ClaimsNum() >= 2 {
   128  		if combinationCoeff, err = next(transcript, []{{.ElementType}}{}, &remainingChallengeNames); err != nil {
   129  			return err
   130  		}
   131  	}
   132  
   133  	r := make([]{{.ElementType}}, claims.VarsNum())
   134  
   135  	// Just so that there is enough room for gJ to be reused
   136  	maxDegree := claims.Degree(0)
   137  	for j := 1; j < claims.VarsNum(); j++ {
   138  		if d := claims.Degree(j); d > maxDegree {
   139  			maxDegree = d
   140  		}
   141  	}
   142  	gJ := make(polynomial.Polynomial, maxDegree+1) //At the end of iteration j, gJ = ∑_{i < 2ⁿ⁻ʲ⁻¹} g(X₁, ..., Xⱼ₊₁, i...)		NOTE: n is shorthand for claims.VarsNum()
   143  	gJR := claims.CombinedSum(combinationCoeff)    // At the beginning of iteration j, gJR = ∑_{i < 2ⁿ⁻ʲ} g(r₁, ..., rⱼ, i...)
   144  
   145  	for j := 0; j < claims.VarsNum(); j++ {
   146  		if len(proof.PartialSumPolys[j]) != claims.Degree(j) {
   147  			return fmt.Errorf("malformed proof")
   148  		}
   149  		copy(gJ[1:], proof.PartialSumPolys[j])
   150  		gJ[0].Sub(&gJR, &proof.PartialSumPolys[j][0]) // Requirement that gⱼ(0) + gⱼ(1) = gⱼ₋₁(r)
   151  		// gJ is ready
   152  
   153  		//Prepare for the next iteration
   154  		if r[j], err = next(transcript, proof.PartialSumPolys[j], &remainingChallengeNames); err != nil {
   155  			return err
   156  		}
   157  		// This is an extremely inefficient way of interpolating. TODO: Interpolate without symbolically computing a polynomial
   158  		gJCoeffs := polynomial.InterpolateOnRange(gJ[:(claims.Degree(j) + 1)])
   159  		gJR = gJCoeffs.Eval(&r[j])
   160  	}
   161  
   162  	return claims.VerifyFinalEval(r, combinationCoeff, gJR, proof.FinalEvalProof)
   163  }