Browse Source

expanded fri_test.go and implemented more for the fri verifier

main
Kevin Jue 2 years ago
parent
commit
da80afaac4
5 changed files with 177 additions and 18 deletions
  1. +8
    -0
      field/field.go
  2. +96
    -16
      plonky2_verifier/fri.go
  3. +58
    -2
      plonky2_verifier/fri_test.go
  4. +4
    -0
      plonky2_verifier/structs.go
  5. +11
    -0
      plonky2_verifier/utils.go

+ 8
- 0
field/field.go

@ -1,6 +1,8 @@
package field
import (
"math/big"
"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/std/math/emulated"
@ -28,3 +30,9 @@ func NewFieldAPI(api frontend.API) frontend.API {
}
return field
}
var r EmulatedField
func EmulatedFieldModulus() *big.Int {
return r.Modulus()
}

+ 96
- 16
plonky2_verifier/fri.go

@ -1,9 +1,9 @@
package plonky2_verifier
import (
"fmt"
"gnark-ed25519/field"
. "gnark-ed25519/field"
"gnark-ed25519/poseidon"
"math"
"github.com/consensys/gnark/frontend"
@ -34,16 +34,19 @@ type FriChip struct {
field frontend.API
qe *QuadraticExtensionAPI
poseidonChip *poseidon.PoseidonChip
friParams *FriParams
verifierOnlyCircuitData *VerifierOnlyCircuitData
}
func NewFriChip(api frontend.API, field frontend.API, qe *QuadraticExtensionAPI, friParams *FriParams) *FriChip {
func NewFriChip(api frontend.API, field frontend.API, qe *QuadraticExtensionAPI, poseidonChip *poseidon.PoseidonChip, friParams *FriParams) *FriChip {
return &FriChip{
api: api,
field: field,
qe: qe,
friParams: friParams,
api: api,
field: field,
qe: qe,
poseidonChip: poseidonChip,
friParams: friParams,
}
}
@ -52,9 +55,6 @@ func (f *FriChip) assertLeadingZeros(powWitness F, friConfig FriConfig) {
// Note that this is assuming that the Goldilocks field is being used. Specfically that the
// field is 64 bits long
maxPowWitness := uint64(math.Pow(2, float64(64-friConfig.ProofOfWorkBits))) - 1
f.field.Println(powWitness)
fmt.Println(maxPowWitness)
fmt.Println(friConfig.ProofOfWorkBits)
f.field.AssertIsLessOrEqual(powWitness, field.NewFieldElement(maxPowWitness))
}
@ -70,10 +70,63 @@ func (f *FriChip) fromOpeningsAndAlpha(openings *FriOpenings, alpha QuadraticExt
return reducedOpenings
}
func (f *FriChip) verifyMerkleProofToCap(leafData []F, leafIndex F, merkleCap MerkleCap, proof *MerkleProof) {
func (f *FriChip) hashOrNoop(data []F) Hash {
var elements Hash
if len(data) <= 4 {
// Pad the data to have a size of 4
for i, inputElement := range data {
elements[i] = inputElement
}
for i := len(data); i < 4; i++ {
elements[i] = f.qe.ZERO_F
}
return elements
} else {
hashOutput := f.poseidonChip.HashNToMNoPad(data, 4)
if len(hashOutput) != len(elements) {
panic("The length of hashOutput and elements is different")
}
for i, hashField := range hashOutput {
elements[i] = hashField
}
return elements
}
}
func (f *FriChip) verifyInitialProof(xIndex F, proof *FriInitialTreeProof, initialMerkleCaps []MerkleCap) {
func (f *FriChip) verifyMerkleProofToCapWithCapIndex(leafData []F, leafIndexBits []frontend.Variable, capIndex F, merkleCap MerkleCap, proof *MerkleProof) {
currentDigest := f.hashOrNoop(leafData)
if len(leafIndexBits) != len(proof.Siblings) {
panic("len(leafIndexBits) != len(proof.Siblings)")
}
fourZeros := [4]F{f.qe.ZERO_F, f.qe.ZERO_F, f.qe.ZERO_F, f.qe.ZERO_F}
for i, bit := range leafIndexBits {
sibling := proof.Siblings[i]
var leftSiblingState poseidon.PoseidonState
copy(leftSiblingState[0:4], sibling[0:4])
copy(leftSiblingState[4:8], currentDigest[0:4])
copy(leftSiblingState[8:12], fourZeros[0:4])
leftHash := f.poseidonChip.Poseidon(leftSiblingState)
leftHashCompress := leftHash[0:4]
var rightSiblingState poseidon.PoseidonState
copy(rightSiblingState[0:4], currentDigest[0:4])
copy(rightSiblingState[4:8], sibling[0:4])
copy(rightSiblingState[8:12], fourZeros[0:4])
rightHash := f.poseidonChip.Poseidon(rightSiblingState)
rightHashCompress := rightHash[0:4]
currentDigest = f.api.Select(bit, leftHashCompress, rightHashCompress).(Hash)
}
}
func (f *FriChip) verifyInitialProof(xIndexBits []frontend.Variable, proof *FriInitialTreeProof, initialMerkleCaps []MerkleCap, capIndex F) {
if len(proof.EvalsProofs) != len(initialMerkleCaps) {
panic("length of eval proofs in fri proof should equal length of initial merkle caps")
}
@ -82,7 +135,28 @@ func (f *FriChip) verifyInitialProof(xIndex F, proof *FriInitialTreeProof, initi
evals := proof.EvalsProofs[i].Elements
merkleProof := proof.EvalsProofs[i].MerkleProof
cap := initialMerkleCaps[i]
f.verifyMerkleProofToCap(evals, xIndex, cap, &merkleProof)
f.verifyMerkleProofToCapWithCapIndex(evals, xIndexBits, capIndex, cap, &merkleProof)
}
}
// / We decompose FRI query indices into bits without verifying that the decomposition given by
// / the prover is the canonical one. In particular, if `x_index < 2^field_bits - p`, then the
// / prover could supply the binary encoding of either `x_index` or `x_index + p`, since they are
// / congruent mod `p`. However, this only occurs with probability
// / p_ambiguous = (2^field_bits - p) / p
// / which is small for the field that we use in practice.
// /
// / In particular, the soundness error of one FRI query is roughly the codeword rate, which
// / is much larger than this ambiguous-element probability given any reasonable parameters.
// / Thus ambiguous elements contribute a negligible amount to soundness error.
// /
// / Here we compare the probabilities as a sanity check, to verify the claim above.
func (f *FriChip) assertNoncanonicalIndicesOK() {
numAmbiguousElems := uint64(math.MaxUint64) - EmulatedFieldModulus().Uint64() + 1
queryError := f.friParams.Config.rate()
pAmbiguous := float64(numAmbiguousElems) / float64(EmulatedFieldModulus().Uint64())
if pAmbiguous < queryError*1e-5 {
panic("A non-negligible portion of field elements are in the range that permits non-canonical encodings. Need to do more analysis or enforce canonical encodings.")
}
}
@ -95,11 +169,17 @@ func (f *FriChip) verifyQueryRound(
n uint64,
roundProof *FriQueryRound,
) {
f.verifyInitialProof(xIndex, &roundProof.InitialTreesProof, initialMerkleCaps)
nLog := log2Strict(uint(n))
f.assertNoncanonicalIndicesOK()
xIndexBits := f.qe.field.ToBinary(xIndex, nLog)
capIndex := f.qe.field.FromBinary(xIndexBits[len(xIndexBits)-int(f.friParams.Config.CapHeight):]...).(F)
f.verifyInitialProof(xIndexBits, &roundProof.InitialTreesProof, initialMerkleCaps, capIndex)
}
func (f *FriChip) VerifyFriProof(
openings *FriOpenings,
openings FriOpenings,
friChallenges *FriChallenges,
initialMerkleCaps []MerkleCap,
friProof *FriProof,
@ -116,9 +196,9 @@ func (f *FriChip) VerifyFriProof(
); */
// Check POW
f.assertLeadingZeros(friProof.PowWitness, f.friParams.Config)
f.assertLeadingZeros(friChallenges.FriPowResponse, f.friParams.Config)
precomputedReducedEvals := f.fromOpeningsAndAlpha(openings, friChallenges.FriAlpha)
precomputedReducedEvals := f.fromOpeningsAndAlpha(&openings, friChallenges.FriAlpha)
// Size of the LDE domain.
n := uint64(math.Pow(2, float64(f.friParams.DegreeBits+f.friParams.Config.RateBits)))

+ 58
- 2
plonky2_verifier/fri_test.go

@ -2,6 +2,7 @@ package plonky2_verifier
import (
. "gnark-ed25519/field"
"gnark-ed25519/poseidon"
"testing"
"github.com/consensys/gnark/frontend"
@ -13,11 +14,66 @@ type TestFriCircuit struct{}
func (circuit *TestFriCircuit) Define(api frontend.API) error {
proofWithPis := DeserializeProofWithPublicInputs("./data/fibonacci/proof_with_public_inputs.json")
commonCircuitData := DeserializeCommonCircuitData("./data/fibonacci/common_circuit_data.json")
verifierOnlyCircuitData := DeserializeVerifierOnlyCircuitData("./data/fibonacci/verifier_only_circuit_data.json")
field := NewFieldAPI(api)
qe := NewQuadraticExtensionAPI(field, commonCircuitData.DegreeBits)
poseidonChip := poseidon.NewPoseidonChip(api, field)
friChip := NewFriChip(api, field, qe, poseidonChip, &commonCircuitData.FriParams)
friChallenges := FriChallenges{
FriAlpha: QuadraticExtension{
NewFieldElementFromString("14641715242626918707"),
NewFieldElementFromString("10574243340537902930"),
},
FriBetas: []QuadraticExtension{},
FriPowResponse: NewFieldElement(82451580476419),
FriQueryIndicies: []F{
NewFieldElement(6790812084677375942),
NewFieldElement(12394212020331474798),
NewFieldElement(16457600747000998582),
NewFieldElement(1543271328932331916),
NewFieldElement(12115726870906958644),
NewFieldElement(6775897107605342797),
NewFieldElement(15989401564746021030),
NewFieldElement(10691676456016926845),
NewFieldElement(1632499470630032007),
NewFieldElement(1317292355445098328),
NewFieldElement(18391440812534384252),
NewFieldElement(17321705613231354333),
NewFieldElement(6176487551308859603),
NewFieldElement(7119835651572002873),
NewFieldElement(3903019169623116693),
NewFieldElement(4886491111111487546),
NewFieldElement(4087641893164620518),
NewFieldElement(13801643080324181364),
NewFieldElement(16993775312274189321),
NewFieldElement(9268202926222765679),
NewFieldElement(10683001302406181735),
NewFieldElement(13359465725531647963),
NewFieldElement(4523327590105620849),
NewFieldElement(4883588003760409588),
NewFieldElement(187699146998097671),
NewFieldElement(14489263557623716717),
NewFieldElement(11748359318238148146),
NewFieldElement(13636347200053048758),
},
}
initialMerkleCaps := []MerkleCap{
verifierOnlyCircuitData.ConstantSigmasCap,
proofWithPis.Proof.WiresCap,
proofWithPis.Proof.PlonkZsPartialProductsCap,
proofWithPis.Proof.QuotientPolysCap,
}
friChip.VerifyFriProof(
proofWithPis.Proof.Openings.ToFriOpenings(),
&friChallenges,
initialMerkleCaps,
&proofWithPis.Proof.OpeningProof,
)
friChip := NewFriChip(api, field, commonCircuitData.Config.FriConfig)
friChip.VerifyFriProof(&proofWithPis.Proof.OpeningProof)
return nil
}

+ 4
- 0
plonky2_verifier/structs.go

@ -75,6 +75,10 @@ type FriConfig struct {
// TODO: add FriReductionStrategy
}
func (fc *FriConfig) rate() float64 {
return 1.0 / float64((uint64(1) << fc.RateBits))
}
type FriParams struct {
Config FriConfig
Hiding bool

+ 11
- 0
plonky2_verifier/utils.go

@ -1,7 +1,9 @@
package plonky2_verifier
import (
"fmt"
. "gnark-ed25519/field"
"math/bits"
)
func reduceWithPowers(qe *QuadraticExtensionAPI, terms []QuadraticExtension, scalar QuadraticExtension) QuadraticExtension {
@ -19,3 +21,12 @@ func reduceWithPowers(qe *QuadraticExtensionAPI, terms []QuadraticExtension, sca
return sum
}
// Computes `log_2(n)`, panicking if `n` is not a power of two.
func log2Strict(n uint) int {
res := bits.TrailingZeros(n)
if n>>res != 1 {
panic(fmt.Sprintf("Not a power of two: %d", n))
}
return res
}

Loading…
Cancel
Save