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

Add an n to 1 MUX and MAP #475

Merged
merged 15 commits into from
Feb 20, 2023
Merged

Add an n to 1 MUX and MAP #475

merged 15 commits into from
Feb 20, 2023

Conversation

aybehrouz
Copy link
Contributor

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.

gbotrel and others added 4 commits February 7, 2023 12:24
This commit adds a simple n to 1 multiplexer to the std lib. This multiplexer is based on non-deterministic advice.
@ivokub
Copy link
Collaborator

ivokub commented Feb 17, 2023

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 const * log(n)) than a permutation network.

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 std/memory/mux etc). But I do not right now have a good suggestion. I'll think about and propose something so that different implementations would positioned logically.

@gbotrel gbotrel requested a review from ivokub February 17, 2023 16:33
@aybehrouz
Copy link
Contributor Author

I’m so glad this worked out. Happy to help.
Choosing names usually is a bit tricky. Maybe std/selectors?

Old tests were dependant to outputs and were not able to test invalid inputs correctly.
@aybehrouz
Copy link
Contributor Author

aybehrouz commented Feb 17, 2023

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?

@aybehrouz aybehrouz marked this pull request as draft February 18, 2023 09:47
@aybehrouz aybehrouz changed the title Add an n to 1 multiplexer Add an n to 1 multiplexer and a lookup table Feb 18, 2023
@aybehrouz
Copy link
Contributor Author

aybehrouz commented Feb 18, 2023

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 ProverFailed(..).

...and I'm not sure if I've chosen good names, feel free to suggest me new names 😅

@aybehrouz aybehrouz marked this pull request as ready for review February 18, 2023 16:58
A lookup table usually uses indices and not keys, so we renamed `Lookup` to `Map`. We also renamed the `gadgets` package to `selector`.
@aybehrouz aybehrouz changed the title Add an n to 1 multiplexer and a lookup table Add an n to 1 multiplexer (MUX) and a map Feb 19, 2023
@aybehrouz aybehrouz changed the title Add an n to 1 multiplexer (MUX) and a map Add an n to 1 MUX and MAP Feb 19, 2023
@ivokub
Copy link
Collaborator

ivokub commented Feb 20, 2023

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
+}

@ivokub
Copy link
Collaborator

ivokub commented Feb 20, 2023

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

Copy link
Collaborator

@ivokub ivokub left a 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.

@aybehrouz
Copy link
Contributor Author

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.

Copy link
Collaborator

@ivokub ivokub left a 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.

@aybehrouz
Copy link
Contributor Author

I fixed them in a new commit.

@ivokub ivokub merged commit daa9b14 into Consensys:develop Feb 20, 2023
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

Successfully merging this pull request may close these issues.

3 participants