-
Notifications
You must be signed in to change notification settings - Fork 370
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
Add an n to 1 MUX and MAP #475
Conversation
v0.8.0 placeholder
This commit adds a simple n to 1 multiplexer to the std lib. This multiplexer is based on non-deterministic advice.
It is definitely useful! We are seeing actual need for different ways for doing lookups and handling memory and muxer is a feasible approach. There is #388 which uses permutation network, but it is far more complex and for small number of queries a muxer is more efficient (up to The code seems good and well tested and I do not see any problems merging it. I think maybe it would be better to move it into a separate package though (something like |
I’m so glad this worked out. Happy to help. |
Old tests were dependant to outputs and were not able to test invalid inputs correctly.
I tried to add some tests and now the test fails! It seems that while the solver is failing, the prover does not produce any errors (for two curves I think). Is that normal? |
I also added a key value lookup table. The problem with tests resolved, after I ignored the output of the component instead of adding a trivial multiplication to zero constraint. However, I still suspect that there is a bug somewhere, maybe in ...and I'm not sure if I've chosen good names, feel free to suggest me new names 😅 |
A lookup table usually uses indices and not keys, so we renamed `Lookup` to `Map`. We also renamed the `gadgets` package to `selector`.
Suggested edit: diff --git a/std/selector/doc_map_test.go b/std/selector/doc_map_test.go
new file mode 100644
index 00000000..8d2fc91a
--- /dev/null
+++ b/std/selector/doc_map_test.go
@@ -0,0 +1,79 @@
+package selector_test
+
+import (
+ "fmt"
+
+ "github.com/consensys/gnark-crypto/ecc"
+ "github.com/consensys/gnark/backend/groth16"
+ "github.com/consensys/gnark/frontend"
+ "github.com/consensys/gnark/frontend/cs/r1cs"
+ "github.com/consensys/gnark/std/selector"
+)
+
+// MapCircuit is a minimal circuit using a selector map.
+type MapCircuit struct {
+ QueryKey frontend.Variable
+ Keys [10]frontend.Variable // we use array in witness to allocate sufficiently sized vector
+ Values [10]frontend.Variable // we use array in witness to allocate sufficiently sized vector
+ ExpectedValue frontend.Variable
+}
+
+// Define defines the arithmetic circuit.
+func (c *MapCircuit) Define(api frontend.API) error {
+ result := selector.Map(api, c.QueryKey, c.Keys[:], c.Values[:]) // Note Mux takes var-arg input, need to expand the input vector
+ api.AssertIsEqual(result, c.ExpectedValue)
+ return nil
+}
+
+// ExampleMap gives an example on how to use map selector.
+func ExampleMap() {
+ circuit := MapCircuit{}
+ witness := MapCircuit{
+ QueryKey: 55,
+ Keys: [10]frontend.Variable{0, 11, 22, 33, 44, 55, 66, 77, 88, 99},
+ Values: [10]frontend.Variable{0, 2, 4, 6, 8, 10, 12, 14, 16, 18},
+ ExpectedValue: 10, // element in values which corresponds to the position of 55 in keys
+ }
+ ccs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit)
+ if err != nil {
+ panic(err)
+ } else {
+ fmt.Println("compiled")
+ }
+ pk, vk, err := groth16.Setup(ccs)
+ if err != nil {
+ panic(err)
+ } else {
+ fmt.Println("setup done")
+ }
+ secretWitness, err := frontend.NewWitness(&witness, ecc.BN254.ScalarField())
+ if err != nil {
+ panic(err)
+ } else {
+ fmt.Println("secret witness")
+ }
+ publicWitness, err := secretWitness.Public()
+ if err != nil {
+ panic(err)
+ } else {
+ fmt.Println("public witness")
+ }
+ proof, err := groth16.Prove(ccs, pk, secretWitness)
+ if err != nil {
+ panic(err)
+ } else {
+ fmt.Println("proof")
+ }
+ err = groth16.Verify(proof, vk, publicWitness)
+ if err != nil {
+ panic(err)
+ } else {
+ fmt.Println("verify")
+ }
+ // Output: compiled
+ // setup done
+ // secret witness
+ // public witness
+ // proof
+ // verify
+}
diff --git a/std/selector/doc_mux_test.go b/std/selector/doc_mux_test.go
new file mode 100644
index 00000000..af47e677
--- /dev/null
+++ b/std/selector/doc_mux_test.go
@@ -0,0 +1,77 @@
+package selector_test
+
+import (
+ "fmt"
+
+ "github.com/consensys/gnark-crypto/ecc"
+ "github.com/consensys/gnark/backend/groth16"
+ "github.com/consensys/gnark/frontend"
+ "github.com/consensys/gnark/frontend/cs/r1cs"
+ "github.com/consensys/gnark/std/selector"
+)
+
+// MuxCircuit is a minimal circuit using a selector mux.
+type MuxCircuit struct {
+ Selector frontend.Variable
+ In [10]frontend.Variable // we use array in witness to allocate sufficiently sized vector
+ Expected frontend.Variable
+}
+
+// Define defines the arithmetic circuit.
+func (c *MuxCircuit) Define(api frontend.API) error {
+ result := selector.Mux(api, c.Selector, c.In[:]...) // Note Mux takes var-arg input, need to expand the input vector
+ api.AssertIsEqual(result, c.Expected)
+ return nil
+}
+
+// ExampleMux gives an example on how to use mux selector.
+func ExampleMux() {
+ circuit := MuxCircuit{}
+ witness := MuxCircuit{
+ Selector: 5,
+ In: [10]frontend.Variable{0, 2, 4, 6, 8, 10, 12, 14, 16, 18},
+ Expected: 10, // 5-th elemen in vector
+ }
+ ccs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit)
+ if err != nil {
+ panic(err)
+ } else {
+ fmt.Println("compiled")
+ }
+ pk, vk, err := groth16.Setup(ccs)
+ if err != nil {
+ panic(err)
+ } else {
+ fmt.Println("setup done")
+ }
+ secretWitness, err := frontend.NewWitness(&witness, ecc.BN254.ScalarField())
+ if err != nil {
+ panic(err)
+ } else {
+ fmt.Println("secret witness")
+ }
+ publicWitness, err := secretWitness.Public()
+ if err != nil {
+ panic(err)
+ } else {
+ fmt.Println("public witness")
+ }
+ proof, err := groth16.Prove(ccs, pk, secretWitness)
+ if err != nil {
+ panic(err)
+ } else {
+ fmt.Println("proof")
+ }
+ err = groth16.Verify(proof, vk, publicWitness)
+ if err != nil {
+ panic(err)
+ } else {
+ fmt.Println("verify")
+ }
+ // Output: compiled
+ // setup done
+ // secret witness
+ // public witness
+ // proof
+ // verify
+}
|
Suggested edit: diff --git a/std/selector/multiplexer.go b/std/selector/multiplexer.go
index 89a19a6b..de71dfe9 100644
--- a/std/selector/multiplexer.go
+++ b/std/selector/multiplexer.go
@@ -1,3 +1,15 @@
+// Package selector provides a lookup table and map based on linear scan.
+//
+// The native [frontend.API] provides 1- and 2-bit lookups through the interface
+// methods Select and Lookup2. This package extends the lookups to
+// arbitrary-sized vectors. The lookups can be performed using the index of the
+// elements (function [Mux]) or using a key, for which the user needs to provide
+// the slice of keys (function [Map]).
+//
+// The implementation uses linear scan over all inputs, so the constraint count
+// for every invocation of the function is C*len(values)+1, where:
+// - for R1CS, C = 3
+// - for PLONK, C = 5
package selector
import (
@@ -70,6 +82,8 @@ func generateSelector(api frontend.API, wantMux bool, sel frontend.Variable,
return out
}
+// MuxIndicator is a hint function used within [Mux] function. It must be
+// provided to the prover when circuit uses it.
func MuxIndicators(_ *big.Int, inputs []*big.Int, results []*big.Int) error {
sel := inputs[0]
for i := 0; i < len(results); i++ {
@@ -84,6 +98,8 @@ func MuxIndicators(_ *big.Int, inputs []*big.Int, results []*big.Int) error {
return nil
}
+// MapIndicators is a hint function used within [Map] function. It must be
+// provided to the prover when circuit uses it.
func MapIndicators(_ *big.Int, inputs []*big.Int, results []*big.Int) error {
key := inputs[len(inputs)-1]
// We must make sure that we are initializing all elements of results
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise than the few documentation changes, I think the submission is great! Could you have a look if those make sense.
I think the name std/selector
is perfect for the gadget.
Thank you so much. All the suggested changes look great. I've just spotted a few unimportant typos. If you commit the changes, I think, on Github I can mark where they are. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my side good to go. I'll incorporate the fixes to the typos before merging.
I fixed them in a new commit. |
I was experimenting with the library and I wrote a simple multiplexer. Then, I thought it might come in useful, so I made a pull request. I don't know if you need that or not and I was too lazy to make an issue first. If it is not useful feel free to tell me to close the pull request.