Skip to content

Commit

Permalink
Merge pull request #14 from reilabs/wz/4844-pr
Browse files Browse the repository at this point in the history
[4/4 EIP 4844 in inserter circuit] prover: insertion_circuit: implement EIP 4844
  • Loading branch information
wzmuda authored Jun 29, 2024
2 parents 4245085 + 49e57af commit e3341d2
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 51 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/consensys/bavard v0.1.13 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect
github.com/ingonyama-zk/icicle v0.0.0-20230928131117-97f0079e5c71 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ github.com/consensys/gnark-crypto v0.12.2-0.20240504013751-564b6f724c3b/go.mod h
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI=
github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
6 changes: 4 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"math/big"
"os"
"os/signal"

"worldcoin/gnark-mbu/logging"
poseidon "worldcoin/gnark-mbu/poseidon_native"
"worldcoin/gnark-mbu/prover"
"worldcoin/gnark-mbu/server"

Expand Down Expand Up @@ -226,7 +228,7 @@ func main() {

if mode == server.InsertionMode {
params := prover.InsertionParameters{}
tree := NewTree(treeDepth)
tree := poseidon.NewTree(treeDepth)

params.StartIndex = 0
params.PreRoot = tree.Root()
Expand All @@ -241,7 +243,7 @@ func main() {
r, err = json.Marshal(&params)
} else if mode == server.DeletionMode {
params := prover.DeletionParameters{}
tree := NewTree(treeDepth)
tree := poseidon.NewTree(treeDepth)

params.DeletionIndices = make([]uint32, batchSize)
params.IdComms = make([]big.Int, batchSize)
Expand Down
5 changes: 3 additions & 2 deletions test_tree.go → poseidon_native/poseidon_native.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package main
package poseidon_native

import (
"github.com/iden3/go-iden3-crypto/poseidon"
"math/big"

"github.com/iden3/go-iden3-crypto/poseidon"
)

type PoseidonNode interface {
Expand Down
8 changes: 4 additions & 4 deletions prover/circuit_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"os"
"strconv"

"worldcoin/gnark-mbu/logging"
"worldcoin/gnark-mbu/prover/poseidon"

Expand Down Expand Up @@ -244,18 +245,17 @@ func (r ReducedModRCheck) DefineGadget(api frontend.API) interface{} {
return []frontend.Variable{}
}

// ToReducedBinaryBigEndian converts the provided variable to the corresponding bit
// ToBigEndian converts the provided variable to the corresponding bit
// pattern using big-endian byte ordering. It also makes sure to pick the smallest
// binary representation (i.e. one that is reduced modulo scalar field order).
type ToReducedBigEndian struct {
type ToBigEndian struct {
Variable frontend.Variable

Size int
}

func (gadget ToReducedBigEndian) DefineGadget(api frontend.API) interface{} {
func (gadget ToBigEndian) DefineGadget(api frontend.API) interface{} {
bitsLittleEndian := api.ToBinary(gadget.Variable, gadget.Size)
abstractor.CallVoid(api, ReducedModRCheck{Input: bitsLittleEndian})

// Swapping Endianness
// It does not introduce any new circuit constraints as it simply moves the
Expand Down
6 changes: 3 additions & 3 deletions prover/deletion_circuit.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ func (circuit *DeletionMbuCircuit) Define(api frontend.API) error {
var bits []frontend.Variable

for i := 0; i < circuit.BatchSize; i++ {
bits_idx := abstractor.Call1(api, ToReducedBigEndian{Variable: circuit.DeletionIndices[i], Size: 32})
bits_idx := abstractor.Call1(api, ToBigEndian{Variable: circuit.DeletionIndices[i], Size: 32})
bits = append(bits, bits_idx...)
}

bits_pre := abstractor.Call1(api, ToReducedBigEndian{Variable: circuit.PreRoot, Size: 256})
bits_pre := abstractor.Call1(api, ToBigEndian{Variable: circuit.PreRoot, Size: 256})
bits = append(bits, bits_pre...)

bits_post := abstractor.Call1(api, ToReducedBigEndian{Variable: circuit.PostRoot, Size: 256})
bits_post := abstractor.Call1(api, ToBigEndian{Variable: circuit.PostRoot, Size: 256})
bits = append(bits, bits_post...)

hash, err := keccak.Keccak256(api, bits)
Expand Down
182 changes: 142 additions & 40 deletions prover/insertion_circuit.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package prover

import (
"math"
"math/big"
"math/bits"

"github.com/consensys/gnark/std/math/emulated"

"worldcoin/gnark-mbu/prover/barycentric"
"worldcoin/gnark-mbu/prover/keccak"
"worldcoin/gnark-mbu/prover/poseidon"

"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark/frontend"
Expand All @@ -10,67 +18,161 @@ import (
)

type InsertionMbuCircuit struct {
// single public input
InputHash frontend.Variable `gnark:",public"`

// private inputs, but used as public inputs
StartIndex frontend.Variable `gnark:"input"`
PreRoot frontend.Variable `gnark:"input"`
PostRoot frontend.Variable `gnark:"input"`
IdComms []frontend.Variable `gnark:"input"`
// public inputs
InputHash frontend.Variable `gnark:",public"`
ExpectedEvaluation frontend.Variable `gnark:",public"`
Commitment4844 frontend.Variable `gnark:",public"`
StartIndex frontend.Variable `gnark:",public"`
PreRoot frontend.Variable `gnark:",public"`
PostRoot frontend.Variable `gnark:",public"`

// private inputs
IdComms []frontend.Variable `gnark:"input"`
MerkleProofs [][]frontend.Variable `gnark:"input"`

BatchSize int
Depth int
}

func (circuit *InsertionMbuCircuit) Define(api frontend.API) error {
// Hash private inputs.
// We keccak hash all input to save verification gas. Inputs are arranged as follows:
// StartIndex || PreRoot || PostRoot || IdComms[0] || IdComms[1] || ... || IdComms[batchSize-1]
// 32 || 256 || 256 || 256 || 256 || ... || 256 bits
var bits []frontend.Variable
// getMerkleTreeRoot calculates the Merkle Tree root repeatedly hashing pairs of elements in the input slice until only
// one element remains. This process effectively builds a binary tree of hashes, where each level of the tree is half
// the size of the level below it.
// At the end or the process the function returns the root value of such constructed Merkle Tree.
func getMerkleTreeRoot(api frontend.API, input []frontend.Variable) frontend.Variable {
temp := input[:]
for len(temp) > 1 {
newInput := make([]frontend.Variable, len(temp)/2)
for i := range newInput {
newInput[i] = abstractor.Call(
api, poseidon.Poseidon2{
In1: temp[2*i],
In2: temp[2*i+1],
},
)
}
temp = newInput
}
return temp[0]
}

// We convert all the inputs to the keccak hash to use big-endian (network) byte
// ordering so that it agrees with Solidity. This ensures that we don't have to
// perform the conversion inside the contract and hence save on gas.
bits_start := abstractor.Call1(api, ToReducedBigEndian{Variable: circuit.StartIndex, Size: 32})
bits = append(bits, bits_start...)
type Fr = emulated.BLS12381Fr

const polynomialDegree = 4096

func computeOmegaToI() (*big.Int, *big.Int) {
// This function assumes BLS12381Fr field and polynomial degree 4096
modulus, _ := new(big.Int).SetString(
"52435875175126190479447740508185965837690552500527637822603658699938581184513", 10,
)

// For polynomial degree d = 4096 = 2^12:
// ω^(2^32) = ω^(2^20 * 2^12)
// Calculate ω^20 starting with root of unity of 2^32 degree
omega, _ := new(big.Int).SetString(
"10238227357739495823651030575849232062558860180284477541189508159991286009131", 10,
)
polynomialDegreeExp := int(math.Log2(float64(polynomialDegree)))
omegaExpExp := 32 // ω^(2^32)
for range omegaExpExp - polynomialDegreeExp {
omega.Mul(omega, omega)
omega.Mod(omega, modulus)
}

bits_pre := abstractor.Call1(api, ToReducedBigEndian{Variable: circuit.PreRoot, Size: 256})
bits = append(bits, bits_pre...)
return omega, modulus
}

bits_post := abstractor.Call1(api, ToReducedBigEndian{Variable: circuit.PostRoot, Size: 256})
bits = append(bits, bits_post...)
// bitReversedIndex returns the bit-reversed index of the given index of the polynomial's
// slice of roots of unity.
//
// The function calculates the logarithm base 2 of the polynomial degree to determine
// the number of bits needed. Then, it reverses the bits of the input index and shifts
// the result to obtain the bit-reversed index.
func bitReversedIndex(idx int, polynomialDegree int) uint64 {
logOfDegree := int(math.Log2(float64(polynomialDegree)))
return bits.Reverse64(uint64(idx))>>(64-logOfDegree)
}

for i := 0; i < circuit.BatchSize; i++ {
bits_id := abstractor.Call1(api, ToReducedBigEndian{Variable: circuit.IdComms[i], Size: 256})
bits = append(bits, bits_id...)
func evaluatePolynomial(
api frontend.API, interpolatingPoints []frontend.Variable, pointOfEvaluation frontend.Variable,
) (evaluationValue frontend.Variable) {
startingOmega, _ := computeOmegaToI()
omegasToI := make([]emulated.Element[Fr], polynomialDegree)
omegaToI := big.NewInt(1)
for i := range polynomialDegree {
omegasToI[bitReversedIndex(i, polynomialDegree)] = emulated.ValueOf[Fr](omegaToI)
omegaToI.Mul(omegaToI, startingOmega)
}

hash, err := keccak.Keccak256(api, bits)
field, err := emulated.NewField[Fr](api)
if err != nil {
return err
}
sum := abstractor.Call(api, FromBinaryBigEndian{Variable: hash})

// The same endianness conversion has been performed in the hash generation
// externally, so we can safely assert their equality here.
api.AssertIsEqual(circuit.InputHash, sum)
x := *field.FromBits(api.ToBinary(pointOfEvaluation)...)
w := make([]emulated.Element[Fr], len(interpolatingPoints))
for i, p := range interpolatingPoints {
w[i] = *field.FromBits(api.ToBinary(p)...)
}
y := barycentric.CalculateBarycentricFormula(field, omegasToI, w, x)

yBits := field.ToBits(field.Reduce(&y))
bitWidth := Fr{}.NbLimbs() * Fr{}.BitsPerLimb()
evaluationValue = api.FromBinary(yBits[:bitWidth]...)
return
}

// Actual batch merkle proof verification.
root := abstractor.Call(api, InsertionProof{
StartIndex: circuit.StartIndex,
PreRoot: circuit.PreRoot,
IdComms: circuit.IdComms,
func (circuit *InsertionMbuCircuit) Define(api frontend.API) error {
paddedIdComms := make([]frontend.Variable, polynomialDegree)
copy(paddedIdComms, circuit.IdComms)
for i := len(circuit.IdComms); i < polynomialDegree; i++ {
paddedIdComms[i] = 0
}
rootHash := getMerkleTreeRoot(api, paddedIdComms)
api.AssertIsEqual(circuit.InputHash, rootHash)

MerkleProofs: circuit.MerkleProofs,
var bitsHashCommitment []frontend.Variable
// We convert all the inputs to the keccak hash to use big-endian (network) byte
// ordering so that it agrees with Solidity. This ensures that we don't have to
// perform the conversion inside the contract and hence save on gas.
bitsHash := abstractor.Call1(
api, ToBigEndian{
Variable: circuit.InputHash,
Size: 256,
},
)
bitsHashCommitment = append(bitsHashCommitment, bitsHash...)
bitsCommitment := abstractor.Call1(
api, ToBigEndian{
Variable: circuit.Commitment4844,
Size: 256,
},
)
bitsHashCommitment = append(bitsHashCommitment, bitsCommitment...)

// Compute Fiat-Shamir challenge of input hash and 4844 commitment
hash, err := keccak.Keccak256(api, bitsHashCommitment)
if err != nil {
return err
}
challenge := abstractor.Call(api, FromBinaryBigEndian{Variable: hash})

// Calculate evaluation of polynomial interpolated by identities in the point x=challenge
evaluation := evaluatePolynomial(api, paddedIdComms, challenge)
api.AssertIsEqual(circuit.ExpectedEvaluation, evaluation)

BatchSize: circuit.BatchSize,
Depth: circuit.Depth,
})
// Actual batch merkle proof verification.
root := abstractor.Call(
api, InsertionProof{
StartIndex: circuit.StartIndex,
PreRoot: circuit.PreRoot,
IdComms: circuit.IdComms,

MerkleProofs: circuit.MerkleProofs,

BatchSize: circuit.BatchSize,
Depth: circuit.Depth,
},
)

// Final root needs to match.
api.AssertIsEqual(root, circuit.PostRoot)
Expand Down
Loading

0 comments on commit e3341d2

Please sign in to comment.