2025-05-27 10:27:40 +02:00

192 lines
6.3 KiB
Go

package verifier
import (
"github.com/consensys/gnark/frontend"
"github.com/succinctlabs/gnark-plonky2-verifier/challenger"
"github.com/succinctlabs/gnark-plonky2-verifier/fri"
gl "github.com/succinctlabs/gnark-plonky2-verifier/goldilocks"
"github.com/succinctlabs/gnark-plonky2-verifier/plonk"
"github.com/succinctlabs/gnark-plonky2-verifier/poseidon"
"github.com/succinctlabs/gnark-plonky2-verifier/types"
"github.com/succinctlabs/gnark-plonky2-verifier/variables"
)
type VerifierChip struct {
api frontend.API `gnark:"-"`
glChip *gl.Chip `gnark:"-"`
poseidonGlChip *poseidon.GoldilocksChip `gnark:"-"`
poseidonBN254Chip *poseidon.BN254Chip `gnark:"-"`
plonkChip *plonk.PlonkChip `gnark:"-"`
friChip *fri.Chip `gnark:"-"`
commonData types.CommonCircuitData `gnark:"-"`
}
func NewVerifierChip(api frontend.API, commonCircuitData types.CommonCircuitData) *VerifierChip {
glChip := gl.New(api)
friChip := fri.NewChip(api, &commonCircuitData, &commonCircuitData.FriParams)
plonkChip := plonk.NewPlonkChip(api, commonCircuitData)
poseidonGlChip := poseidon.NewGoldilocksChip(api)
poseidonBN254Chip := poseidon.NewBN254Chip(api)
return &VerifierChip{
api: api,
glChip: glChip,
poseidonGlChip: poseidonGlChip,
poseidonBN254Chip: poseidonBN254Chip,
plonkChip: plonkChip,
friChip: friChip,
commonData: commonCircuitData,
}
}
func (c *VerifierChip) GetPublicInputsHash(publicInputs []gl.Variable) poseidon.GoldilocksHashOut {
return c.poseidonGlChip.HashNoPad(publicInputs)
}
func (c *VerifierChip) GetChallenges(
proof variables.Proof,
publicInputsHash poseidon.GoldilocksHashOut,
verifierData variables.VerifierOnlyCircuitData,
) variables.ProofChallenges {
config := c.commonData.Config
numChallenges := config.NumChallenges
challenger := challenger.NewChip(c.api)
// observe the FRI config
var friParams = c.commonData.FriParams
var friConfig = friParams.Config
challenger.ObserveElement(gl.NewVariable(friConfig.RateBits))
challenger.ObserveElement(gl.NewVariable(friConfig.CapHeight))
challenger.ObserveElement(gl.NewVariable(friConfig.ProofOfWorkBits))
// here we fix the reduction strategy to the standard one:
// reduction_strategy: FriReductionStrategy::ConstantArityBits(4, 5)
// this is serialized as [1,4,5]
// TODO: make this work for all reduction strategies
challenger.ObserveElement(gl.One())
challenger.ObserveElement(gl.NewVariable(4))
challenger.ObserveElement(gl.NewVariable(5))
challenger.ObserveElement(gl.NewVariable(friConfig.NumQueryRounds))
var hide uint64 = boolToUint64(friParams.Hiding)
challenger.ObserveElement(gl.NewVariable(hide))
challenger.ObserveElement(gl.NewVariable(friParams.DegreeBits))
for _, arity := range friParams.ReductionArityBits {
challenger.ObserveElement(gl.NewVariable(arity))
}
var circuitDigest = verifierData.CircuitDigest
challenger.ObserveBN254Hash(circuitDigest)
challenger.ObserveHash(publicInputsHash)
challenger.ObserveCap(proof.WiresCap)
plonkBetas := challenger.GetNChallenges(numChallenges)
plonkGammas := challenger.GetNChallenges(numChallenges)
challenger.ObserveCap(proof.PlonkZsPartialProductsCap)
plonkAlphas := challenger.GetNChallenges(numChallenges)
challenger.ObserveCap(proof.QuotientPolysCap)
plonkZeta := challenger.GetExtensionChallenge()
challenger.ObserveOpenings(c.friChip.ToOpenings(proof.Openings))
return variables.ProofChallenges{
PlonkBetas: plonkBetas,
PlonkGammas: plonkGammas,
PlonkAlphas: plonkAlphas,
PlonkZeta: plonkZeta,
FriChallenges: challenger.GetFriChallenges(
proof.OpeningProof.CommitPhaseMerkleCaps,
proof.OpeningProof.FinalPoly,
proof.OpeningProof.PowWitness,
config.FriConfig,
),
}
}
func (c *VerifierChip) rangeCheckProof(proof variables.Proof) {
// Need to verify the plonky2 proof's openings, openings proof (other than the sibling elements), fri's final poly, pow witness.
// Note that this is NOT range checking the public inputs (first 32 elements should be no more than 8 bits and the last 4 elements should be no more than 64 bits). Since this is currently being inputted via the smart contract,
// we will assume that caller is doing that check.
// Range check the proof's openings.
for _, constant := range proof.Openings.Constants {
c.glChip.RangeCheckQE(constant)
}
for _, plonkSigma := range proof.Openings.PlonkSigmas {
c.glChip.RangeCheckQE(plonkSigma)
}
for _, wire := range proof.Openings.Wires {
c.glChip.RangeCheckQE(wire)
}
for _, plonkZ := range proof.Openings.PlonkZs {
c.glChip.RangeCheckQE(plonkZ)
}
for _, plonkZNext := range proof.Openings.PlonkZsNext {
c.glChip.RangeCheckQE(plonkZNext)
}
for _, partialProduct := range proof.Openings.PartialProducts {
c.glChip.RangeCheckQE(partialProduct)
}
for _, quotientPoly := range proof.Openings.QuotientPolys {
c.glChip.RangeCheckQE(quotientPoly)
}
// Range check the openings proof.
for _, queryRound := range proof.OpeningProof.QueryRoundProofs {
for _, evalsProof := range queryRound.InitialTreesProof.EvalsProofs {
for _, evalsProofElement := range evalsProof.Elements {
c.glChip.RangeCheck(evalsProofElement)
}
}
for _, queryStep := range queryRound.Steps {
for _, eval := range queryStep.Evals {
c.glChip.RangeCheckQE(eval)
}
}
}
// Range check the fri's final poly.
for _, coeff := range proof.OpeningProof.FinalPoly.Coeffs {
c.glChip.RangeCheckQE(coeff)
}
// Range check the pow witness.
c.glChip.RangeCheck(proof.OpeningProof.PowWitness)
}
func (c *VerifierChip) Verify(
proof variables.Proof,
publicInputs []gl.Variable,
verifierData variables.VerifierOnlyCircuitData,
) {
c.rangeCheckProof(proof)
// Generate the parts of the witness that is for the plonky2 proof input
publicInputsHash := c.GetPublicInputsHash(publicInputs)
proofChallenges := c.GetChallenges(proof, publicInputsHash, verifierData)
c.plonkChip.Verify(proofChallenges, proof.Openings, publicInputsHash)
initialMerkleCaps := []variables.FriMerkleCap{
verifierData.ConstantSigmasCap,
proof.WiresCap,
proof.PlonkZsPartialProductsCap,
proof.QuotientPolysCap,
}
c.friChip.VerifyFriProof(
c.friChip.GetInstance(proofChallenges.PlonkZeta),
c.friChip.ToOpenings(proof.Openings),
&proofChallenges.FriChallenges,
initialMerkleCaps,
&proof.OpeningProof,
)
}