From 2753ae60ed8f6de4fc793d4ec3cbcb9291d52236 Mon Sep 17 00:00:00 2001 From: Gautam Botrel Date: Tue, 14 Feb 2023 14:33:34 -0600 Subject: [PATCH 01/12] feat: update gnark version to v0.8.0 --- doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc.go b/doc.go index 84ae2e9545..697944b7d7 100644 --- a/doc.go +++ b/doc.go @@ -22,7 +22,7 @@ import ( "github.com/consensys/gnark-crypto/ecc" ) -var Version = semver.MustParse("0.8.0-alpha") +var Version = semver.MustParse("0.8.0") // Curves return the curves supported by gnark func Curves() []ecc.ID { From 572593c9d63f683dbf25a37264b0d82ab498334a Mon Sep 17 00:00:00 2001 From: aybehrouz Date: Fri, 17 Feb 2023 17:08:01 +0330 Subject: [PATCH 02/12] Add an n to 1 multiplexer This commit adds a simple n to 1 multiplexer to the std lib. This multiplexer is based on non-deterministic advice. --- std/gadgets/multiplexer.go | 48 +++++++++++++++++++++++++++++++++ std/gadgets/multiplexer_test.go | 40 +++++++++++++++++++++++++++ std/hints.go | 2 ++ 3 files changed, 90 insertions(+) create mode 100644 std/gadgets/multiplexer.go create mode 100644 std/gadgets/multiplexer_test.go diff --git a/std/gadgets/multiplexer.go b/std/gadgets/multiplexer.go new file mode 100644 index 0000000000..03480b7e07 --- /dev/null +++ b/std/gadgets/multiplexer.go @@ -0,0 +1,48 @@ +package gadgets + +import ( + "math/big" + + "github.com/consensys/gnark/backend/hint" + "github.com/consensys/gnark/frontend" +) + +func init() { + // register hints + hint.Register(SelectionIndicators) +} + +// Mux is an n to 1 multiplexer: out = inputs[sel]. In other words, it selects exactly one of its +// inputs based on sel. The index of inputs starts from zero. +// +// sel needs to be between 0 and n - 1 (inclusive), where n is the number of inputs, otherwise the proof will fail. +func Mux(api frontend.API, sel frontend.Variable, inputs ...frontend.Variable) (out frontend.Variable) { + out = 0 + indicators, _ := api.Compiler().NewHint(SelectionIndicators, len(inputs), sel) + indicatorsSum := frontend.Variable(0) + for i := 0; i < len(inputs); i++ { + // indicators[i] * (sel - i) == 0. Check that all indicators for inputs that are not selected, are zero. + api.AssertIsEqual(api.Mul(indicators[i], api.Sub(sel, i)), 0) + indicatorsSum = api.Add(indicatorsSum, indicators[i]) + // out += indicators[i] * inputs[i] + out = api.Add(out, api.Mul(indicators[i], inputs[i])) + } + // We need to check that the indicator of the selected input is exactly 1. We used a sum constraint, cause usually + // it is cheap. + api.AssertIsEqual(indicatorsSum, 1) + return out +} + +func SelectionIndicators(_ *big.Int, inputs []*big.Int, results []*big.Int) error { + sel := inputs[0] + for i := 0; i < len(results); i++ { + // i is an int which can be int32 or int64. We convert i to int64 then to bigInt, which is safe. We should + // not convert sel to int64. + if sel.Cmp(big.NewInt(int64(i))) == 0 { + results[i].SetUint64(1) + } else { + results[i].SetUint64(0) + } + } + return nil +} diff --git a/std/gadgets/multiplexer_test.go b/std/gadgets/multiplexer_test.go new file mode 100644 index 0000000000..dc35ab4427 --- /dev/null +++ b/std/gadgets/multiplexer_test.go @@ -0,0 +1,40 @@ +package gadgets_test + +import ( + "testing" + + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/gadgets" + "github.com/consensys/gnark/test" +) + +type muxCircuit struct { + SEL frontend.Variable + I0, I1, I2, I3, I4 frontend.Variable + OUT frontend.Variable +} + +func (c *muxCircuit) Define(api frontend.API) error { + + out := gadgets.Mux(api, c.SEL, c.I0, c.I1, c.I2, c.I3, c.I4) + + api.AssertIsEqual(out, c.OUT) + + return nil +} + +func TestMux(t *testing.T) { + assert := test.NewAssert(t) + assert.ProverSucceeded(&muxCircuit{}, &muxCircuit{SEL: 2, I0: 10, I1: 11, I2: 12, I3: 13, I4: 14, OUT: 12}) + + assert.ProverSucceeded(&muxCircuit{}, &muxCircuit{SEL: 0, I0: 10, I1: 11, I2: 12, I3: 13, I4: 14, OUT: 10}) + + assert.ProverSucceeded(&muxCircuit{}, &muxCircuit{SEL: 4, I0: 10, I1: 11, I2: 12, I3: 13, I4: 14, OUT: 14}) + + assert.ProverSucceeded(&muxCircuit{}, &muxCircuit{SEL: 3, I0: 20, I1: 21, I2: 22, I3: 23, I4: 24, OUT: 23}) + + // Failures + assert.ProverFailed(&muxCircuit{}, &muxCircuit{SEL: 5, I0: 20, I1: 21, I2: 22, I3: 23, I4: 24, OUT: 23}) + + assert.ProverFailed(&muxCircuit{}, &muxCircuit{SEL: 0, I0: 20, I1: 21, I2: 22, I3: 23, I4: 24, OUT: 21}) +} diff --git a/std/hints.go b/std/hints.go index 8fc1222781..a5b9008eaf 100644 --- a/std/hints.go +++ b/std/hints.go @@ -6,6 +6,7 @@ import ( "github.com/consensys/gnark/backend/hint" "github.com/consensys/gnark/std/algebra/sw_bls12377" "github.com/consensys/gnark/std/algebra/sw_bls24315" + "github.com/consensys/gnark/std/gadgets" "github.com/consensys/gnark/std/math/bits" "github.com/consensys/gnark/std/math/emulated" ) @@ -30,5 +31,6 @@ func registerHints() { hint.Register(bits.NNAF) hint.Register(bits.IthBit) hint.Register(bits.NBits) + hint.Register(gadgets.SelectionIndicators) hint.Register(emulated.GetHints()...) } From f724491bd7b42eaebf4e3f6dc64708e86b6a113a Mon Sep 17 00:00:00 2001 From: aybehrouz Date: Fri, 17 Feb 2023 23:43:19 +0330 Subject: [PATCH 03/12] Improve tests for `multiplexer.go` Old tests were dependant to outputs and were not able to test invalid inputs correctly. --- std/gadgets/multiplexer_test.go | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/std/gadgets/multiplexer_test.go b/std/gadgets/multiplexer_test.go index dc35ab4427..3506d00c1b 100644 --- a/std/gadgets/multiplexer_test.go +++ b/std/gadgets/multiplexer_test.go @@ -23,8 +23,25 @@ func (c *muxCircuit) Define(api frontend.API) error { return nil } +// The output of this circuit is ignored and the only way its proof can fail is by invalid inputs. +type zeroedMuxCircuit struct { + SEL frontend.Variable + I0, I1, I2 frontend.Variable +} + +func (c *zeroedMuxCircuit) Define(api frontend.API) error { + + out := gadgets.Mux(api, c.SEL, c.I0, c.I1, c.I2) + + // a dummy constraint + api.AssertIsEqual(api.Mul(out, 0), 0) + + return nil +} + func TestMux(t *testing.T) { assert := test.NewAssert(t) + assert.ProverSucceeded(&muxCircuit{}, &muxCircuit{SEL: 2, I0: 10, I1: 11, I2: 12, I3: 13, I4: 14, OUT: 12}) assert.ProverSucceeded(&muxCircuit{}, &muxCircuit{SEL: 0, I0: 10, I1: 11, I2: 12, I3: 13, I4: 14, OUT: 10}) @@ -34,7 +51,19 @@ func TestMux(t *testing.T) { assert.ProverSucceeded(&muxCircuit{}, &muxCircuit{SEL: 3, I0: 20, I1: 21, I2: 22, I3: 23, I4: 24, OUT: 23}) // Failures - assert.ProverFailed(&muxCircuit{}, &muxCircuit{SEL: 5, I0: 20, I1: 21, I2: 22, I3: 23, I4: 24, OUT: 23}) + assert.ProverFailed(&muxCircuit{}, &muxCircuit{SEL: 5, I0: 20, I1: 21, I2: 22, I3: 23, I4: 24, OUT: 24}) assert.ProverFailed(&muxCircuit{}, &muxCircuit{SEL: 0, I0: 20, I1: 21, I2: 22, I3: 23, I4: 24, OUT: 21}) + + // Zeroed circuit: + assert.ProverSucceeded(&zeroedMuxCircuit{}, &zeroedMuxCircuit{SEL: 0, I0: 0, I1: 1, I2: 2}) + + assert.ProverSucceeded(&zeroedMuxCircuit{}, &zeroedMuxCircuit{SEL: 2, I0: 0, I1: 1, I2: 2}) + + // Failures + assert.ProverFailed(&zeroedMuxCircuit{}, &zeroedMuxCircuit{SEL: 3, I0: 0, I1: 1, I2: 2}) + assert.SolvingFailed(&zeroedMuxCircuit{}, &zeroedMuxCircuit{SEL: 3, I0: 0, I1: 1, I2: 2}) + + assert.ProverFailed(&zeroedMuxCircuit{}, &zeroedMuxCircuit{SEL: -1, I0: 0, I1: 1, I2: 2}) + assert.SolvingFailed(&zeroedMuxCircuit{}, &zeroedMuxCircuit{SEL: -1, I0: 0, I1: 1, I2: 2}) } From 3b7d7b2bb6a791654df3664d6c019c627ab34c5f Mon Sep 17 00:00:00 2001 From: aybehrouz Date: Sat, 18 Feb 2023 14:00:37 +0330 Subject: [PATCH 04/12] Refactor multiplexer test --- std/gadgets/multiplexer_test.go | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/std/gadgets/multiplexer_test.go b/std/gadgets/multiplexer_test.go index 3506d00c1b..802ce382da 100644 --- a/std/gadgets/multiplexer_test.go +++ b/std/gadgets/multiplexer_test.go @@ -23,18 +23,15 @@ func (c *muxCircuit) Define(api frontend.API) error { return nil } -// The output of this circuit is ignored and the only way its proof can fail is by invalid inputs. -type zeroedMuxCircuit struct { +// The output of this circuit is ignored and the only way its proof can fail is by providing invalid inputs. +type ignoredOutputMuxCircuit struct { SEL frontend.Variable I0, I1, I2 frontend.Variable } -func (c *zeroedMuxCircuit) Define(api frontend.API) error { - - out := gadgets.Mux(api, c.SEL, c.I0, c.I1, c.I2) - - // a dummy constraint - api.AssertIsEqual(api.Mul(out, 0), 0) +func (c *ignoredOutputMuxCircuit) Define(api frontend.API) error { + // We ignore the output + _ = gadgets.Mux(api, c.SEL, c.I0, c.I1, c.I2) return nil } @@ -46,24 +43,20 @@ func TestMux(t *testing.T) { assert.ProverSucceeded(&muxCircuit{}, &muxCircuit{SEL: 0, I0: 10, I1: 11, I2: 12, I3: 13, I4: 14, OUT: 10}) - assert.ProverSucceeded(&muxCircuit{}, &muxCircuit{SEL: 4, I0: 10, I1: 11, I2: 12, I3: 13, I4: 14, OUT: 14}) - - assert.ProverSucceeded(&muxCircuit{}, &muxCircuit{SEL: 3, I0: 20, I1: 21, I2: 22, I3: 23, I4: 24, OUT: 23}) + assert.ProverSucceeded(&muxCircuit{}, &muxCircuit{SEL: 4, I0: 20, I1: 21, I2: 22, I3: 23, I4: 24, OUT: 24}) // Failures assert.ProverFailed(&muxCircuit{}, &muxCircuit{SEL: 5, I0: 20, I1: 21, I2: 22, I3: 23, I4: 24, OUT: 24}) assert.ProverFailed(&muxCircuit{}, &muxCircuit{SEL: 0, I0: 20, I1: 21, I2: 22, I3: 23, I4: 24, OUT: 21}) - // Zeroed circuit: - assert.ProverSucceeded(&zeroedMuxCircuit{}, &zeroedMuxCircuit{SEL: 0, I0: 0, I1: 1, I2: 2}) + // Ignoring the circuit's output: + assert.ProverSucceeded(&ignoredOutputMuxCircuit{}, &ignoredOutputMuxCircuit{SEL: 0, I0: 0, I1: 1, I2: 2}) - assert.ProverSucceeded(&zeroedMuxCircuit{}, &zeroedMuxCircuit{SEL: 2, I0: 0, I1: 1, I2: 2}) + assert.ProverSucceeded(&ignoredOutputMuxCircuit{}, &ignoredOutputMuxCircuit{SEL: 2, I0: 0, I1: 1, I2: 2}) // Failures - assert.ProverFailed(&zeroedMuxCircuit{}, &zeroedMuxCircuit{SEL: 3, I0: 0, I1: 1, I2: 2}) - assert.SolvingFailed(&zeroedMuxCircuit{}, &zeroedMuxCircuit{SEL: 3, I0: 0, I1: 1, I2: 2}) + assert.ProverFailed(&ignoredOutputMuxCircuit{}, &ignoredOutputMuxCircuit{SEL: 3, I0: 0, I1: 1, I2: 2}) - assert.ProverFailed(&zeroedMuxCircuit{}, &zeroedMuxCircuit{SEL: -1, I0: 0, I1: 1, I2: 2}) - assert.SolvingFailed(&zeroedMuxCircuit{}, &zeroedMuxCircuit{SEL: -1, I0: 0, I1: 1, I2: 2}) + assert.ProverFailed(&ignoredOutputMuxCircuit{}, &ignoredOutputMuxCircuit{SEL: -1, I0: 0, I1: 1, I2: 2}) } From 95f2065309fc1862c9eae4853288673d9df8edd1 Mon Sep 17 00:00:00 2001 From: aybehrouz Date: Sat, 18 Feb 2023 18:16:31 +0330 Subject: [PATCH 05/12] Add a key value lookup table --- std/gadgets/multiplexer.go | 70 ++++++++++++-- std/gadgets/multiplexer_test.go | 161 ++++++++++++++++++++++++++++++++ std/hints.go | 3 +- 3 files changed, 223 insertions(+), 11 deletions(-) diff --git a/std/gadgets/multiplexer.go b/std/gadgets/multiplexer.go index 03480b7e07..53ca9dc3a3 100644 --- a/std/gadgets/multiplexer.go +++ b/std/gadgets/multiplexer.go @@ -9,31 +9,68 @@ import ( func init() { // register hints - hint.Register(SelectionIndicators) + hint.Register(MuxIndicators) + hint.Register(LookupIndicators) +} + +// Lookup is a key value lookup table: the output will be values[i] such that keys[i] == sel. If keys does not +// contain sel, no proofs can be generated. If keys has more than one key that equals to sel, the output will +// be undefined, and the output could be a linear combination of the corresponding values with those keys. +// +// In case keys and values do not have the same length, this function will panic. +func Lookup(api frontend.API, sel frontend.Variable, + keys []frontend.Variable, values []frontend.Variable) frontend.Variable { + // we don't need this check, but we added it to produce more informative errors and disallow + // len(keys) < len(values) which is supported by generateSelector. + if len(keys) != len(values) { + panic("The number of keys and values must be equal") + } + return generateSelector(api, false, sel, keys, values) } // Mux is an n to 1 multiplexer: out = inputs[sel]. In other words, it selects exactly one of its // inputs based on sel. The index of inputs starts from zero. // // sel needs to be between 0 and n - 1 (inclusive), where n is the number of inputs, otherwise the proof will fail. -func Mux(api frontend.API, sel frontend.Variable, inputs ...frontend.Variable) (out frontend.Variable) { +func Mux(api frontend.API, sel frontend.Variable, inputs ...frontend.Variable) frontend.Variable { + return generateSelector(api, true, sel, nil, inputs) +} + +// generateSelector generates a circuit for a multiplexer or a lookup table. If wantMux is true, a multiplexer is +// generated and keys are ignored. If wantMux is false, a lookup table is generated and we must have +// len(keys) <= len(values), otherwise it panics. +func generateSelector(api frontend.API, wantMux bool, sel frontend.Variable, + keys []frontend.Variable, values []frontend.Variable) (out frontend.Variable) { + + var indicators []frontend.Variable + if wantMux { + indicators, _ = api.Compiler().NewHint(MuxIndicators, len(values), sel) + } else { + indicators, _ = api.Compiler().NewHint(LookupIndicators, len(keys), append(keys, sel)...) + } + out = 0 - indicators, _ := api.Compiler().NewHint(SelectionIndicators, len(inputs), sel) indicatorsSum := frontend.Variable(0) - for i := 0; i < len(inputs); i++ { - // indicators[i] * (sel - i) == 0. Check that all indicators for inputs that are not selected, are zero. - api.AssertIsEqual(api.Mul(indicators[i], api.Sub(sel, i)), 0) + for i := 0; i < len(indicators); i++ { + // Check that all indicators for inputs that are not selected, are zero. + if wantMux { + // indicators[i] * (sel - i) == 0 + api.AssertIsEqual(api.Mul(indicators[i], api.Sub(sel, i)), 0) + } else { + // indicators[i] * (sel - keys[i]) == 0 + api.AssertIsEqual(api.Mul(indicators[i], api.Sub(sel, keys[i])), 0) + } indicatorsSum = api.Add(indicatorsSum, indicators[i]) - // out += indicators[i] * inputs[i] - out = api.Add(out, api.Mul(indicators[i], inputs[i])) + // out += indicators[i] * values[i] + out = api.MulAcc(out, indicators[i], values[i]) } - // We need to check that the indicator of the selected input is exactly 1. We used a sum constraint, cause usually + // We need to check that the indicator of the selected input is exactly 1. We used a sum constraint, because usually // it is cheap. api.AssertIsEqual(indicatorsSum, 1) return out } -func SelectionIndicators(_ *big.Int, inputs []*big.Int, results []*big.Int) error { +func MuxIndicators(_ *big.Int, inputs []*big.Int, results []*big.Int) error { sel := inputs[0] for i := 0; i < len(results); i++ { // i is an int which can be int32 or int64. We convert i to int64 then to bigInt, which is safe. We should @@ -46,3 +83,16 @@ func SelectionIndicators(_ *big.Int, inputs []*big.Int, results []*big.Int) erro } return nil } + +func LookupIndicators(_ *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 + for i := 0; i < len(results); i++ { + if key.Cmp(inputs[i]) == 0 { + results[i].SetUint64(1) + } else { + results[i].SetUint64(0) + } + } + return nil +} diff --git a/std/gadgets/multiplexer_test.go b/std/gadgets/multiplexer_test.go index 802ce382da..68b5c1cfe1 100644 --- a/std/gadgets/multiplexer_test.go +++ b/std/gadgets/multiplexer_test.go @@ -1,9 +1,13 @@ package gadgets_test import ( + "fmt" "testing" + "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" + "github.com/consensys/gnark/profile" "github.com/consensys/gnark/std/gadgets" "github.com/consensys/gnark/test" ) @@ -60,3 +64,160 @@ func TestMux(t *testing.T) { assert.ProverFailed(&ignoredOutputMuxCircuit{}, &ignoredOutputMuxCircuit{SEL: -1, I0: 0, I1: 1, I2: 2}) } + +// Lookup table tests: +type lookupCircuit struct { + SEL frontend.Variable + K0, K1, K2, K3 frontend.Variable + V0, V1, V2, V3 frontend.Variable + OUT frontend.Variable +} + +func (c *lookupCircuit) Define(api frontend.API) error { + + out := gadgets.Lookup(api, c.SEL, + []frontend.Variable{c.K0, c.K1, c.K2, c.K3}, + []frontend.Variable{c.V0, c.V1, c.V2, c.V3}) + + api.AssertIsEqual(out, c.OUT) + + return nil +} + +type ignoredOutputLookupCircuit struct { + SEL frontend.Variable + K0, K1 frontend.Variable + V0, V1 frontend.Variable +} + +func (c *ignoredOutputLookupCircuit) Define(api frontend.API) error { + + _ = gadgets.Lookup(api, c.SEL, + []frontend.Variable{c.K0, c.K1}, + []frontend.Variable{c.V0, c.V1}) + + return nil +} + +func TestLookup(t *testing.T) { + assert := test.NewAssert(t) + assert.ProverSucceeded(&lookupCircuit{}, + &lookupCircuit{ + SEL: 100, + K0: 100, K1: 111, K2: 222, K3: 333, + V0: 0, V1: 1, V2: 2, V3: 3, + OUT: 0, + }) + + assert.ProverSucceeded(&lookupCircuit{}, + &lookupCircuit{ + SEL: 222, + K0: 100, K1: 111, K2: 222, K3: 333, + V0: 0, V1: 1, V2: 2, V3: 3, + OUT: 2, + }) + + assert.ProverSucceeded(&lookupCircuit{}, + &lookupCircuit{ + SEL: 333, + K0: 100, K1: 111, K2: 222, K3: 333, + V0: 0, V1: 1, V2: 2, V3: 3, + OUT: 3, + }) + + // Duplicated key, success: + assert.ProverSucceeded(&lookupCircuit{}, + &lookupCircuit{ + SEL: 333, + K0: 222, K1: 222, K2: 222, K3: 333, + V0: 0, V1: 1, V2: 2, V3: 3, + OUT: 3, + }) + + // Duplicated key, UNDEFINED behavior: (with our hint implementation it fails) + assert.ProverFailed(&lookupCircuit{}, + &lookupCircuit{ + SEL: 333, + K0: 100, K1: 111, K2: 333, K3: 333, + V0: 0, V1: 1, V2: 2, V3: 3, + OUT: 3, + }) + + assert.ProverFailed(&lookupCircuit{}, + &lookupCircuit{ + SEL: 77, + K0: 100, K1: 111, K2: 222, K3: 333, + V0: 0, V1: 1, V2: 2, V3: 3, + OUT: 3, + }) + + assert.ProverFailed(&lookupCircuit{}, + &lookupCircuit{ + SEL: 111, + K0: 100, K1: 111, K2: 222, K3: 333, + V0: 0, V1: 1, V2: 2, V3: 3, + OUT: 2, + }) + + // Ignoring the circuit's output: + assert.ProverSucceeded(&ignoredOutputLookupCircuit{}, + &ignoredOutputLookupCircuit{SEL: 5, + K0: 5, K1: 7, + V0: 10, V1: 11, + }) + + assert.ProverFailed(&ignoredOutputLookupCircuit{}, + &ignoredOutputLookupCircuit{SEL: 5, + K0: 5, K1: 5, + V0: 10, V1: 11, + }) + + assert.ProverFailed(&ignoredOutputLookupCircuit{}, + &ignoredOutputLookupCircuit{SEL: 6, + K0: 5, K1: 7, + V0: 10, V1: 11, + }) + +} + +func Example() { + // default options generate gnark.pprof in current dir + // use pprof as usual (go tool pprof -http=:8080 gnark.pprof) to read the profile file + // overlapping profiles are allowed (define profiles inside Define or subfunction to profile + // part of the circuit only) + p := profile.Start() + _, _ = frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &muxCircuit{}) + p.Stop() + + fmt.Println("Number of constraints:", p.NbConstraints()) + fmt.Println(p.Top()) + + p = profile.Start() + _, _ = frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &lookupCircuit{}) + p.Stop() + + fmt.Println("Number of constraints:", p.NbConstraints()) + fmt.Println(p.Top()) + // Output: + // Number of constraints: 17 + // Showing nodes accounting for 17, 100% of 17 total + // flat flat% sum% cum cum% + // 7 41.18% 41.18% 7 41.18% r1cs.(*builder).AssertIsEqual frontend/cs/r1cs/api_assertions.go:37 + // 5 29.41% 70.59% 10 58.82% gadgets.generateSelector std/gadgets/multiplexer.go:58 + // 5 29.41% 100% 5 29.41% gadgets.generateSelector std/gadgets/multiplexer.go:65 + // 0 0% 100% 16 94.12% gadgets.Mux std/gadgets/multiplexer.go:36 + // 0 0% 100% 1 5.88% gadgets.generateSelector std/gadgets/multiplexer.go:69 + // 0 0% 100% 16 94.12% gadgets_test.(*muxCircuit).Define std/gadgets/multiplexer_test.go:23 + // 0 0% 100% 1 5.88% gadgets_test.(*muxCircuit).Define std/gadgets/multiplexer_test.go:25 + // + // Number of constraints: 14 + // Showing nodes accounting for 14, 100% of 14 total + // flat flat% sum% cum cum% + // 6 42.86% 42.86% 6 42.86% r1cs.(*builder).AssertIsEqual frontend/cs/r1cs/api_assertions.go:37 + // 4 28.57% 71.43% 8 57.14% gadgets.generateSelector std/gadgets/multiplexer.go:61 + // 4 28.57% 100% 4 28.57% gadgets.generateSelector std/gadgets/multiplexer.go:65 + // 0 0% 100% 13 92.86% gadgets.Lookup std/gadgets/multiplexer.go:28 + // 0 0% 100% 1 7.14% gadgets.generateSelector std/gadgets/multiplexer.go:69 + // 0 0% 100% 13 92.86% gadgets_test.(*lookupCircuit).Define std/gadgets/multiplexer_test.go:78 + // 0 0% 100% 1 7.14% gadgets_test.(*lookupCircuit).Define std/gadgets/multiplexer_test.go:82 +} diff --git a/std/hints.go b/std/hints.go index a5b9008eaf..616bf644ee 100644 --- a/std/hints.go +++ b/std/hints.go @@ -31,6 +31,7 @@ func registerHints() { hint.Register(bits.NNAF) hint.Register(bits.IthBit) hint.Register(bits.NBits) - hint.Register(gadgets.SelectionIndicators) + hint.Register(gadgets.MuxIndicators) + hint.Register(gadgets.LookupIndicators) hint.Register(emulated.GetHints()...) } From aad9adcbb55132855891d77af26d50acab3d171c Mon Sep 17 00:00:00 2001 From: aybehrouz Date: Mon, 20 Feb 2023 02:01:09 +0330 Subject: [PATCH 06/12] Change some names A lookup table usually uses indices and not keys, so we renamed `Lookup` to `Map`. We also renamed the `gadgets` package to `selector`. --- std/hints.go | 6 +- std/{gadgets => selector}/multiplexer.go | 20 ++-- std/{gadgets => selector}/multiplexer_test.go | 91 ++++++++++--------- 3 files changed, 59 insertions(+), 58 deletions(-) rename std/{gadgets => selector}/multiplexer.go (79%) rename std/{gadgets => selector}/multiplexer_test.go (64%) diff --git a/std/hints.go b/std/hints.go index 616bf644ee..b4b16ae52f 100644 --- a/std/hints.go +++ b/std/hints.go @@ -6,9 +6,9 @@ import ( "github.com/consensys/gnark/backend/hint" "github.com/consensys/gnark/std/algebra/sw_bls12377" "github.com/consensys/gnark/std/algebra/sw_bls24315" - "github.com/consensys/gnark/std/gadgets" "github.com/consensys/gnark/std/math/bits" "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/selector" ) var registerOnce sync.Once @@ -31,7 +31,7 @@ func registerHints() { hint.Register(bits.NNAF) hint.Register(bits.IthBit) hint.Register(bits.NBits) - hint.Register(gadgets.MuxIndicators) - hint.Register(gadgets.LookupIndicators) + hint.Register(selector.MuxIndicators) + hint.Register(selector.MapIndicators) hint.Register(emulated.GetHints()...) } diff --git a/std/gadgets/multiplexer.go b/std/selector/multiplexer.go similarity index 79% rename from std/gadgets/multiplexer.go rename to std/selector/multiplexer.go index 53ca9dc3a3..89a19a6bed 100644 --- a/std/gadgets/multiplexer.go +++ b/std/selector/multiplexer.go @@ -1,4 +1,4 @@ -package gadgets +package selector import ( "math/big" @@ -10,15 +10,15 @@ import ( func init() { // register hints hint.Register(MuxIndicators) - hint.Register(LookupIndicators) + hint.Register(MapIndicators) } -// Lookup is a key value lookup table: the output will be values[i] such that keys[i] == sel. If keys does not -// contain sel, no proofs can be generated. If keys has more than one key that equals to sel, the output will +// Map is a key value associative array: the output will be values[i] such that keys[i] == sel. If keys does not +// contain sel, no proofs can be generated. If keys array has more than one key that equals to sel, the output will // be undefined, and the output could be a linear combination of the corresponding values with those keys. // // In case keys and values do not have the same length, this function will panic. -func Lookup(api frontend.API, sel frontend.Variable, +func Map(api frontend.API, sel frontend.Variable, keys []frontend.Variable, values []frontend.Variable) frontend.Variable { // we don't need this check, but we added it to produce more informative errors and disallow // len(keys) < len(values) which is supported by generateSelector. @@ -36,9 +36,9 @@ func Mux(api frontend.API, sel frontend.Variable, inputs ...frontend.Variable) f return generateSelector(api, true, sel, nil, inputs) } -// generateSelector generates a circuit for a multiplexer or a lookup table. If wantMux is true, a multiplexer is -// generated and keys are ignored. If wantMux is false, a lookup table is generated and we must have -// len(keys) <= len(values), otherwise it panics. +// generateSelector generates a circuit for a multiplexer or an associative array (map). If wantMux is true, a +// multiplexer is generated and keys are ignored. If wantMux is false, a map is generated, and we must have +// len(keys) <= len(values), or it panics. func generateSelector(api frontend.API, wantMux bool, sel frontend.Variable, keys []frontend.Variable, values []frontend.Variable) (out frontend.Variable) { @@ -46,7 +46,7 @@ func generateSelector(api frontend.API, wantMux bool, sel frontend.Variable, if wantMux { indicators, _ = api.Compiler().NewHint(MuxIndicators, len(values), sel) } else { - indicators, _ = api.Compiler().NewHint(LookupIndicators, len(keys), append(keys, sel)...) + indicators, _ = api.Compiler().NewHint(MapIndicators, len(keys), append(keys, sel)...) } out = 0 @@ -84,7 +84,7 @@ func MuxIndicators(_ *big.Int, inputs []*big.Int, results []*big.Int) error { return nil } -func LookupIndicators(_ *big.Int, inputs []*big.Int, results []*big.Int) error { +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 for i := 0; i < len(results); i++ { diff --git a/std/gadgets/multiplexer_test.go b/std/selector/multiplexer_test.go similarity index 64% rename from std/gadgets/multiplexer_test.go rename to std/selector/multiplexer_test.go index 68b5c1cfe1..25d16161e8 100644 --- a/std/gadgets/multiplexer_test.go +++ b/std/selector/multiplexer_test.go @@ -1,4 +1,4 @@ -package gadgets_test +package selector_test import ( "fmt" @@ -8,7 +8,7 @@ import ( "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/frontend/cs/r1cs" "github.com/consensys/gnark/profile" - "github.com/consensys/gnark/std/gadgets" + "github.com/consensys/gnark/std/selector" "github.com/consensys/gnark/test" ) @@ -20,7 +20,7 @@ type muxCircuit struct { func (c *muxCircuit) Define(api frontend.API) error { - out := gadgets.Mux(api, c.SEL, c.I0, c.I1, c.I2, c.I3, c.I4) + out := selector.Mux(api, c.SEL, c.I0, c.I1, c.I2, c.I3, c.I4) api.AssertIsEqual(out, c.OUT) @@ -35,7 +35,7 @@ type ignoredOutputMuxCircuit struct { func (c *ignoredOutputMuxCircuit) Define(api frontend.API) error { // We ignore the output - _ = gadgets.Mux(api, c.SEL, c.I0, c.I1, c.I2) + _ = selector.Mux(api, c.SEL, c.I0, c.I1, c.I2) return nil } @@ -65,17 +65,17 @@ func TestMux(t *testing.T) { assert.ProverFailed(&ignoredOutputMuxCircuit{}, &ignoredOutputMuxCircuit{SEL: -1, I0: 0, I1: 1, I2: 2}) } -// Lookup table tests: -type lookupCircuit struct { +// Map tests: +type mapCircuit struct { SEL frontend.Variable K0, K1, K2, K3 frontend.Variable V0, V1, V2, V3 frontend.Variable OUT frontend.Variable } -func (c *lookupCircuit) Define(api frontend.API) error { +func (c *mapCircuit) Define(api frontend.API) error { - out := gadgets.Lookup(api, c.SEL, + out := selector.Map(api, c.SEL, []frontend.Variable{c.K0, c.K1, c.K2, c.K3}, []frontend.Variable{c.V0, c.V1, c.V2, c.V3}) @@ -84,41 +84,41 @@ func (c *lookupCircuit) Define(api frontend.API) error { return nil } -type ignoredOutputLookupCircuit struct { +type ignoredOutputMapCircuit struct { SEL frontend.Variable K0, K1 frontend.Variable V0, V1 frontend.Variable } -func (c *ignoredOutputLookupCircuit) Define(api frontend.API) error { +func (c *ignoredOutputMapCircuit) Define(api frontend.API) error { - _ = gadgets.Lookup(api, c.SEL, + _ = selector.Map(api, c.SEL, []frontend.Variable{c.K0, c.K1}, []frontend.Variable{c.V0, c.V1}) return nil } -func TestLookup(t *testing.T) { +func TestMap(t *testing.T) { assert := test.NewAssert(t) - assert.ProverSucceeded(&lookupCircuit{}, - &lookupCircuit{ + assert.ProverSucceeded(&mapCircuit{}, + &mapCircuit{ SEL: 100, K0: 100, K1: 111, K2: 222, K3: 333, V0: 0, V1: 1, V2: 2, V3: 3, OUT: 0, }) - assert.ProverSucceeded(&lookupCircuit{}, - &lookupCircuit{ + assert.ProverSucceeded(&mapCircuit{}, + &mapCircuit{ SEL: 222, K0: 100, K1: 111, K2: 222, K3: 333, V0: 0, V1: 1, V2: 2, V3: 3, OUT: 2, }) - assert.ProverSucceeded(&lookupCircuit{}, - &lookupCircuit{ + assert.ProverSucceeded(&mapCircuit{}, + &mapCircuit{ SEL: 333, K0: 100, K1: 111, K2: 222, K3: 333, V0: 0, V1: 1, V2: 2, V3: 3, @@ -126,8 +126,8 @@ func TestLookup(t *testing.T) { }) // Duplicated key, success: - assert.ProverSucceeded(&lookupCircuit{}, - &lookupCircuit{ + assert.ProverSucceeded(&mapCircuit{}, + &mapCircuit{ SEL: 333, K0: 222, K1: 222, K2: 222, K3: 333, V0: 0, V1: 1, V2: 2, V3: 3, @@ -135,24 +135,24 @@ func TestLookup(t *testing.T) { }) // Duplicated key, UNDEFINED behavior: (with our hint implementation it fails) - assert.ProverFailed(&lookupCircuit{}, - &lookupCircuit{ + assert.ProverFailed(&mapCircuit{}, + &mapCircuit{ SEL: 333, K0: 100, K1: 111, K2: 333, K3: 333, V0: 0, V1: 1, V2: 2, V3: 3, OUT: 3, }) - assert.ProverFailed(&lookupCircuit{}, - &lookupCircuit{ + assert.ProverFailed(&mapCircuit{}, + &mapCircuit{ SEL: 77, K0: 100, K1: 111, K2: 222, K3: 333, V0: 0, V1: 1, V2: 2, V3: 3, OUT: 3, }) - assert.ProverFailed(&lookupCircuit{}, - &lookupCircuit{ + assert.ProverFailed(&mapCircuit{}, + &mapCircuit{ SEL: 111, K0: 100, K1: 111, K2: 222, K3: 333, V0: 0, V1: 1, V2: 2, V3: 3, @@ -160,20 +160,20 @@ func TestLookup(t *testing.T) { }) // Ignoring the circuit's output: - assert.ProverSucceeded(&ignoredOutputLookupCircuit{}, - &ignoredOutputLookupCircuit{SEL: 5, + assert.ProverSucceeded(&ignoredOutputMapCircuit{}, + &ignoredOutputMapCircuit{SEL: 5, K0: 5, K1: 7, V0: 10, V1: 11, }) - assert.ProverFailed(&ignoredOutputLookupCircuit{}, - &ignoredOutputLookupCircuit{SEL: 5, + assert.ProverFailed(&ignoredOutputMapCircuit{}, + &ignoredOutputMapCircuit{SEL: 5, K0: 5, K1: 5, V0: 10, V1: 11, }) - assert.ProverFailed(&ignoredOutputLookupCircuit{}, - &ignoredOutputLookupCircuit{SEL: 6, + assert.ProverFailed(&ignoredOutputMapCircuit{}, + &ignoredOutputMapCircuit{SEL: 6, K0: 5, K1: 7, V0: 10, V1: 11, }) @@ -193,7 +193,7 @@ func Example() { fmt.Println(p.Top()) p = profile.Start() - _, _ = frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &lookupCircuit{}) + _, _ = frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &mapCircuit{}) p.Stop() fmt.Println("Number of constraints:", p.NbConstraints()) @@ -203,21 +203,22 @@ func Example() { // Showing nodes accounting for 17, 100% of 17 total // flat flat% sum% cum cum% // 7 41.18% 41.18% 7 41.18% r1cs.(*builder).AssertIsEqual frontend/cs/r1cs/api_assertions.go:37 - // 5 29.41% 70.59% 10 58.82% gadgets.generateSelector std/gadgets/multiplexer.go:58 - // 5 29.41% 100% 5 29.41% gadgets.generateSelector std/gadgets/multiplexer.go:65 - // 0 0% 100% 16 94.12% gadgets.Mux std/gadgets/multiplexer.go:36 - // 0 0% 100% 1 5.88% gadgets.generateSelector std/gadgets/multiplexer.go:69 - // 0 0% 100% 16 94.12% gadgets_test.(*muxCircuit).Define std/gadgets/multiplexer_test.go:23 - // 0 0% 100% 1 5.88% gadgets_test.(*muxCircuit).Define std/gadgets/multiplexer_test.go:25 + // 5 29.41% 70.59% 10 58.82% selector.generateSelector std/selector/multiplexer.go:58 + // 5 29.41% 100% 5 29.41% selector.generateSelector std/selector/multiplexer.go:65 + // 0 0% 100% 16 94.12% selector.Mux std/selector/multiplexer.go:36 + // 0 0% 100% 1 5.88% selector.generateSelector std/selector/multiplexer.go:69 + // 0 0% 100% 16 94.12% selector_test.(*muxCircuit).Define std/selector/multiplexer_test.go:23 + // 0 0% 100% 1 5.88% selector_test.(*muxCircuit).Define std/selector/multiplexer_test.go:25 // // Number of constraints: 14 // Showing nodes accounting for 14, 100% of 14 total // flat flat% sum% cum cum% // 6 42.86% 42.86% 6 42.86% r1cs.(*builder).AssertIsEqual frontend/cs/r1cs/api_assertions.go:37 - // 4 28.57% 71.43% 8 57.14% gadgets.generateSelector std/gadgets/multiplexer.go:61 - // 4 28.57% 100% 4 28.57% gadgets.generateSelector std/gadgets/multiplexer.go:65 - // 0 0% 100% 13 92.86% gadgets.Lookup std/gadgets/multiplexer.go:28 - // 0 0% 100% 1 7.14% gadgets.generateSelector std/gadgets/multiplexer.go:69 - // 0 0% 100% 13 92.86% gadgets_test.(*lookupCircuit).Define std/gadgets/multiplexer_test.go:78 - // 0 0% 100% 1 7.14% gadgets_test.(*lookupCircuit).Define std/gadgets/multiplexer_test.go:82 + // 4 28.57% 71.43% 8 57.14% selector.generateSelector std/selector/multiplexer.go:61 + // 4 28.57% 100% 4 28.57% selector.generateSelector std/selector/multiplexer.go:65 + // 0 0% 100% 13 92.86% selector.Map std/selector/multiplexer.go:28 + // 0 0% 100% 1 7.14% selector.generateSelector std/selector/multiplexer.go:69 + // 0 0% 100% 13 92.86% selector_test.(*mapCircuit).Define std/selector/multiplexer_test.go:78 + // 0 0% 100% 1 7.14% selector_test.(*mapCircuit).Define std/selector/multiplexer_test.go:82 + } From e332109bcd6949a3fc513819fac75c99df3b0832 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 20 Feb 2023 14:35:34 +0100 Subject: [PATCH 07/12] docs: add examples into documentation --- std/selector/doc_map_test.go | 79 ++++++++++++++++++++++++++++++++++++ std/selector/doc_mux_test.go | 77 +++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 std/selector/doc_map_test.go create mode 100644 std/selector/doc_mux_test.go diff --git a/std/selector/doc_map_test.go b/std/selector/doc_map_test.go new file mode 100644 index 0000000000..8d2fc91a34 --- /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 0000000000..af47e67748 --- /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 +} From 53980733689b8c9e5e5928e09a11007f1b2cd26a Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 20 Feb 2023 14:35:47 +0100 Subject: [PATCH 08/12] docs: add package documentation --- std/selector/multiplexer.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/std/selector/multiplexer.go b/std/selector/multiplexer.go index 89a19a6bed..d097d83fa8 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 ( From a741a924193b48aad4043eba8484697b3aa4f93e Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 20 Feb 2023 14:35:56 +0100 Subject: [PATCH 09/12] docs: describe hint functions --- std/selector/multiplexer.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/std/selector/multiplexer.go b/std/selector/multiplexer.go index d097d83fa8..de71dfe9b4 100644 --- a/std/selector/multiplexer.go +++ b/std/selector/multiplexer.go @@ -82,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++ { @@ -96,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 From 9057929cfae32c09eaba64256965ed559a475517 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 20 Feb 2023 14:36:26 +0100 Subject: [PATCH 10/12] docs: remove profiling example --- std/selector/multiplexer_test.go | 47 -------------------------------- 1 file changed, 47 deletions(-) diff --git a/std/selector/multiplexer_test.go b/std/selector/multiplexer_test.go index 25d16161e8..a0233205d1 100644 --- a/std/selector/multiplexer_test.go +++ b/std/selector/multiplexer_test.go @@ -1,13 +1,9 @@ package selector_test import ( - "fmt" "testing" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/frontend/cs/r1cs" - "github.com/consensys/gnark/profile" "github.com/consensys/gnark/std/selector" "github.com/consensys/gnark/test" ) @@ -179,46 +175,3 @@ func TestMap(t *testing.T) { }) } - -func Example() { - // default options generate gnark.pprof in current dir - // use pprof as usual (go tool pprof -http=:8080 gnark.pprof) to read the profile file - // overlapping profiles are allowed (define profiles inside Define or subfunction to profile - // part of the circuit only) - p := profile.Start() - _, _ = frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &muxCircuit{}) - p.Stop() - - fmt.Println("Number of constraints:", p.NbConstraints()) - fmt.Println(p.Top()) - - p = profile.Start() - _, _ = frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &mapCircuit{}) - p.Stop() - - fmt.Println("Number of constraints:", p.NbConstraints()) - fmt.Println(p.Top()) - // Output: - // Number of constraints: 17 - // Showing nodes accounting for 17, 100% of 17 total - // flat flat% sum% cum cum% - // 7 41.18% 41.18% 7 41.18% r1cs.(*builder).AssertIsEqual frontend/cs/r1cs/api_assertions.go:37 - // 5 29.41% 70.59% 10 58.82% selector.generateSelector std/selector/multiplexer.go:58 - // 5 29.41% 100% 5 29.41% selector.generateSelector std/selector/multiplexer.go:65 - // 0 0% 100% 16 94.12% selector.Mux std/selector/multiplexer.go:36 - // 0 0% 100% 1 5.88% selector.generateSelector std/selector/multiplexer.go:69 - // 0 0% 100% 16 94.12% selector_test.(*muxCircuit).Define std/selector/multiplexer_test.go:23 - // 0 0% 100% 1 5.88% selector_test.(*muxCircuit).Define std/selector/multiplexer_test.go:25 - // - // Number of constraints: 14 - // Showing nodes accounting for 14, 100% of 14 total - // flat flat% sum% cum cum% - // 6 42.86% 42.86% 6 42.86% r1cs.(*builder).AssertIsEqual frontend/cs/r1cs/api_assertions.go:37 - // 4 28.57% 71.43% 8 57.14% selector.generateSelector std/selector/multiplexer.go:61 - // 4 28.57% 100% 4 28.57% selector.generateSelector std/selector/multiplexer.go:65 - // 0 0% 100% 13 92.86% selector.Map std/selector/multiplexer.go:28 - // 0 0% 100% 1 7.14% selector.generateSelector std/selector/multiplexer.go:69 - // 0 0% 100% 13 92.86% selector_test.(*mapCircuit).Define std/selector/multiplexer_test.go:78 - // 0 0% 100% 1 7.14% selector_test.(*mapCircuit).Define std/selector/multiplexer_test.go:82 - -} From 477c107be40b2e373e65a34a0d77dd736b333695 Mon Sep 17 00:00:00 2001 From: Ivo Kubjas Date: Mon, 20 Feb 2023 14:37:21 +0100 Subject: [PATCH 11/12] refactor: rename 'sel' to 'queryKey' --- std/selector/multiplexer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/selector/multiplexer.go b/std/selector/multiplexer.go index de71dfe9b4..8062ed9f30 100644 --- a/std/selector/multiplexer.go +++ b/std/selector/multiplexer.go @@ -30,14 +30,14 @@ func init() { // be undefined, and the output could be a linear combination of the corresponding values with those keys. // // In case keys and values do not have the same length, this function will panic. -func Map(api frontend.API, sel frontend.Variable, +func Map(api frontend.API, queryKey frontend.Variable, keys []frontend.Variable, values []frontend.Variable) frontend.Variable { // we don't need this check, but we added it to produce more informative errors and disallow // len(keys) < len(values) which is supported by generateSelector. if len(keys) != len(values) { panic("The number of keys and values must be equal") } - return generateSelector(api, false, sel, keys, values) + return generateSelector(api, false, queryKey, keys, values) } // Mux is an n to 1 multiplexer: out = inputs[sel]. In other words, it selects exactly one of its From 5b2373ec7579574d9a1febbac8b6ce6385b23b27 Mon Sep 17 00:00:00 2001 From: aybehrouz Date: Mon, 20 Feb 2023 17:22:22 +0330 Subject: [PATCH 12/12] docs: correct typos --- std/selector/doc_map_test.go | 2 +- std/selector/doc_mux_test.go | 2 +- std/selector/multiplexer.go | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/std/selector/doc_map_test.go b/std/selector/doc_map_test.go index 8d2fc91a34..f70c353f61 100644 --- a/std/selector/doc_map_test.go +++ b/std/selector/doc_map_test.go @@ -20,7 +20,7 @@ type MapCircuit struct { // 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 + result := selector.Map(api, c.QueryKey, c.Keys[:], c.Values[:]) api.AssertIsEqual(result, c.ExpectedValue) return nil } diff --git a/std/selector/doc_mux_test.go b/std/selector/doc_mux_test.go index af47e67748..5a7b75ba36 100644 --- a/std/selector/doc_mux_test.go +++ b/std/selector/doc_mux_test.go @@ -30,7 +30,7 @@ func ExampleMux() { 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 + Expected: 10, // 5-th element in vector } ccs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit) if err != nil { diff --git a/std/selector/multiplexer.go b/std/selector/multiplexer.go index 8062ed9f30..8e9bec5003 100644 --- a/std/selector/multiplexer.go +++ b/std/selector/multiplexer.go @@ -25,9 +25,9 @@ func init() { hint.Register(MapIndicators) } -// Map is a key value associative array: the output will be values[i] such that keys[i] == sel. If keys does not -// contain sel, no proofs can be generated. If keys array has more than one key that equals to sel, the output will -// be undefined, and the output could be a linear combination of the corresponding values with those keys. +// Map is a key value associative array: the output will be values[i] such that keys[i] == queryKey. If keys does not +// contain queryKey, no proofs can be generated. If keys has more than one key that equals to queryKey, the output will +// be undefined, and the output could be a linear combination of all the corresponding values with that queryKey. // // In case keys and values do not have the same length, this function will panic. func Map(api frontend.API, queryKey frontend.Variable, @@ -82,7 +82,7 @@ func generateSelector(api frontend.API, wantMux bool, sel frontend.Variable, return out } -// MuxIndicator is a hint function used within [Mux] function. It must be +// MuxIndicators 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]