Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify Circom proofs on Gnark recursivelly #1306

Open
p4u opened this issue Oct 30, 2024 · 15 comments
Open

Verify Circom proofs on Gnark recursivelly #1306

p4u opened this issue Oct 30, 2024 · 15 comments

Comments

@p4u
Copy link

p4u commented Oct 30, 2024

Hello.
I managed to create a Parser for Circom/SnarkJS proof, verifying key and inputs to Gnark. I do believe this is a nice to have feature because there is nothing like Circom for browsers (in terms of cmpatibility, memory consumption and so on). The idea behind this is to be able to aggregate Circom proofs using Gnark and the bn254 recursion.

The current status is that the Parser works and I can verify the Circom Proof using the groth16 Verifier from Gnark. That's great!

However, I'm struggling trying to build a second SNARK to prove recursively the Circom Proof (I have been following the example on std/recursion/groth16). So at this point I'm blocked and so asking for help.

Everything seems to work fine until the proof is generated, then after some time computing, it fails: 12:30:22 ERR error="no modular inverse" nbConstraints=3510919.

Since the non-recursive verification of the proof works, I do believe the issue is when formatting the types to the new recursion types. I have tried several ways to do it, but the result is always the same.

Any hint that helps me resolve this issue would be very welcome.

The code is messy and dirty, I'll package it correctly if I manage to make it work :)

https://github.com/p4u/circomgnark

(this log output is from the branch with_circuit_definition)

Public input 0: [1949230679015292902 16913946402569752895 5177146667339417225 1571765431670520771]
Public input 1: [12436184717236109307 3962172157175319849 7381016538464732718 1011752739694698287]
Public input 2: [7381856711771946919 429036917624397183 17012281127673022365 3251803507597988222]
Public input 3: [0 0 0 0]
Public input 4: [10272051228544329101 12328405056306261465 926657253105981457 1367149825311690890]
Public input 5: [1949230679015292902 16913946402569752895 5177146667339417225 1571765431670520771]
Public input 6: [6425625360762666998 7924344314350639699 14762033076929465436 2023505479389396574]
Public input 7: [0 0 0 0]
Public input 8: [0 0 0 0]
Public input 9: [12040111780922220025 4325807880699871267 13120311456592824604 2226031597383081337]
Public input 10: [11764515480738773313 4215923096526147436 7533939148934551966 91149140963873405]
Public input 11: [1965350006196174261 2171591664523914686 8082212424981722321 381458811606861544]
Public input 12: [13829608120566258243 13026078290154432408 4728977771231651952 3364579299652711593]
Public input 13: [12040111780922220025 4325807880699871267 13120311456592824604 2226031597383081337]
Public input 14: [11764515480738773313 4215923096526147436 7533939148934551966 91149140963873405]
Public input 15: [18442302885340442099 14061972045435770817 8549366572767765664 560350734708278544]
Public input 16: [17712226625198621984 6219651947140399819 8361883782466450530 3123854711584495659]
Public input 17: [12040111780922220025 4325807880699871267 13120311456592824604 2226031597383081337]
Public input 18: [11764515480738773313 4215923096526147436 7533939148934551966 91149140963873405]
Public input 19: [10292018168060981093 16580195074090224483 13485807706758543224 934225353760603390]
Public input 20: [8543142694313795891 16228910262411961620 4162896216055972041 579772761061470113]
Public input 21: [12040111780922220025 4325807880699871267 13120311456592824604 2226031597383081337]
Public input 22: [11764515480738773313 4215923096526147436 7533939148934551966 91149140963873405]
Public input 23: [11877705370643086064 15945885667828877581 13817082923595099113 2285467444859142942]
Public input 24: [4061656487843108707 4359513650293369008 8126370224395253529 1315541459449750593]
Public input 25: [12040111780922220025 4325807880699871267 13120311456592824604 2226031597383081337]
Public input 26: [11764515480738773313 4215923096526147436 7533939148934551966 91149140963873405]
Public input 27: [9245887466604383170 7185355432707463144 14435458581108120669 3397371439800124547]
Public input 28: [10448819902323371563 1355755425742092714 15145846332919553397 1985166372601279576]
Public input 29: [0 0 0 0]
Public input 30: [0 0 0 0]
Public input 31: [0 0 0 0]
Public input 32: [0 0 0 0]
Public input 33: [0 0 0 0]
Public input 34: [0 0 0 0]
Public input 35: [0 0 0 0]
Public input 36: [0 0 0 0]
Public input 37: [0 0 0 0]
Public input 38: [0 0 0 0]
Public input 39: [0 0 0 0]
Public input 40: [0 0 0 0]
Public input 41: [2555425411133916779 13777352117729142758 8238930184915962037 1555101545257120607]
Converting proof...
Verifying proof outside the recursive circuit with 42 public inputs...
12:25:24 DBG verifier done backend=groth16 curve=bn254 took=0.917615
Proof is valid!
12:25:24 INF compiling circuit
12:25:24 INF parsed circuit inputs nbPublic=42 nbSecret=0
12:25:24 INF building constraint builder nbConstraints=0
Public input 0: [1949230679015292902 16913946402569752895 5177146667339417225 1571765431670520771] / 5
Public input 1: [12436184717236109307 3962172157175319849 7381016538464732718 1011752739694698287] / 1
Public input 2: [7381856711771946919 429036917624397183 17012281127673022365 3251803507597988222] / 17
Public input 3: [0 0 0 0] / 0
Public input 4: [10272051228544329101 12328405056306261465 926657253105981457 1367149825311690890] / 1280
Public input 5: [1949230679015292902 16913946402569752895 5177146667339417225 1571765431670520771] / 5
Public input 6: [6425625360762666998 7924344314350639699 14762033076929465436 2023505479389396574] / 2
Public input 7: [0 0 0 0] / 0
Public input 8: [0 0 0 0] / 0
Public input 9: [12040111780922220025 4325807880699871267 13120311456592824604 2226031597383081337] / 13925320212206982232428640707676195363704145127946783541219937817294847414241
Public input 10: [11764515480738773313 4215923096526147436 7533939148934551966 91149140963873405] / 3546267591220393084162659211991112887948363009687628092411479274393877892114
Public input 11: [1965350006196174261 2171591664523914686 8082212424981722321 381458811606861544] / 4838662003912240974174491450254760980882584869227624734322613078574548635813
Public input 12: [13829608120566258243 13026078290154432408 4728977771231651952 3364579299652711593] / 5982078806884560335317636424046893239638928868423409750154898658660066776616
Public input 13: [12040111780922220025 4325807880699871267 13120311456592824604 2226031597383081337] / 13925320212206982232428640707676195363704145127946783541219937817294847414241
Public input 14: [11764515480738773313 4215923096526147436 7533939148934551966 91149140963873405] / 3546267591220393084162659211991112887948363009687628092411479274393877892114
Public input 15: [18442302885340442099 14061972045435770817 8549366572767765664 560350734708278544] / 4393030701815580500490847178293266328597776841775736034700102934230314604508
Public input 16: [17712226625198621984 6219651947140399819 8361883782466450530 3123854711584495659] / 747535843803927955345627059496038641624950254133015743694032228842677853146
Public input 17: [12040111780922220025 4325807880699871267 13120311456592824604 2226031597383081337] / 13925320212206982232428640707676195363704145127946783541219937817294847414241
Public input 18: [11764515480738773313 4215923096526147436 7533939148934551966 91149140963873405] / 3546267591220393084162659211991112887948363009687628092411479274393877892114
Public input 19: [10292018168060981093 16580195074090224483 13485807706758543224 934225353760603390] / 16705034546293862874357733271077818938744882061666829456239295149893939449703
Public input 20: [8543142694313795891 16228910262411961620 4162896216055972041 579772761061470113] / 14175919288381594604903670263282587485243287281356860423601956045737746332936
Public input 21: [12040111780922220025 4325807880699871267 13120311456592824604 2226031597383081337] / 13925320212206982232428640707676195363704145127946783541219937817294847414241
Public input 22: [11764515480738773313 4215923096526147436 7533939148934551966 91149140963873405] / 3546267591220393084162659211991112887948363009687628092411479274393877892114
Public input 23: [11877705370643086064 15945885667828877581 13817082923595099113 2285467444859142942] / 9082071685869338530708388864045580880848449748066341270208976905705702512195
Public input 24: [4061656487843108707 4359513650293369008 8126370224395253529 1315541459449750593] / 3305799440892288903442706075777330563887469639405549809586456297896174789603
Public input 25: [12040111780922220025 4325807880699871267 13120311456592824604 2226031597383081337] / 13925320212206982232428640707676195363704145127946783541219937817294847414241
Public input 26: [11764515480738773313 4215923096526147436 7533939148934551966 91149140963873405] / 3546267591220393084162659211991112887948363009687628092411479274393877892114
Public input 27: [9245887466604383170 7185355432707463144 14435458581108120669 3397371439800124547] / 3858617418519233491543638944351419075612779593770539143351294424414613861001
Public input 28: [10448819902323371563 1355755425742092714 15145846332919553397 1985166372601279576] / 13006702209958354218038867933538999229292219653526211593034262546828228805063
Public input 29: [0 0 0 0] / 0
Public input 30: [0 0 0 0] / 0
Public input 31: [0 0 0 0] / 0
Public input 32: [0 0 0 0] / 0
Public input 33: [0 0 0 0] / 0
Public input 34: [0 0 0 0] / 0
Public input 35: [0 0 0 0] / 0
Public input 36: [0 0 0 0] / 0
Public input 37: [0 0 0 0] / 0
Public input 38: [0 0 0 0] / 0
Public input 39: [0 0 0 0] / 0
Public input 40: [0 0 0 0] / 0
Public input 41: [2555425411133916779 13777352117729142758 8238930184915962037 1555101545257120607] / 5630210813752003343528524591438274147462060549360432321983578688556994400044
Compiling circuit...
12:25:24 INF compiling circuit
12:25:24 INF parsed circuit inputs nbPublic=168 nbSecret=464
12:25:35 INF building constraint builder nbConstraints=3510919
Compilation time: 11.478919857s
Setting up proving and verifying keys...
Setup time: 4m17.603151623s
Wrote proving key to pk.bin
Wrote verifying key to vk.bin
Wrote circuit to circuit.r1cs
Transforming proof and verification key to recursion types...
Placeholder VK G1.K length: 43
Actual VK G1.K length: 43
Placeholder Witness Public length: 42
Actual Witness Public length: 42
Creating witness...
Witness creation time: 385.911µs
Proving...
12:30:22 ERR error="no modular inverse" nbConstraints=3510919
Proving failed: no modular inverse
@ivokub
Copy link
Collaborator

ivokub commented Oct 30, 2024

This is super cool!

I see that some of the public inputs are zero. For efficiency, we by default use the in-complete formulas when computing a multi-scalar multiplication and zero as a public input would hit the edge case.

To force using complete formulas, you can provide the groth16.WithCompleteArithmetic() verification option. In your example you should do:

@@ -416,5 +430,5 @@ func (c *VerifyCircomProofCircuit) Define(api frontend.API) error {
        if err != nil {
                return fmt.Errorf("new verifier: %w", err)
        }
-       return verifier.AssertProof(c.VerifyingKey, c.Proof, c.PublicInputs)
+       return verifier.AssertProof(c.VerifyingKey, c.Proof, c.PublicInputs, stdgroth16.WithCompleteArithmetic())
 }

By the way - we also have the "test engine" which does not create a proof, but instead evaluates the circuit using *big.Int and checks the in-circuit assertions. This is allows for faster debugging of the circuit without needing to set up the keys etc. For example:

@@ -13,6 +13,7 @@ import (
        "github.com/consensys/gnark/frontend"
        "github.com/consensys/gnark/frontend/cs/r1cs"
        "github.com/consensys/gnark/logger"
+       "github.com/consensys/gnark/test"
        "github.com/rs/zerolog"

        "github.com/consensys/gnark/std/algebra/emulated/sw_bn254"
@@ -229,6 +230,19 @@ func main() {
        var pk groth16.ProvingKey
        var vk groth16.VerifyingKey

+       // Create the circuit assignment with actual values
+       circuitAssignment := &VerifyCircomProofCircuit{
+               Proof:        recursionProof,
+               VerifyingKey: recursionVk,
+               PublicInputs: recursionPublicInputs,
+       }
+
+       err = test.IsSolved(placeholderCircuit, circuitAssignment, ecc.BN254.ScalarField())
+       if err != nil {
+               panic(err)
+       }
+       return
+

And it seems that your example works.

@p4u
Copy link
Author

p4u commented Oct 30, 2024

@ivokub thanks! It works now!! It only took 14s to prove, with 4,772,531 constraints (the Circom circuit has around 35k and 42 public signals) on my laptop (AMD Ryzen 7 7840U + 64 GiB). That's very outstanding.

I'll package it correctly and provide here the link for the final repository, so other ppl can take advantage of it.

@ivokub
Copy link
Collaborator

ivokub commented Oct 30, 2024

That would be great. I'll see if I could incorporate as an example also in gnark, it has been asked several times.

Meanwhile, I'm also starting to prepare an example how to reduce the number of public inputs, so instead of providing all 42 public inputs you could only provide a single public input H(input1, ..., input42) and then everything else as a private input. Then in circuit you could check that the hashed input corresponds to the input1,.... I think currently the circuit size is dominated by a large MSM and this way it would boil down to a single scalarmul. It could potentially reduce the outer circuit size considerably, making the proving time even faster.

And this is in the end really useful for Solidity verifier also to reduce the calldata.

@p4u
Copy link
Author

p4u commented Oct 31, 2024

Ok, I've cleaned up and refactored the code. Here is the final repository. https://github.com/vocdoni/circom2gnark
There are still some functions from the parser that can be improved, but nothing important as far as I understand.

@ivokub feel free to send a PR with improvements or make any comment you like.

That would be great. I'll see if I could incorporate as an example also in gnark, it has been asked several times.

Sure, let me know if I can help.

Meanwhile, I'm also starting to prepare an example how to reduce the number of public inputs, so instead of providing all 42 public inputs you could only provide a single public input H(input1, ..., input42) and then everything else as a private input. Then in circuit you could check that the hashed input corresponds to the input1,.... I think currently the circuit size is dominated by a large MSM and this way it would boil down to a single scalarmul. It could potentially reduce the outer circuit size considerably, making the proving time even faster.

That makes sense, I've seen this technique used many times. Please notify me if you implement the example, so I incorporate it.

@ivokub
Copy link
Collaborator

ivokub commented Nov 4, 2024

Ok, I've cleaned up and refactored the code. Here is the final repository. https://github.com/vocdoni/circom2gnark There are still some functions from the parser that can be improved, but nothing important as far as I understand.

@ivokub feel free to send a PR with improvements or make any comment you like.

That would be great. I'll see if I could incorporate as an example also in gnark, it has been asked several times.

Sure, let me know if I can help.

Meanwhile, I'm also starting to prepare an example how to reduce the number of public inputs, so instead of providing all 42 public inputs you could only provide a single public input H(input1, ..., input42) and then everything else as a private input. Then in circuit you could check that the hashed input corresponds to the input1,.... I think currently the circuit size is dominated by a large MSM and this way it would boil down to a single scalarmul. It could potentially reduce the outer circuit size considerably, making the proving time even faster.

That makes sense, I've seen this technique used many times. Please notify me if you implement the example, so I incorporate it.

Thanks. I'll be traveling, but will have a look soon.

Meanwhile, I added an example on how to use the hash of the public inputs to reduce the recursive verification cost. See #1311. I'll also think about if I should add another example how it would look in a recursive circuit.

@p4u
Copy link
Author

p4u commented Nov 6, 2024

I am not sure, but I might have located a little issue on the Placeolder helpers.

While doing another example, this time using native recursion (bls12-377 + bw6-761) to create a second SNARK that aggregate several proofs previously recursively transformed from bn254 (circom) to bls12-377 Gnark.

When trying to generate the proof on the bw6-716 curve to aggregate 10 bls12-377 proofs, I receive the error:

2024/11/06 14:03:51 Failed to test solve circuit: define: assert proof: invalid number of commitments, got 1, expected 0

So I have printed the number of commitment keys and got the following:

Number of commitments Vk: 1
Number of commitments Vk placeholder: 0
Number of commitments Proof: 1
Number of commitments Proof placeholder: 1

The placeholder Vk is created using stdgroth16.PlaceholderVerifyingKey[sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT](ccs)

I tried to manually modify the placeholder Vk with: placeholderVk.G1.K = make([]sw_bls12377.G1Affine, len(placeholderVk.G1.K)). Then it becomes 1, but got blocked on the second check:which compares PublicAndCommitmentCommited: invalid number of commitment keys, got 1, expected 0

I can keep trying to modifying manually the Vk, but it does not feel good...

I can perfectly verify all bls12-377 independent proofs with the standard groth16.Verify() function.

Am I doing something wrong or there is something weird happening with the bw6 recursion?

@p4u
Copy link
Author

p4u commented Nov 11, 2024

I'm very stuck here. I would really appreciate some help (@ivokub)

What I'm trying to achieve is a native recursion, so aggregating circom proofs should be faster.
bn254/circom ---emulated---> bls12377/gnark ----native---> bw6_761/gnark

The new example code I'm building can be found here: https://github.com/vocdoni/circom2gnark/tree/main/example_native_aggregation

I'm trying to do the things exactly as the native example, but for some reason the proof generation fails.

@ivokub
Copy link
Collaborator

ivokub commented Nov 11, 2024

I'm looking into

I'm very stuck here. I would really appreciate some help (@ivokub)

What I'm trying to achieve is a native recursion, so aggregating circom proofs should be faster. bn254/circom ---emulated---> bls12377/gnark ----native---> bw6_761/gnark

The new example code I'm building can be found here: https://github.com/vocdoni/circom2gnark/tree/main/example_native_aggregation

I'm trying to do the things exactly as the native example, but for some reason the proof generation fails.

Sorry for the late reply - I have been at Devcon.

It took me some time to debug, but I found it. I think our documentation is not super clean, we wrote it before we had the support for commitments in Groth16 recursion. So, the issue is that when we have a commitment, then we need to hash it and instead of the default hash function when we don't use recursion, we use in circuit different hash function which is snark friendly. But we need to define it at proving time.

So, with the following modification:

diff --git a/example_native_aggregation/circomproofs.go b/example_native_aggregation/circomproofs.go
index d4a25c1..3d274c8 100644
--- a/example_native_aggregation/circomproofs.go
+++ b/example_native_aggregation/circomproofs.go
@@ -11,6 +11,7 @@ import (
        "github.com/consensys/gnark/backend/witness"
        "github.com/consensys/gnark/constraint"
        "github.com/consensys/gnark/frontend"
+       stdgroth16 "github.com/consensys/gnark/std/recursion/groth16"
        "github.com/consensys/gnark/test"
        "github.com/vocdoni/circom2gnark/parser"
 )
@@ -122,7 +123,7 @@ func circom2gnarkRecursiveBls12377(proofData, vkData, publicSignalsData []byte,
        // Create the proof
        fmt.Println("Generating a recursive proof BLS12-377 of an independent Circom proof...")
        startTime := time.Now()
-       proof, err := groth16.Prove(ccs, pk, witnessFull)
+       proof, err := groth16.Prove(ccs, pk, witnessFull, stdgroth16.GetNativeProverOptions(ecc.BW6_761.ScalarField(), ecc.BLS12_377.ScalarField()))
        if err != nil {
                log.Fatalf("Failed to create proof: %v", err)
        }
@@ -131,7 +132,7 @@ func circom2gnarkRecursiveBls12377(proofData, vkData, publicSignalsData []byte,
        // Verify the proof
        fmt.Println("Verifying...")
        startTime = time.Now()
-       err = groth16.Verify(proof, vk, publicWitness)
+       err = groth16.Verify(proof, vk, publicWitness, stdgroth16.GetNativeVerifierOptions(ecc.BW6_761.ScalarField(), ecc.BLS12_377.ScalarField()))
        if err != nil {
                log.Fatalf("Failed to verify proof: %v", err)
        }

I tried it with a smaller inner circuit, but I think you should be able to reproduce for the big circuit also. Let me know if it works or not, and I'll have another look.

Another thing what I would recommend - for making proving key reading faster you can use the gnarkio.BinaryDumper interfaces which are faster. See https://pkg.go.dev/github.com/consensys/[email protected]/backend/groth16#ProvingKey

p4u added a commit to vocdoni/circom2gnark that referenced this issue Nov 11, 2024
@p4u
Copy link
Author

p4u commented Nov 11, 2024

I'm looking into

I'm very stuck here. I would really appreciate some help (@ivokub)
What I'm trying to achieve is a native recursion, so aggregating circom proofs should be faster. bn254/circom ---emulated---> bls12377/gnark ----native---> bw6_761/gnark
The new example code I'm building can be found here: https://github.com/vocdoni/circom2gnark/tree/main/example_native_aggregation
I'm trying to do the things exactly as the native example, but for some reason the proof generation fails.

Sorry for the late reply - I have been at Devcon.

It took me some time to debug, but I found it. I think our documentation is not super clean, we wrote it before we had the support for commitments in Groth16 recursion. So, the issue is that when we have a commitment, then we need to hash it and instead of the default hash function when we don't use recursion, we use in circuit different hash function which is snark friendly. But we need to define it at proving time.

So, with the following modification:

diff --git a/example_native_aggregation/circomproofs.go b/example_native_aggregation/circomproofs.go
index d4a25c1..3d274c8 100644
--- a/example_native_aggregation/circomproofs.go
+++ b/example_native_aggregation/circomproofs.go
@@ -11,6 +11,7 @@ import (
        "github.com/consensys/gnark/backend/witness"
        "github.com/consensys/gnark/constraint"
        "github.com/consensys/gnark/frontend"
+       stdgroth16 "github.com/consensys/gnark/std/recursion/groth16"
        "github.com/consensys/gnark/test"
        "github.com/vocdoni/circom2gnark/parser"
 )
@@ -122,7 +123,7 @@ func circom2gnarkRecursiveBls12377(proofData, vkData, publicSignalsData []byte,
        // Create the proof
        fmt.Println("Generating a recursive proof BLS12-377 of an independent Circom proof...")
        startTime := time.Now()
-       proof, err := groth16.Prove(ccs, pk, witnessFull)
+       proof, err := groth16.Prove(ccs, pk, witnessFull, stdgroth16.GetNativeProverOptions(ecc.BW6_761.ScalarField(), ecc.BLS12_377.ScalarField()))
        if err != nil {
                log.Fatalf("Failed to create proof: %v", err)
        }
@@ -131,7 +132,7 @@ func circom2gnarkRecursiveBls12377(proofData, vkData, publicSignalsData []byte,
        // Verify the proof
        fmt.Println("Verifying...")
        startTime = time.Now()
-       err = groth16.Verify(proof, vk, publicWitness)
+       err = groth16.Verify(proof, vk, publicWitness, stdgroth16.GetNativeVerifierOptions(ecc.BW6_761.ScalarField(), ecc.BLS12_377.ScalarField()))
        if err != nil {
                log.Fatalf("Failed to verify proof: %v", err)
        }

I tried it with a smaller inner circuit, but I think you should be able to reproduce for the big circuit also. Let me know if it works or not, and I'll have another look.

Another thing what I would recommend - for making proving key reading faster you can use the gnarkio.BinaryDumper interfaces which are faster. See https://pkg.go.dev/github.com/consensys/[email protected]/backend/groth16#ProvingKey

Ok, it works now! Amazing @ivokub
I can aggregate 10 circom proofs (already transformed to bls12-377) in 0.4 seconds (400k constraints). This is pretty good.

Thanks for your help! I hope this digging on recursion can be useful to improve gnark documentation.

@p4u
Copy link
Author

p4u commented Nov 12, 2024

Hey @ivokub hope you are enjoying devcon, good luck with your talk tomorrow. There is no rush to reply, take your time.

I'm implementing the last part of my example, which is going back from bw6-761 to bn254, so the final aggregation of Circom proofs is Ethereum friendly and can be verified onchain.

To this end I've impemented a new circuit:

// AggregateProofCircuitBN254 is the circuit that verifies the proof aggregation using BN254 curve
type AggregateProofCircuitBN254 struct {
	Proof        stdgroth16.Proof[sw_bw6761.G1Affine, sw_bw6761.G2Affine]
	VerifyingKey stdgroth16.VerifyingKey[sw_bw6761.G1Affine, sw_bw6761.G2Affine, sw_bw6761.GTEl]
	PublicInputs stdgroth16.Witness[sw_bw6761.ScalarField] `gnark:",public"`
}

func (c *AggregateProofCircuitBN254) Define(api frontend.API) error {
	verifier, err := stdgroth16.NewVerifier[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine, sw_bw6761.GTEl](api)
	if err != nil {
		return fmt.Errorf("new verifier: %w", err)
	}
	return verifier.AssertProof(c.VerifyingKey, c.Proof, c.PublicInputs)
}

And so, I'm trying to generate a proof of emulated bw6761 within bn254. But no luck again.
23:42:35 ERR error="no modular inverse" nbConstraints=10859962
And the test also panics.

For testing, I tried to switch the native recursion curves to BLS24-315 and BW6-633 instead, but there is no emulated algebra for bw6-633 for some reason.

I've pushed the new code https://github.com/vocdoni/circom2gnark/blob/main/example_native_aggregation/aggregatebn254.go

Again, any help is welcome. Thank you.

EDIT

Forget it, works! Again, stdgroth16.WithCompleteArithmetic() was missing.

However, making this last step, heavily slows down the process. There are 96 public inputs, so I expect that using the Hash(publicInputs) will reduce the number of constraints. But still, do you see a way to optimize the process a little more?

@p4u
Copy link
Author

p4u commented Nov 14, 2024

I’m hitting a problem again @ivokub, when trying to make the Verifying Key (VK) a constant in the bw6761 aggregation circuit. Setting the VK as a fixed constant causes the circuit to fail with this error:

failed to solve circuit: define: assert proof: invalid number of commitment keys, got 1, expected 0

This is confusing because I wrote a simplified test, vk_test.go, and it works fine there.

Here's the commit that introduced the change and caused the crash:
vocdoni/circom2gnark@a3ec908

Any help is welcome and thanks in advance.

@ivokub
Copy link
Collaborator

ivokub commented Nov 15, 2024

EDIT

Forget it, works! Again, stdgroth16.WithCompleteArithmetic() was missing.

However, making this last step, heavily slows down the process. There are 96 public inputs, so I expect that using the Hash(publicInputs) will reduce the number of constraints. But still, do you see a way to optimize the process a little more?

I think when you would use the Hash(publicInputs) approach, then you should be able to significantly improve the performance. Essentially the recursion complexity depends on the number of public inputs, so we have to do 96 input MSM right now which requires quite a lot of constraints. Additionally, when you perform the hashing then you should be able to drop the WithCompleteArithmetic() option as the hash output should be random so that we wouldn't hit the edge cases.

@ivokub
Copy link
Collaborator

ivokub commented Nov 15, 2024

I’m hitting a problem again @ivokub, when trying to make the Verifying Key (VK) a constant in the bw6761 aggregation circuit. Setting the VK as a fixed constant causes the circuit to fail with this error:

failed to solve circuit: define: assert proof: invalid number of commitment keys, got 1, expected 0

This is confusing because I wrote a simplified test, vk_test.go, and it works fine there.

Here's the commit that introduced the change and caused the crash: vocdoni/circom2gnark@a3ec908

Any help is welcome and thanks in advance.

I started debugging. It may seem there is a bug but I need to dig a bit deeper. I think the issue may be that we can essentially have a commitment of the public inputs and private inputs. When we have a commitment over public inputs, then actually only the verifier computes the commitment value (as a hash of the public committed values), but when we have a commitment over private inputs, then the prover computes it and gives a POK of correctness.

I'll dig a bit deeper and let you know more.

@ivokub
Copy link
Collaborator

ivokub commented Nov 16, 2024

I’m hitting a problem again @ivokub, when trying to make the Verifying Key (VK) a constant in the bw6761 aggregation circuit. Setting the VK as a fixed constant causes the circuit to fail with this error:

failed to solve circuit: define: assert proof: invalid number of commitment keys, got 1, expected 0

This is confusing because I wrote a simplified test, vk_test.go, and it works fine there.
Here's the commit that introduced the change and caused the crash: vocdoni/circom2gnark@a3ec908
Any help is welcome and thanks in advance.

I started debugging. It may seem there is a bug but I need to dig a bit deeper. I think the issue may be that we can essentially have a commitment of the public inputs and private inputs. When we have a commitment over public inputs, then actually only the verifier computes the commitment value (as a hash of the public committed values), but when we have a commitment over private inputs, then the prover computes it and gives a POK of correctness.

I'll dig a bit deeper and let you know more.

Indeed there was a bug. See #1317

Meanwhile before it is merged you can use the tagged version with go get github.com/consensys/gnark@38377c28b5560641416493ae4b915c89fb109370

@p4u
Copy link
Author

p4u commented Nov 18, 2024

It works!
So I've finished the example to aggregate circom proofs, which makes native recursion and ends with a bn254 (ethereum compatible) proof. Using fixed Verifying Key and Hashing the inputs to reduce constraints.

These are the numbers for aggregating 100 circom proofs, divided into 3 circuits on an AMD Ryzen 9 7950X3D 16-Core with 128 GiB

  1. circom bn254 to gnark bls12377: 1,432,832 constraints (4s) per proof
  2. native aggregation bls12377 to bw6761: 4,105,543 constraints (23s)
  3. back from bw6761 to bn254: 6,947,832 constraints (11s)

The total time to aggregate 100 Circom proofs is 7.2 minutes, on a $100/month machine.

I think this is a powerful tool. We will use it to build our new decentralized voting infrastructure. I hope the parser and the example helps others too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants