├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── circuit ├── assignment.go ├── circuit.go ├── gates.go └── gates │ ├── add.go │ ├── cipher.go │ ├── copy.go │ ├── gates_test.go │ └── mul.go ├── common ├── challenge.go ├── common.go ├── math.go ├── math_test.go ├── parallelize.go ├── profiling.go └── timing.go ├── examples ├── mimc.go └── mimc_test.go ├── gkr ├── gkr_test.go ├── prover.go └── verifier.go ├── go.mod ├── go.sum ├── hash ├── ark.go ├── gmimc.go ├── hash.go ├── hash_test.go ├── mimc.go └── poseidon.go ├── poly ├── eq.go ├── eq_test.go ├── lagrange.go ├── lagrange_test.go ├── multilin.go ├── multilin_test.go └── pool.go ├── profiling └── .gitkeep ├── prover └── gadget │ ├── circuit.go │ ├── cs.go │ ├── gadget.go │ ├── gadget_api.go │ ├── hints.go │ ├── io_store.go │ ├── performances_test.go │ ├── prove.go │ ├── prover_test.go │ ├── random_test.go │ ├── setup.go │ ├── setup_test.go │ ├── solution.go │ ├── solver_test.go │ └── verify.go ├── snark ├── gkr │ ├── gkr.go │ └── gkr_test.go ├── hash │ ├── mimc.go │ └── mimc_test.go ├── polynomial │ ├── eq.go │ ├── multilinear.go │ ├── univariate.go │ └── univariate_test.go └── sumcheck │ ├── sumcheck.go │ └── sumcheck_test.go └── sumcheck ├── algo.go ├── instance.go ├── prover.go ├── prover_test.go ├── testing.go ├── verifier.go └── worker.go /.gitignore: -------------------------------------------------------------------------------- 1 | profiling 2 | **.prof 3 | **.test 4 | **.txt 5 | **trace.out -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gnark"] 2 | path = pkg/gnark 3 | url = https://github.com/AlexandreBelling/gnark.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GKR 2 | 3 | gkr-mimc is a POC-grade gnark gadget to accelerate the proving time of MiMc hash computations. It can take a batch of hash inputs and output a batch of hash outputs along with a gkr proof that can be verified from inside a SNARK circuit. 4 | 5 | It is a toy implementation of this [post](https://ethresear.ch/t/using-gkr-inside-a-snark-to-reduce-the-cost-of-hash-verification-down-to-3-constraints/7550) for MiMC. Over time, it will into a more generic library, that can support other use cases than MiMC. -------------------------------------------------------------------------------- /circuit/assignment.go: -------------------------------------------------------------------------------- 1 | package circuit 2 | 3 | import ( 4 | "github.com/consensys/gkr-mimc/poly" 5 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 6 | ) 7 | 8 | // Assigment for a GKR circuit 9 | type Assignment []poly.MultiLin 10 | 11 | // Assign computes the full assignment 12 | func (c Circuit) Assign(inps ...poly.MultiLin) (a Assignment) { 13 | 14 | a = make(Assignment, len(c)) 15 | 16 | // Assigns the provided input layers 17 | for i := 0; i < len(inps); i++ { 18 | a[i] = inps[i].DeepCopyLarge() 19 | } 20 | 21 | for i := len(inps); i < len(a); i++ { 22 | 23 | inp := make([][]fr.Element, len(c[i].In)) 24 | for j := range inp { 25 | inp[j] = a[c[i].In[j]] 26 | } 27 | 28 | a[i] = c[i].Evaluate(inp...) 29 | } 30 | 31 | return a 32 | } 33 | 34 | // InputLayersOf returns the input layers of the layer l 35 | func (a Assignment) InputsOfLayer(c Circuit, l int) []poly.MultiLin { 36 | positions := c[l].In 37 | res := make([]poly.MultiLin, len(positions)) 38 | 39 | for i := range res { 40 | 41 | pos := positions[i] 42 | // We want to know if current layer `l` is the first output of layer `pos`* 43 | // Indeed, `Out` is guaranteed to be sorted in ascending order. 44 | // It matters, because the result will be mutated by the sumcheck prover 45 | // and we may need to use a layer's output more than once 46 | isFirst := c[pos].Out[0] == l 47 | 48 | if isFirst { 49 | // Then no need to deep-copy 50 | res[i] = a[pos] 51 | } else { 52 | res[i] = a[pos].DeepCopyLarge() 53 | } 54 | } 55 | 56 | return res 57 | } 58 | 59 | // InputLayersOf returns the input layers of the layer l 60 | func (a Assignment) Dump() { 61 | for _, p := range a { 62 | poly.DumpLarge(p) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /circuit/circuit.go: -------------------------------------------------------------------------------- 1 | package circuit 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/consensys/gkr-mimc/common" 7 | "github.com/consensys/gkr-mimc/poly" 8 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 9 | ) 10 | 11 | type Circuit []Layer 12 | 13 | // Variable describes a circuit variable 14 | type Layer struct { 15 | // Variables indexes that are inputs to compute the current variable 16 | // Empty, means this an input layer 17 | In []int 18 | // Variables indexes that are fed by the current variable 19 | // Empty means this is an output layer 20 | Out []int 21 | // Expresses how to build the variable 22 | Gate Gate 23 | } 24 | 25 | // BuildCircuit 26 | // - Computes the Out layers 27 | // - Ensures there is no input used more than once : multi-instances must be explicitly provided as intermediary layers 28 | func BuildCircuit(c Circuit) error { 29 | // Computes the output layers 30 | for l := range c { 31 | for _, pos := range c[l].In { 32 | c[pos].Out = append(c[pos].Out, l) 33 | } 34 | } 35 | 36 | // Counts the number of multi-instances 37 | for l := range c { 38 | if len(c[l].In) == 0 && len(c[l].Out) > 1 { 39 | return fmt.Errorf("Layer %v is an input layer but has %v outputs", l, len(c[l].Out)) 40 | } 41 | } 42 | 43 | return nil 44 | } 45 | 46 | // Evaluate returns the assignment of the next layer 47 | // It can be multi-threaded 48 | func (l *Layer) Evaluate(inputs ...[]fr.Element) []fr.Element { 49 | nbIterations := len(inputs[0]) 50 | res := poly.MakeLarge(nbIterations) 51 | 52 | common.Parallelize(nbIterations, 53 | func(start, stop int) { 54 | 55 | inps := make([][]fr.Element, len(inputs)) 56 | for i := 0; i < len(inputs); i++ { 57 | inps[i] = inputs[i][start:stop] 58 | } 59 | 60 | l.Gate.EvalBatch(res[start:stop], inps...) 61 | }, 62 | ) 63 | return res 64 | } 65 | 66 | // IsInputLayer returns true/false if this is an input layer 67 | // There are multiple ways of checking a layer is an input or output 68 | // All of them are checked. This helps as a sanity checks : 69 | // it will panic if any of the checks contradict the others. 70 | func (c Circuit) IsInputLayer(layer int) bool { 71 | hasNoInputs := len(c[layer].In) == 0 72 | hasNogates := c[layer].Gate == nil 73 | 74 | if hasNoInputs != hasNogates { 75 | panic(fmt.Sprintf("layer %v has no inputs? : %v but also has no gate? : %v", layer, hasNoInputs, hasNogates)) 76 | } 77 | 78 | return hasNoInputs 79 | } 80 | 81 | // Returns the input arity of the circuit 82 | func (c Circuit) InputArity() int { 83 | count := 0 84 | for layer := range c { 85 | if !c.IsInputLayer(layer) { 86 | break 87 | } 88 | count++ 89 | } 90 | return count 91 | } 92 | -------------------------------------------------------------------------------- /circuit/gates.go: -------------------------------------------------------------------------------- 1 | package circuit 2 | 3 | import ( 4 | "github.com/AlexandreBelling/gnark/frontend" 5 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 6 | ) 7 | 8 | // Gate assumes the gate can only have 2 inputs 9 | type Gate interface { 10 | // ID returns an ID that is unique for the gate 11 | ID() string 12 | // GnarkEval performs the same computation as Eval but on Gnark variables 13 | GnarkEval(cs frontend.API, xs ...frontend.Variable) frontend.Variable 14 | // EvalBatch returns an evaluation for a range of inputs passed in evals 15 | // And puts each result in res 16 | EvalBatch(res []fr.Element, xs ...[]fr.Element) 17 | // Eval returns an evaluation for a unique pair of Eval 18 | Eval(res *fr.Element, xs ...*fr.Element) 19 | // Degree returns the degrees of the gate relatively to HPrime 20 | Degree() (degHPrime int) 21 | } 22 | -------------------------------------------------------------------------------- /circuit/gates/add.go: -------------------------------------------------------------------------------- 1 | package gates 2 | 3 | import ( 4 | "github.com/AlexandreBelling/gnark/frontend" 5 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 6 | ) 7 | 8 | // AddGate performs an addition 9 | type AddGate struct{} 10 | 11 | // ID returns the gate ID 12 | func (a AddGate) ID() string { return "AddGate" } 13 | 14 | // Eval return the result vL + vR 15 | func (a AddGate) Eval(res, vL, vR *fr.Element) { 16 | res.Add(vL, vR) 17 | } 18 | 19 | // GnarkEval compute the gate on a gnark circuit 20 | func (a AddGate) GnarkEval(cs frontend.API, vL, vR frontend.Variable) frontend.Variable { 21 | // Unoptimized, but unlikely to cause any significant performance loss 22 | return cs.Add(vL, vR) 23 | } 24 | 25 | // EvalManyVR performs an element-wise addition of many vRs values by one vL value 26 | // res must be initialized with the same size as vRs 27 | func (a AddGate) EvalManyVR(res []fr.Element, vL *fr.Element, vRs []fr.Element) { 28 | for i, vR := range vRs { 29 | res[i].Add(vL, &vR) 30 | } 31 | } 32 | 33 | // EvalManyVL performs an element-wise addition of many vLs values by one vR value 34 | func (a AddGate) EvalManyVL(res []fr.Element, vLs []fr.Element, vR *fr.Element) { 35 | for i, vL := range vLs { 36 | res[i].Add(&vL, vR) 37 | } 38 | } 39 | 40 | // Degrees returns the degrees of the gate on hL, hR and hPrime 41 | func (a AddGate) Degrees() (degHL, degHR, degHPrime int) { 42 | return 1, 1, 1 43 | } 44 | -------------------------------------------------------------------------------- /circuit/gates/cipher.go: -------------------------------------------------------------------------------- 1 | package gates 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlexandreBelling/gnark/frontend" 7 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 8 | ) 9 | 10 | // CipherGate cipher gate returns vL + (vR + c)^7 11 | type CipherGate struct { 12 | Ark fr.Element 13 | } 14 | 15 | // NewCipherGate construct a new cipher gate given an ark 16 | // CipherGate cipher gate returns vL + (vR + c)^7 17 | func NewCipherGate(ark fr.Element) *CipherGate { 18 | return &CipherGate{Ark: ark} 19 | } 20 | 21 | // ID returns the id of the cipher gate and print the ark as well 22 | func (c *CipherGate) ID() string { return fmt.Sprintf("CipherGate-%v", c.Ark.String()) } 23 | 24 | // Eval returns (vR + c + vL)^7, on the range of output 25 | func (c *CipherGate) EvalBatch(res []fr.Element, xs ...[]fr.Element) { 26 | 27 | ls := xs[0] 28 | rs := xs[1] 29 | 30 | var tmp fr.Element 31 | 32 | for i := range res { 33 | // tmp = vR + Ark + vL 34 | tmp.Add(&rs[i], &c.Ark) 35 | tmp.Add(&tmp, &ls[i]) 36 | // res = tmp^7 37 | res[i].Square(&tmp) 38 | res[i].Mul(&res[i], &tmp) 39 | res[i].Square(&res[i]) 40 | res[i].Mul(&res[i], &tmp) 41 | } 42 | } 43 | 44 | // Eval returns (vL + vR + c)^7 45 | func (c *CipherGate) Eval(res *fr.Element, xs ...*fr.Element) { 46 | // tmp = vR + Ark + vL 47 | var tmp fr.Element 48 | tmp.Add(xs[1], &c.Ark) 49 | tmp.Add(&tmp, xs[0]) 50 | // res = tmp^7 51 | res.Square(&tmp) 52 | res.Mul(res, &tmp) 53 | res.Square(res) 54 | res.Mul(res, &tmp) 55 | } 56 | 57 | // GnarkEval performs the cipher operation on gnark variables 58 | func (c *CipherGate) GnarkEval(cs frontend.API, xs ...frontend.Variable) frontend.Variable { 59 | tmp := cs.Add(xs[0], xs[1], frontend.Variable(c.Ark)) 60 | cipher := cs.Mul(tmp, tmp) 61 | cipher = cs.Mul(cipher, tmp) 62 | cipher = cs.Mul(cipher, cipher) 63 | cipher = cs.Mul(cipher, tmp) 64 | return cipher 65 | } 66 | 67 | // Degree returns the Degree of the gate on hL, hR and hPrime 68 | func (c *CipherGate) Degree() (degHPrime int) { 69 | return 7 70 | } 71 | -------------------------------------------------------------------------------- /circuit/gates/copy.go: -------------------------------------------------------------------------------- 1 | package gates 2 | 3 | import ( 4 | "github.com/AlexandreBelling/gnark/frontend" 5 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 6 | ) 7 | 8 | // IdentityGate performs a copy of the vL value and ignores the vR value 9 | type IdentityGate struct{} 10 | 11 | // ID returns "CopyGate" as an ID for CopyGate 12 | func (c IdentityGate) ID() string { return "CopyGate" } 13 | 14 | // Eval returns for a range of inputs 15 | func (c IdentityGate) EvalBatch(res []fr.Element, xs ...[]fr.Element) { 16 | copy(res, xs[0]) 17 | } 18 | 19 | // Eval returns vL 20 | func (c IdentityGate) Eval(res *fr.Element, xs ...*fr.Element) { 21 | res.Set(xs[0]) 22 | } 23 | 24 | // GnarkEval performs the copy on gnark variable 25 | func (c IdentityGate) GnarkEval(cs frontend.API, x ...frontend.Variable) frontend.Variable { 26 | return x[0] 27 | } 28 | 29 | // Degree returns the Degree of the gate on hL, hR and hPrime 30 | func (c IdentityGate) Degree() (degHPrime int) { 31 | return 1 32 | } 33 | -------------------------------------------------------------------------------- /circuit/gates/gates_test.go: -------------------------------------------------------------------------------- 1 | package gates 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/consensys/gkr-mimc/circuit" 7 | "github.com/consensys/gkr-mimc/common" 8 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func genericTest(t *testing.T, gate circuit.Gate) { 13 | 14 | size := 10 15 | 16 | l := common.RandomFrArray(size) 17 | r := common.RandomFrArray(size) 18 | 19 | resA := make([]fr.Element, size) 20 | resB := make([]fr.Element, size) 21 | 22 | gate.EvalBatch(resA, l, r) 23 | 24 | for i := range resB { 25 | gate.Eval(&resB[i], &l[i], &r[i]) 26 | } 27 | 28 | assert.Equal(t, resA, resB) 29 | } 30 | 31 | func TestGates(t *testing.T) { 32 | gates := []circuit.Gate{ 33 | IdentityGate{}, 34 | NewCipherGate(fr.NewElement(25)), 35 | } 36 | 37 | for _, gate := range gates { 38 | genericTest(t, gate) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /circuit/gates/mul.go: -------------------------------------------------------------------------------- 1 | package gates 2 | 3 | import ( 4 | "github.com/AlexandreBelling/gnark/frontend" 5 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 6 | ) 7 | 8 | // MulGate performs a multiplication 9 | type MulGate struct{} 10 | 11 | // ID returns the MulGate as ID 12 | func (m MulGate) ID() string { return "MulGate" } 13 | 14 | // Eval returns vL * vR 15 | func (m MulGate) Eval(res, vL, vR *fr.Element) { 16 | res.Mul(vL, vR) 17 | } 18 | 19 | // GnarkEval performs the gate operation on gnark variables 20 | func (m MulGate) GnarkEval(cs frontend.API, vL, vR frontend.Variable) frontend.Variable { 21 | return cs.Mul(vL, vR) 22 | } 23 | 24 | // EvalManyVR performs an element-wise multiplication of many vRs values by one vL value 25 | func (m MulGate) EvalManyVR(res []fr.Element, vL *fr.Element, vRs []fr.Element) { 26 | for i, vR := range vRs { 27 | res[i].Mul(vL, &vR) 28 | } 29 | } 30 | 31 | // EvalManyVL performs an element-wise multiplication of many vLs values by one vR value 32 | func (m MulGate) EvalManyVL(res, vLs []fr.Element, vR *fr.Element) { 33 | for i, vL := range vLs { 34 | res[i].Mul(&vL, vR) 35 | } 36 | } 37 | 38 | // Degrees returns the degrees of the gate on hL, hR and hPrime 39 | func (m MulGate) Degrees() (degHL, degHR, degHPrime int) { 40 | return 1, 1, 2 41 | } 42 | -------------------------------------------------------------------------------- /common/challenge.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/consensys/gkr-mimc/hash" 5 | 6 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 7 | ) 8 | 9 | // GetChallenge returns a interaction challenge 10 | func GetChallenge(challengeSeed []fr.Element) fr.Element { 11 | return hash.MimcHash(challengeSeed) 12 | } 13 | -------------------------------------------------------------------------------- /common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 7 | ) 8 | 9 | // FrSliceToString pretty prints a slice of fr.Element to ease debugging 10 | func FrSliceToString(slice []fr.Element) string { 11 | res := "[" 12 | for _, x := range slice { 13 | res += fmt.Sprintf("%v, ", x.String()) 14 | } 15 | res += "]" 16 | return res 17 | } 18 | 19 | // UintSliceToString pretty-prints a slice of uint for debugging 20 | func UintSliceToString(slice []uint) string { 21 | res := "[" 22 | for _, x := range slice { 23 | res += fmt.Sprintf("%v, ", x) 24 | } 25 | res += "]" 26 | return res 27 | } 28 | 29 | // IntSliceToString pretty-prints a slice of int for debugging 30 | func IntSliceToString(slice []int) string { 31 | res := "[" 32 | for _, x := range slice { 33 | res += fmt.Sprintf("%v, ", x) 34 | } 35 | res += "]" 36 | return res 37 | } 38 | 39 | // FrToGenericArray downcast to an slice of interface 40 | func FrToGenericArray(slice []fr.Element) []interface{} { 41 | res := make([]interface{}, len(slice)) 42 | for i := range slice { 43 | res[i] = slice[i] 44 | } 45 | return res 46 | } 47 | 48 | // RandomFrArray returns a random array 49 | func RandomFrArray(size int) []fr.Element { 50 | res := make([]fr.Element, size) 51 | for i := range res { 52 | res[i].SetUint64(uint64(i)*uint64(i) ^ 0xf45c9df123f) 53 | } 54 | return res 55 | } 56 | 57 | // Uint64ToFr allows to quickly create fr.Element 58 | func Uint64ToFr(x uint64) fr.Element { 59 | var res fr.Element 60 | res.SetUint64(x) 61 | return res 62 | } 63 | 64 | // Assert is equivalent of if 65 | // ``` 66 | // if !assertion { 67 | // panic(fmt.Sprintf(msg, args...)) 68 | //} 69 | // ``` 70 | func Assert(assertion bool, msg string, args ...interface{}) { 71 | if !assertion { 72 | panic(fmt.Sprintf(msg, args...)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /common/math.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | // Min returns the minimum of two numbers 4 | func Min(a int, b int) int { 5 | if a <= b { 6 | return a 7 | } 8 | return b 9 | } 10 | 11 | // Max returns the maximum of two number 12 | func Max(a int, b int) int { 13 | if a >= b { 14 | return a 15 | } 16 | return b 17 | } 18 | 19 | // Log2Floor computes the floored value of Log2 20 | func Log2Floor(a int) int { 21 | res := 0 22 | for i := a; i > 1; i = i >> 1 { 23 | res++ 24 | } 25 | return res 26 | } 27 | 28 | // Log2Ceil computes the ceiled value of Log2 29 | func Log2Ceil(a int) int { 30 | floor := Log2Floor(a) 31 | if a != 1< 0 { 33 | _end++ 34 | extraTasks-- 35 | extraTasksOffset++ 36 | } 37 | go func() { 38 | work(_start, _end) 39 | wg.Done() 40 | }() 41 | } 42 | 43 | wg.Wait() 44 | } 45 | 46 | // Split the large task in smaller chunks appropriately, and `dispatch` for all`. 47 | // Usefull to send jobs to a worker pool, returns `true`. 48 | // If it's not practical to dispatch asynchronously, does nothing and returns `0` 49 | func TryDispatch(nbIteration, minTaskSize int, dispatch func(start, stop int)) int { 50 | 51 | // For better balance between the threads, make small tasks 52 | nbTasks := runtime.NumCPU() * 8 53 | nbIterationPerTasks := nbIteration / nbTasks 54 | 55 | if nbIterationPerTasks < minTaskSize { 56 | // Not enough iterations per tasks to make it worth it parallelizing at max 57 | // Make bigger tasks 58 | nbIterationPerTasks = minTaskSize 59 | nbTasks = nbIteration / nbIterationPerTasks 60 | } 61 | 62 | if nbTasks <= 1 { 63 | // Not enough iteration per tasks to make parallelizing interesting at all 64 | // Does not do anything 65 | return 0 66 | } 67 | 68 | // Accounts that `nbTasks` might not divide `nbIteration` 69 | extraIteration := nbIteration - nbTasks*nbIterationPerTasks 70 | extraIterationOffset := 0 71 | 72 | for i := 0; i < nbTasks; i++ { 73 | // Stuffs the extra remain iterations inside the first tasks 74 | start := i*nbIterationPerTasks + extraIterationOffset 75 | stop := start + nbIterationPerTasks 76 | 77 | if extraIteration > 0 { 78 | stop++ 79 | extraIteration-- 80 | extraIterationOffset++ 81 | } 82 | 83 | dispatch(start, stop) 84 | } 85 | 86 | return nbTasks 87 | 88 | } 89 | -------------------------------------------------------------------------------- /common/profiling.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "path/filepath" 8 | "runtime/trace" 9 | "testing" 10 | 11 | "github.com/pkg/profile" 12 | ) 13 | 14 | const GKR_MIMC string = "gkr-mimc" 15 | 16 | // Helper to create to a file, and does a few checks 17 | // Ensure it does not create a file from outside the repo 18 | // The path should be specified assuming it is relative to the root of the project 19 | func GetPath(p string) (string, error) { 20 | base, _ := os.Getwd() 21 | 22 | // Only keeps the part of the base, that leads to the project root 23 | loop: 24 | for { 25 | switch path.Base(base) { 26 | case ".": 27 | return "", fmt.Errorf("the current directory `%v` is not included in `%v`. try running from another place", GKR_MIMC, base) 28 | case GKR_MIMC: 29 | // We stop truncating, we have the right absolute path 30 | break loop 31 | default: 32 | // Truncate the base path : haven't found a better way than this 33 | base = path.Clean(base + "/..") 34 | } 35 | } 36 | 37 | // Get dir where we want to write 38 | dir, file := filepath.Split(p) 39 | dir = filepath.Join(base, dir) 40 | 41 | // Ensure the folder was created prior running the test 42 | if err := os.MkdirAll(dir, os.ModePerm); err != nil { 43 | return "", err 44 | } 45 | 46 | // The create the file 47 | p = filepath.Join(dir, file) 48 | return p, nil 49 | } 50 | 51 | // ProfileTrace run the benchmark function with optionally, benchmarking and tracing 52 | func ProfileTrace(b *testing.B, profiled, traced bool, fn func()) { 53 | var f *os.File 54 | var pprof interface{ Stop() } 55 | 56 | if traced { 57 | _path, err := GetPath(fmt.Sprintf("profiling/%v/trace.out", b.Name())) 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | f, err = os.Create(_path) 63 | if err != nil { 64 | panic(err) 65 | } 66 | 67 | err = trace.Start(f) 68 | if err != nil { 69 | panic(err) 70 | } 71 | 72 | defer trace.Stop() 73 | } 74 | 75 | if profiled { 76 | _path, err := GetPath(fmt.Sprintf("profiling/%v", b.Name())) 77 | if err != nil { 78 | panic(err) 79 | } 80 | 81 | pprof = profile.Start( 82 | profile.ProfilePath(_path), 83 | profile.Quiet, 84 | ) 85 | defer pprof.Stop() 86 | } 87 | 88 | b.StartTimer() 89 | defer b.StopTimer() 90 | 91 | fn() 92 | } 93 | -------------------------------------------------------------------------------- /common/timing.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type TimeTracker struct { 9 | label string 10 | t time.Time 11 | } 12 | 13 | func NewTimer(label string) TimeTracker { 14 | return TimeTracker{ 15 | label: label, 16 | t: time.Now(), 17 | } 18 | } 19 | 20 | func (t TimeTracker) Close() { 21 | fmt.Printf("Elapsed time for %v : %v (ms) \n", t.label, time.Since(t.t).Milliseconds()) 22 | } 23 | -------------------------------------------------------------------------------- /examples/mimc.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "github.com/consensys/gkr-mimc/circuit" 5 | "github.com/consensys/gkr-mimc/circuit/gates" 6 | "github.com/consensys/gkr-mimc/hash" 7 | ) 8 | 9 | // MimcCircuit returns the GKR MIMC proving circuit 10 | func MimcCircuit() circuit.Circuit { 11 | nRounds := 91 12 | 13 | c := make(circuit.Circuit, nRounds+3) 14 | 15 | // Contains the `block` of the permutations 16 | c[0] = circuit.Layer{In: []int{}} 17 | // Contains the initial state of the permutation 18 | c[1] = circuit.Layer{In: []int{}} 19 | // Multi-instance layer of the key : added explicitly 20 | c[2] = circuit.Layer{In: []int{0}, Gate: gates.IdentityGate{}} 21 | 22 | for i := 0; i < nRounds; i++ { 23 | inp := i + 2 24 | if i == 0 { 25 | // Points to 1 : not the multi-instance 26 | inp = 1 27 | } 28 | 29 | c[i+3] = circuit.Layer{In: []int{2, inp}, Gate: gates.NewCipherGate(hash.Arks[i])} 30 | } 31 | 32 | if err := circuit.BuildCircuit(c); err != nil { 33 | panic(err) 34 | } 35 | 36 | return c 37 | } 38 | -------------------------------------------------------------------------------- /examples/mimc_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/consensys/gkr-mimc/common" 7 | "github.com/consensys/gkr-mimc/hash" 8 | "github.com/stretchr/testify/assert" 9 | 10 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 11 | ) 12 | 13 | func randomInputs(bN int) (keys, state []fr.Element) { 14 | size := (1 << bN) 15 | 16 | return common.RandomFrArray(size), common.RandomFrArray(size) 17 | } 18 | 19 | func TestMimc(t *testing.T) { 20 | 21 | // Initialize the circuit 22 | mimcCircuit := MimcCircuit() 23 | 24 | assert.True(t, mimcCircuit.IsInputLayer(0)) 25 | assert.True(t, mimcCircuit.IsInputLayer(1)) 26 | assert.Equal(t, mimcCircuit.InputArity(), 2) 27 | 28 | // Test the consistency between the the combinator and the transition function 29 | bN := 3 30 | 31 | // Performs the assignment 32 | key, payload := randomInputs(bN) 33 | a := mimcCircuit.Assign(key, payload) 34 | 35 | outputs := a[93] 36 | 37 | // Sees if the output is consistent with the result of calling Mimc permutation 38 | finstate0 := hash.MimcKeyedPermutation(payload[0], key[0]) 39 | 40 | // An error here indicates a mismatch between the circuit and the mimc permutation 41 | assert.Equal(t, finstate0.String(), outputs[0].String(), "Error on the state calculation") 42 | } 43 | 44 | func TestCircuitForm(t *testing.T) { 45 | 46 | // Initialize the circuit 47 | circ := MimcCircuit() 48 | 49 | // Test that the Out are always in increasing order 50 | for l := range circ { 51 | assert.IsIncreasing(t, circ[l].Out) 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /gkr/gkr_test.go: -------------------------------------------------------------------------------- 1 | package gkr 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/consensys/gkr-mimc/common" 8 | "github.com/consensys/gkr-mimc/examples" 9 | "github.com/consensys/gkr-mimc/poly" 10 | "github.com/consensys/gkr-mimc/sumcheck" 11 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 12 | ) 13 | 14 | func TestGKR(t *testing.T) { 15 | 16 | for bn := 0; bn < 12; bn++ { 17 | 18 | var one fr.Element 19 | one.SetOne() 20 | 21 | c := examples.MimcCircuit() 22 | 23 | block := common.RandomFrArray(1 << bn) 24 | initstate := common.RandomFrArray(1 << bn) 25 | qPrime := common.RandomFrArray(bn) 26 | 27 | a := c.Assign(block, initstate) 28 | // Gets a deep-copy of the assignment 29 | a2 := c.Assign(block, initstate) 30 | _ = a2[0].String() 31 | 32 | proof := Prove(c, a, qPrime) 33 | 34 | // Check that the claims are consistents with the assignment 35 | for layer := len(c) - 1; layer >= 0; layer-- { 36 | 37 | for j, claim := range proof.Claims[layer] { 38 | claim2 := a2[layer].Evaluate(proof.QPrimes[layer][j]) 39 | 40 | if claim2 != claim { 41 | panic(fmt.Sprintf("at bn = %v, claim inconsistent with assignment at layer %v no %v, %v != %v", bn, layer, j, claim.String(), claim2.String())) 42 | } 43 | } 44 | } 45 | 46 | // Check that the claims are consistents with the layers evaluations 47 | for layer := len(c) - 1; layer >= 0; layer-- { 48 | // Skip if this is an input layer 49 | if c[layer].Gate == nil { 50 | break 51 | } 52 | 53 | Xs := a2.InputsOfLayer(c, layer) 54 | 55 | for j, claim := range proof.Claims[layer] { 56 | qPrime := proof.QPrimes[layer][j] 57 | claim2 := sumcheck.Evaluation(c[layer].Gate, [][]fr.Element{qPrime}, []fr.Element{}, Xs...) 58 | 59 | if claim2 != claim { 60 | panic(fmt.Sprintf("inconsistent claim at layer %v no %v, %v != %v", layer, j, claim.String(), claim2.String())) 61 | } 62 | } 63 | 64 | for _, X := range Xs { 65 | poly.DumpLarge(X) 66 | } 67 | 68 | } 69 | 70 | err := Verify(c, proof, []poly.MultiLin{block, initstate}, a[93], qPrime) 71 | if err != nil { 72 | panic(fmt.Sprintf("bn = %v error at gkr verifier : %v", bn, err)) 73 | } 74 | 75 | cleared := poly.ClearPool() 76 | fmt.Printf("cleared %v elements \n", cleared) 77 | } 78 | } 79 | 80 | func BenchmarkGkr(b *testing.B) { 81 | for bn := 17; bn < 24; bn++ { 82 | b.Run(fmt.Sprintf("bn-%v", bn), func(b *testing.B) { 83 | benchmarkGkr(b, bn) 84 | }) 85 | } 86 | } 87 | 88 | func benchmarkGkr(b *testing.B, bn int) { 89 | 90 | var one fr.Element 91 | one.SetOne() 92 | 93 | c := examples.MimcCircuit() 94 | 95 | block := common.RandomFrArray(1 << bn) 96 | initstate := common.RandomFrArray(1 << bn) 97 | qPrime := common.RandomFrArray(bn) 98 | 99 | a := c.Assign(block, initstate) 100 | 101 | b.ResetTimer() 102 | 103 | common.ProfileTrace(b, false, true, func() { 104 | _ = Prove(c, a, qPrime) 105 | }) 106 | 107 | } 108 | -------------------------------------------------------------------------------- /gkr/prover.go: -------------------------------------------------------------------------------- 1 | package gkr 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | "github.com/consensys/gkr-mimc/circuit" 8 | "github.com/consensys/gkr-mimc/sumcheck" 9 | 10 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 11 | ) 12 | 13 | // Proof contains all the data for a GKR to be verified 14 | type Proof struct { 15 | SumcheckProofs []sumcheck.Proof 16 | Claims [][]fr.Element 17 | QPrimes [][][]fr.Element 18 | } 19 | 20 | // Prove returns a new prover 21 | func Prove(c circuit.Circuit, a circuit.Assignment, qPrime []fr.Element) (proof Proof) { 22 | 23 | nLayers := len(c) 24 | 25 | // Allocate the proof 26 | proof.Claims = make([][]fr.Element, nLayers) 27 | proof.SumcheckProofs = make([]sumcheck.Proof, nLayers) 28 | proof.QPrimes = make([][][]fr.Element, nLayers) 29 | 30 | // Passes the initial qPrime inside the proof 31 | proof.QPrimes[nLayers-1] = [][]fr.Element{qPrime} 32 | 33 | for layer := nLayers - 1; layer >= 0; layer-- { 34 | 35 | if c.IsInputLayer(layer) { 36 | // It's an input layer 37 | // The proof is complete 38 | break 39 | } 40 | 41 | // Otherwise, we are on for a multi-instance with identity 42 | // The fact this is a multi-identity is specified in the circuit description 43 | proof.updateWithSumcheck(c, a, layer) 44 | } 45 | 46 | return 47 | } 48 | 49 | func (p *Proof) updateWithSumcheck( 50 | c circuit.Circuit, 51 | a circuit.Assignment, 52 | layer int, 53 | ) { 54 | 55 | // Sumcheck proof 56 | sumPi, nextQPrime, finalClaims := sumcheck.Prove( 57 | a.InputsOfLayer(c, layer), 58 | p.QPrimes[layer], 59 | p.Claims[layer], 60 | c[layer].Gate, 61 | ) 62 | 63 | p.SumcheckProofs[layer] = sumPi 64 | 65 | // Then update the qPrimes and Claims for the upcoming sumchecks to use them 66 | for i := 1; i < len(finalClaims); i++ { 67 | 68 | // Index of the corresponding input layer 69 | inpL := c[layer].In[i-1] 70 | 71 | if len(p.Claims[inpL]) < 1 { 72 | // Allocates the entire vectors once, so we can write at any index later 73 | p.Claims[inpL] = make([]fr.Element, len(c[inpL].Out)) 74 | p.QPrimes[inpL] = make([][]fr.Element, len(c[inpL].Out)) 75 | } 76 | 77 | // Seach the position of `l` as an output of layer `inpL` 78 | // It works because `c[inpL].Out` is guaranteed to be sorted. 79 | writeAt := sort.SearchInts(c[inpL].Out, layer) 80 | 81 | // Since `SearchInts` does not answer whether the `int` is contained or not 82 | // but returns the position if it "were" inside. We need to test inclusion 83 | if c[inpL].Out[writeAt] != layer { 84 | panic(fmt.Sprintf("circuit misformatted, In and Out are inconsistent between layers %v and %v", layer, inpL)) 85 | } 86 | 87 | p.Claims[inpL][writeAt] = finalClaims[i] 88 | p.QPrimes[inpL][writeAt] = nextQPrime 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /gkr/verifier.go: -------------------------------------------------------------------------------- 1 | package gkr 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sort" 7 | 8 | "github.com/consensys/gkr-mimc/circuit" 9 | "github.com/consensys/gkr-mimc/common" 10 | "github.com/consensys/gkr-mimc/poly" 11 | "github.com/consensys/gkr-mimc/sumcheck" 12 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 13 | ) 14 | 15 | func Verify( 16 | c circuit.Circuit, 17 | proof Proof, 18 | inputs []poly.MultiLin, 19 | outputs poly.MultiLin, 20 | qPrime []fr.Element, 21 | ) (err error) { 22 | 23 | nLayers := len(c) 24 | 25 | if !reflect.DeepEqual(qPrime, proof.QPrimes[nLayers-1][0]) { 26 | return fmt.Errorf("initial qPrime does not match with the proof %v %v", 27 | common.FrSliceToString(qPrime), 28 | common.FrSliceToString(proof.QPrimes[nLayers-1][0]), 29 | ) 30 | } 31 | 32 | // Pass the initial claim into the proof, because the prover does not compute it 33 | // For a matter of immutability : the old value of the claim is saved so we can put it 34 | // back in place before returning 35 | oldClaim := proof.Claims[nLayers-1] 36 | proof.Claims[nLayers-1] = append(proof.Claims[nLayers-1], outputs.Evaluate(qPrime)) 37 | defer func() { proof.Claims[nLayers-1] = oldClaim }() 38 | 39 | for layer := nLayers - 1; layer >= 0; layer-- { 40 | if c.IsInputLayer(layer) { 41 | // It's an input layer 42 | // No, more sumcheck to verify 43 | break 44 | } 45 | 46 | if err := proof.testSumcheck(c, layer); err != nil { 47 | return fmt.Errorf("error at layer %v : %v", layer, err) 48 | } 49 | } 50 | 51 | for layer := range inputs { 52 | if err = proof.testInitialRound(inputs, layer); err != nil { 53 | return err 54 | } 55 | } 56 | 57 | return nil 58 | 59 | } 60 | 61 | func (proof Proof) testSumcheck( 62 | c circuit.Circuit, 63 | layer int, 64 | ) (err error) { 65 | 66 | // First thing, test the sumcheck 67 | nextQprime, nextClaim, recombChal, err := sumcheck.Verify( 68 | proof.Claims[layer], 69 | proof.SumcheckProofs[layer], 70 | ) 71 | 72 | if err != nil { 73 | return fmt.Errorf("error at sumcheck layer %v %v - claims %v", layer, err, common.FrSliceToString(proof.Claims[layer])) 74 | } 75 | 76 | var expectedClaim fr.Element 77 | // 2 is because in practice, a gate cannot have more than two inputs with our designs 78 | subClaims := make([]*fr.Element, 0, 2) 79 | 80 | for _, inpL := range c[layer].In { 81 | 82 | // Seach the position of `l` as an output of layer `inpL` 83 | // It works because `c[inpL].Out` is guaranteed to be sorted. 84 | readAt := sort.SearchInts(c[inpL].Out, layer) 85 | 86 | // Since `SearchInts` does not answer whether the `int` is contained or not 87 | // but returns the position if it "were" inside. We need to test inclusion 88 | if c[inpL].Out[readAt] != layer { 89 | panic(fmt.Sprintf("circuit misformatted, In and Out are inconsistent between layers %v and %v", layer, inpL)) 90 | } 91 | 92 | if !reflect.DeepEqual(proof.QPrimes[inpL][readAt], nextQprime) { 93 | return fmt.Errorf("mismatch for qPrimes between sumcheck and proof at layer %v", layer) 94 | } 95 | 96 | subClaims = append(subClaims, &proof.Claims[inpL][readAt]) 97 | } 98 | 99 | // Run the gate to compute the expected claim 100 | c[layer].Gate.Eval(&expectedClaim, subClaims...) 101 | 102 | // Evaluation of eq to be used for testing the consistency with the challenges 103 | // Recombines the eq evaluations into a single challenge 104 | tmpEvals := make([]fr.Element, len(proof.QPrimes[layer])) 105 | for i := range proof.QPrimes[layer] { 106 | tmpEvals[i] = poly.EvalEq(proof.QPrimes[layer][i], nextQprime) 107 | } 108 | eqEval := poly.EvalUnivariate(tmpEvals, recombChal) 109 | 110 | expectedClaim.Mul(&expectedClaim, &eqEval) 111 | 112 | if expectedClaim != nextClaim { 113 | return fmt.Errorf("the expected claim and the final claim of the sumcheck do not match for layer %v", layer) 114 | } 115 | 116 | return nil 117 | } 118 | 119 | // Performs one of the GKR checks for the inputs layers 120 | func (proof Proof) testInitialRound(inps []poly.MultiLin, layer int) error { 121 | qPrime := proof.QPrimes[layer][0] 122 | claim := proof.Claims[layer][0] 123 | actual := inps[layer].Evaluate(qPrime) 124 | 125 | if actual != claim { 126 | return fmt.Errorf( 127 | "input layer check failed \n\tlayer %v \n\tclaim %v \n\teval %v \n\tqPrime %v", 128 | layer, claim.String(), actual.String(), common.FrSliceToString(qPrime), 129 | ) 130 | } 131 | return nil 132 | } 133 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/consensys/gkr-mimc 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/AlexandreBelling/gnark v0.5.1 7 | github.com/consensys/gnark-crypto v0.6.1-0.20220110145513-493bb1c180d9 8 | github.com/kr/pretty v0.2.1 // indirect 9 | github.com/pkg/profile v1.5.0 10 | github.com/stretchr/testify v1.7.0 11 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 12 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect 13 | ) 14 | 15 | replace github.com/AlexandreBelling/gnark v0.5.1 => ./pkg/gnark 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= 2 | github.com/consensys/gnark-crypto v0.6.1-0.20220110145513-493bb1c180d9 h1:NSmw/VuOS3KCClXAijCQ6dJVtCSMbTwK/M8ulvPCvmY= 3 | github.com/consensys/gnark-crypto v0.6.1-0.20220110145513-493bb1c180d9/go.mod h1:PicAZJP763+7N9LZFfj+MquTXq98pwjD6l8Ry8WdHSU= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ= 8 | github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= 9 | github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= 10 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 11 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 12 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 13 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 14 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 15 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 16 | github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= 17 | github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= 18 | github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= 19 | github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= 20 | github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= 21 | github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug= 22 | github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= 23 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 24 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 26 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 27 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 28 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 29 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 30 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= 31 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 32 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 33 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 34 | golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 35 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= 36 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 37 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 38 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 39 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 40 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 41 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 42 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 44 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 45 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 46 | rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= 47 | -------------------------------------------------------------------------------- /hash/ark.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import "github.com/consensys/gnark-crypto/ecc/bn254/fr" 4 | 5 | // 100 random strings of x's and y's smaller than q 6 | // generated in Python using 7 | // str(random.randrange(21888242871839275222246405745257275088548364400416034343698204186575808495617)) 8 | // Naturally, they only works if we use ecc/bn254 9 | var xArr []fr.Element 10 | var yArr []fr.Element 11 | 12 | // Arks contains the round constants used by gMimc 13 | var Arks []fr.Element 14 | 15 | func initArk() { 16 | 17 | xArr = make([]fr.Element, 100) 18 | yArr = make([]fr.Element, 100) 19 | Arks = make([]fr.Element, 100) 20 | 21 | // Set the values of xArr 22 | for i, x := range [...]string{ 23 | "20935598419934535773939197275086671883976426238465984892689893274776109706840", 24 | "19995897090389691161372090451896659673911988660285271218219666866949687188766", 25 | "6544978823936796530307517047600780472974960393678464677682762811474267306764", 26 | "13423806261013761719286154447241277236171190620841146940924235299828216919004", 27 | "483036459520899251433481070174077337588234874789277590744459238550542022336", 28 | "11834122008725238397540798432599519064159958632173342040922104816182977650323", 29 | "6214250652825909336042102302914495542350963467874575855196090802530598798684", 30 | "7170726223133665141339252286212434611716644939495742319100725465588758577975", 31 | "3628752144011358518780390633429771997829076043796856962918680101301334326717", 32 | "10995056073584958662633592981244351787463906518240783240839224906624462658946", 33 | "10316136354139206944774094995168509765583756998417835440724193449385724904064", 34 | "6887293571249137951954974829583961948681874052171040521689627599170432187821", 35 | "17679014255108003658018203734964304787939319117285088271374270937340979331088", 36 | "4248252868936221819819290432848672487864423222808359880150462211602494495796", 37 | "10838576758032918715215191218446338417881585877259434456182478933129676952115", 38 | "753652442023272502362984997608685353764283209436968416683175270126230919486", 39 | "17745848830945769523932902637022183346001315979559375020608322369317158742340", 40 | "15048113314350148976574068237122414221875489926515297088305374878755157475050", 41 | "9491283014185515531244157897882648088184249254485820722661277551261599129642", 42 | "4258129065487469195892729988726472140786148309087149042478972442852967621345", 43 | "5111992792738425366152826074931113105810075868173875109087887872181607527888", 44 | "17207570210536169229421619009051443126781777633310289161609677098462635375221", 45 | "11676857416455659283983679396450067066140186418683989409364053576635898614423", 46 | "17578181423085285483685548800623294414373345823336170149312376005897219810923", 47 | "16629480769556236124982722400255457396770056235490451205148220949109249575367", 48 | "8839117604736565297825617688776310968162145135592273200563883313600739705885", 49 | "1735306411626188875687496234040719696454614787672232638784102468564002525311", 50 | "1062690232563009764870609669688237582570790433844825729996364011137988522972", 51 | "10056158341775369056001372101480180671326250547841758558233836566253007463970", 52 | "12861676007242550257747460291682219623619046064334768768792486866981698984069", 53 | "10364293972625138632177507866607968975753973525749205969026186608034214999319", 54 | "3381310948007105111429534263163256529484284505313401397404454363021417678968", 55 | "5391644308855774040931138482213992529012013176810874072239316963239013418901", 56 | "5739902267984637424546093272789773631580642499147923310270611599926582772083", 57 | "20806058531191007742886071003337288700928126171889422302290828657325840002837", 58 | "1953912345352260990400998391590986884132317738887679700094700754944948270710", 59 | "6334107054196823364302483562051891503925658093315615419922191758990899370338", 60 | "14436872750302522612547342263479874128706597085400185002907467694318996568975", 61 | "7694649897497530249728485136604371802655316385667275552509740182346397459671", 62 | "19618789529866917206069877900825601316878403452146176103972243181682070108596", 63 | "2021370341346862593327202593746514936950566619994961739175029081766474759734", 64 | "8897565274181604271438529286082177443144026336582705290182549375856364873271", 65 | "2679449823911283247300632056535751191249091461631889726903592697866653888173", 66 | "20129895186588343211831804279732468237555926468631283725949859368323156032328", 67 | "13600013317168617656421359748934876309106612346394267276197743245554826828044", 68 | "4187390986581800164301365868618807900222521766711295721325192212473641955078", 69 | "17190735820869638808784313330525115090358120278752459902349090601764659379986", 70 | "7860693206750828547022882524196397915704308902872703437413899492234638169950", 71 | "263587684044448289719429684465217979694231526668589674423417665681979978672", 72 | "12954057757246385431776838652377320956920815107618502944864994612010557053917", 73 | "10625755329183920915841453005657772997523286066913835639471907499702448594328", 74 | "16175361036106772273053984695425631085622758695469708623031329115839090608091", 75 | "7924643236665918037726545377739468766171745922844271871159360253789090651662", 76 | "14745943428673947439440436509001772198518090233941952319627228181057581147947", 77 | "5200110725015095224900900401491425448872414730842690281649501292204903596258", 78 | "14844515653194611243517183917188743040195272988340890757893132638115648419361", 79 | "18284597462748499391597422334682707085291493485018550859047712392990049612585", 80 | "3312757768539360866544546712719153939543130555858880701206518185577866044127", 81 | "11494491995355104110478447591843802527037727080288399836711586597617930772246", 82 | "16270250187260095576918281103126175126400434232898666530090704942769826010320", 83 | "8680260951893149973525640389344225710096038075121190733144198459306153458739", 84 | "9081613210888370454812488310849186028647077567446266762039801934739912763318", 85 | "10956462511589282027775497802647259672358929202388777534828986598277518510940", 86 | "13370913492321313472348616113510256836815142270399966460977455242823363372922", 87 | "13435079246781743384843720328820403206129294071479578706749467351260674442286", 88 | "18388272646089823700512532610933652257193724639728928401647812388564816035072", 89 | "4824438688649902010762908031802598452324416083897401196317026545608670128170", 90 | "16639475199568140702564184960648997909998564302491101536025960422620186396737", 91 | "9698386349195157718988480221578364140594852561835995069755014032700861137184", 92 | "184730549537418551827887128923167602075009782174981658579083378380561403166", 93 | "5624105519689291645919891509613328425168570010590845601140173844081019857980", 94 | "7450716761741930677175278400352080652394237300409198906696001754049674530245", 95 | "5371470382394845195824877337361558168063810572261588627438109543648132090378", 96 | "15916615953942210085265747284146959402033948678792411724876166650637967413112", 97 | "17530212325321880583076096442221773546008840067636999101801153102182082050803", 98 | "3130575017506563486379643269706590265437956588003605259886854795479035081161", 99 | "21602532357228503237027076470426344915762613291631264074837835884431881157704", 100 | "18175480348381516617958634462144151473385342682669306435382223997224577647885", 101 | "11701690718026895346824771844850592436088535534550764165534456114515752003199", 102 | "12269837419960548249728971163700132941456486865685239188300851998035846614389", 103 | "2400291724637078032895220206931668428857913251539696626122517390770119598631", 104 | "3654343830276165868300300278633476909969601631321485759949471116592609195616", 105 | "17235144671932366808828254897445223424297026530200129204235663184234890499384", 106 | "20506680052641797427690296883223523332483431790083362449544093134410861331851", 107 | "17331504954904950505856350254389798927000070544870687548919051531188198807950", 108 | "6804161102614327828976510754243929743759943219061856517156987357363944829891", 109 | "2317479567028904415200602472561912467973945115634988693464542048750799278467", 110 | "18090469109511824152179049458653009408110373299245056594039366425863625295848", 111 | "10089848084586654625732745860585123183877862273820914158173224863878469115856", 112 | "1395836226546105948872057125025556411270566848892612832821620203563889793940", 113 | "10721598754291822821503791097285933118105954737347233142637972326192311886410", 114 | "8359345204686797469951453420353417935055941782617632382536541727457473046091", 115 | "11837750283757807457455756860464208427747288399432375587762853620364480448031", 116 | "15301971233817766320695731507409466149968450661632089583616278484482700364566", 117 | "6117002297131317426052982382004995803437168289348556329802949005764517586689", 118 | "8903395600979830544130884370204757215790326174543487725690925685601847291516", 119 | "13790842991390267676149228422665487445151021796150434526967048103377183722083", 120 | "9035559823192678393011406366902457990596065627959572306182158186900207605358", 121 | "8655223615688285377345725376026753443683911514166015415235267767049944934264", 122 | "17186780836005891388812353377183471046567173005401842455526923306475002070709"} { 123 | 124 | xArr[i].SetString(x) 125 | } 126 | 127 | for i, y := range [...]string{ 128 | "7889220331855942390152844458153232881279298126168020718835532394961205897587", 129 | "4107703593865508008152532058450894262094481424817058071600894405707215391652", 130 | "2981965480119979542849598348825849434223710590643429757794916099804474854278", 131 | "6510902050797677829384445736759731040248069172634739272245379909319626488073", 132 | "8522870073131781488016212969787534099137132628889578006129058741893408941181", 133 | "11232025606036473857746482629338463504030960124294186766713483778447938231338", 134 | "21108530142895439895654172761661150753777692245579682285942822684968978554433", 135 | "10464664817782197858046869676929914985501473844890126267436644424377450552963", 136 | "9556974973279526871831633641120260160779911188908156358772760560288666751418", 137 | "1241372567008212377746166279809965298086156428510111101318502387802761219670", 138 | "16965808995810370462626517202842630591642545143655935980600413775621762532203", 139 | "14081836386157266284725623730380606274635205225974409983914017720589945843700", 140 | "15717493831146887482628108329054911883169147273489756037945534099976070774027", 141 | "18593090576191442420050903576531433518340171805085928714205763484336633105309", 142 | "1180731836490206389102149955923245281301919815522901801411607002856506167445", 143 | "5630609659278558017493034142969725450091515674276376879425166022960595487458", 144 | "21148350670392926881733606141800108701428090781255797670919919442016299030656", 145 | "15981546030195995367874935019352512874790725250109867282199269312633699915629", 146 | "7128052015328083284771085073609010750898929158617766974896019800003730550665", 147 | "6454471518821978428632182821258703281716542043625557058657747082108395391128", 148 | "18414607559334082314582901653915199096740838481712022680971572855497532846606", 149 | "4183726831064928874612108256656246798198704604512003866935655552220421419091", 150 | "507109504624490978450119158894577498030885167514463224819635968665207473223", 151 | "4808945797743944532330222116938040860765125452102486515966197626781851453765", 152 | "596504558625166257059093301467659831107500064953902351961806116733248199902", 153 | "1891458151530095404569000804740697696724660679385944694286672437478907510222", 154 | "12427768852750863462657510524722931983002850806185662441399170494087988656133", 155 | "12524602396618143105360965068865400589520832232935398577200379636653421496829", 156 | "10317176319397026634788809540621588848523541533718318733088702546661989262007", 157 | "1580596918288326863348722923866523000423264136344986653906336202047212027048", 158 | "6929143459167000241672166204183430630078568358548410719216285168231414683031", 159 | "10173256998300402553059050850081131439790068458053436588904913588514358470417", 160 | "9394145954207241148089853874036143168290380165365020395840910959745476073307", 161 | "13135390516961959054404413834161737161633928206137140011709268875521052233737", 162 | "12564350986146389941548491828894587312126483111975633109578712321107060383752", 163 | "1376491500521760150709492907973860555502715251541536838923266365618931406830", 164 | "8330775466866774738520407826351455126792841542039504412287605575890891747192", 165 | "8701576500767456261196164973750281031375892213475189335508532808123885067373", 166 | "16030142974555072707224309501362306430498788900149770180489521885630856377412", 167 | "17009220094833048875569562275612920502823608442311519823143885493886432964057", 168 | "13892080216395375698746931560259176937443429073833099032912514477312685821892", 169 | "16506694287892680248215076743413944982943234456066862229941320747945996007224", 170 | "2146811437233560927081428012168694503239422028313247606065184935643917198701", 171 | "3976229319282941213618566157961663797083392331239007102357688315789164689026", 172 | "16265853792125339173976869281862044762968348177681140751253978979029225522530", 173 | "14536954120167069032804365361277174806190017485542719968196592398476446880589", 174 | "18130529534729850307847740791671659268675809840961829951251618740400043870764", 175 | "14338057265451744973089233302821749361628076042233938369249313578334710172636", 176 | "19647231889458447797501252411484979149300154772839245739082606313523368528273", 177 | "20290678043421884446850792415593402100334966568961431979799398362492870969221", 178 | "7317436958444775238210672141424321168189998273907308555373508929036274633413", 179 | "9234435097223614475280721625079061129423171578705836489564699916616318385788", 180 | "5043619600308283288967791653456575033009808188361120667517975816759838969455", 181 | "14140513900676850412704402871686036764884759901882686270251696588209026919444", 182 | "2047328283108159420315021324644538293984254000489784806807390746008287238332", 183 | "5814411721062517079967797771653601251038338525707899051655286081361084310522", 184 | "6341197767327335307089281867928368166774239090958491991541758289006398667317", 185 | "9435915283405645088323234373771400328694005263909585840762615241219923218643", 186 | "13387243789278120572730908735093978599880866192723671043240558727701040341664", 187 | "14922064767941059898802635660418998171109153770248849019192832769461096614847", 188 | "5310923943943687463340194424403834984546129832719846198692245211825450770899", 189 | "851009140783773772512403908815075765128031465870408440630408820288915351355", 190 | "16697144460359443610930522567232593392051545681299784638301862128036778257566", 191 | "13669400607027362246135074818354681057821387163359271214689106684804762311417", 192 | "11141011258204312644640174523914553682780271756087434224860558481473358612359", 193 | "21043923187075097875733402828757809791382200839033414339963074884114432838476", 194 | "6774107457039269033319588169677712019543899952764388791605935463346806236879", 195 | "1431660334255478512185227973825287470142674265745864558177025018012516824381", 196 | "4695946731993663641084212622482542242369780629645938409682696955930468198734", 197 | "11288519124585374749333053504198144187726732736459576298847630122944821247265", 198 | "6627077352609406500088975387642304579089554332186025538151234828620879330636", 199 | "6482896472212179002872768082407898635388964565877980132395145112424810601918", 200 | "11001682864775142791446475972658780537632277347220793046046865329880263266846", 201 | "4277697710742254726581057339004435748034039969688558621456719243137568270875", 202 | "9481973311808249476289720795288455592483220972797058963968311255382356691809", 203 | "19971772865700811448218056122573347998664057348591672238129930017824012213547", 204 | "6110971391917572513629143113148458010047250352895271416327657609200332738295", 205 | "10208083437479749091663769290880341696951210333142239058982798491933369634658", 206 | "19371140597274853679292375926419652865869135313465054712996746675235918739169", 207 | "20971661560259707098854603768203949465180509867854450112616175986484870421138", 208 | "21838330569261441578087646010525345708120479655451726406087932484095187695549", 209 | "1076468390231167140360365402549282875608448404587397878869395031512647545868", 210 | "6092562878013814855183104955777715456621614617862565177117435129255704079816", 211 | "18170161495387533917334964667662143192347640390128446556717056467963418198054", 212 | "12109168190744162366750385102312029693950407650008338509019026459317895835309", 213 | "2544515291684966855280435972478516040217778310392748651232105437618227749994", 214 | "15507833383654996913475404388196490841449813254954018401673216235880494534754", 215 | "9380720655920370046211204946052206765055511343725830684785673543023741993312", 216 | "4075185557495157137658600992091098641694110615629352438267813211136993876650", 217 | "20717870862653987407155106996159787542440325578024314005823385621372755326674", 218 | "4815783172417066630729630018577795817279508567440163468943586637205331905419", 219 | "10191708984143774877397162783302180085783051650391418734913757161058566615716", 220 | "12709179554712235414695320020168637754351757243494418321394148981472365589549", 221 | "11713908815142029267977623070427886421590639997586227181669025742969514806643", 222 | "14975567209499876151412402594801810332666577737007342935060399369293114492453", 223 | "4297848205619735573379913160795364592161838502585648079402224506267602723902", 224 | "16395392222317019456016218924241407228380986050333722208118133679977446245065", 225 | "12959937773704006145335986634830507188953799862425156620262967430053968980597", 226 | "10611666550972266129080819432693421560818873779652376153152772165559569100498", 227 | "19720650681541961299836478555453123453649520285923438834236036850370957389533"} { 228 | 229 | yArr[i].SetString(y) 230 | } 231 | 232 | for i, a := range [...]string{ 233 | "12136087830675299266258954793902014139747133950228959214677232437877732505267", 234 | "1949262915742616509924639087995052057439533688639443528419902050101511253219", 235 | "19696026105199390416727112585766461108620822978182620644600554326664686143928", 236 | "4837202928086718576193880638295431461498764555598430221157283092238776342056", 237 | "20604733757835564048563775050992717669420271708242272549953811572144277524421", 238 | "3211475718977376565880387313110606450525142591707961226303740825956563866938", 239 | "20322324153453907734144901224240556024026610989461831427966532469618423079473", 240 | "7934973319760976485021791030537501865656619719264759660277823446649082147312", 241 | "930415486950737914013769279801651475511858502656263669439342111832254726850", 242 | "13233069796564124818867608145207094060962111073577067140167532768756987412088", 243 | "21056848409984369169004352317081356901925342815742628419179104554944602838181", 244 | "7609965049060251551691128076452200168193674628022973404475256739231396295027", 245 | "2875569989607589080323784051637402876467307402338253586046146159474138388518", 246 | "1638405415371537336552461768985626566464351501714515162849864399640580102578", 247 | "15419971351110340210204119021937390512827818431981795058254849419538982779889", 248 | "6266520897908297023042570116888246900567139531590020770952602488348558265061", 249 | "14039893748423238973883972164169603996654684831868979824941451015257316714495", 250 | "17495914808944773938291362208338085117997720817217450529979495804567647637318", 251 | "5560043367941296663296908882927102318709803693167554571558317138775165457566", 252 | "679516368620232917376775416937269411660606739225677609931160531673791709159", 253 | "20771173458695616083113300195745636859853909352816078680615698162064064254487", 254 | "9061949945732349497554037671487309408019595175888253563639003740846345173268", 255 | "6589283089756049627166577132264171123481224689360969470370811604100156764233", 256 | "3533527516202756096389060356777395269308981839403476652028917646088724581131", 257 | "5616942227850617678046840250903304358333298306807220766347746809267032455299", 258 | "19134688161961603498559262912818080142324701420702686735061929518115443109100", 259 | "4455012138630075486254307533606858125267214265131501816598058811172692328101", 260 | "10793166202851599893237663367817743308639336679992856426902640679097285197834", 261 | "15056866545271068254544312503685146788561860865190761206515636207319868585312", 262 | "18588820303015761108497689698317977183405401884497470414262723108370171102023", 263 | "19833892328086915832797048699984794667331247199325415348986995942708708405059", 264 | "898953396730445825940003488465251983486876859688881180356386693085994272154", 265 | "11340240789075205057343229997968129213431697722131695573513514123825351727265", 266 | "19892667690111598338150747633561348627404155092311695371528902260306500224478", 267 | "5675265566752264879035374032667220698134217173464462106243551163373050847632", 268 | "20083467967117235977304805384179142748526655108920556941636271719496304583696", 269 | "17241462814856629393310447955833139605117065616411368427339901837278291194631", 270 | "459523005856707668283348902081873079675765291191967460823756003540662376503", 271 | "7536104111246397807428611027267470467060028762437526544651879177391629902952", 272 | "10590470262497492482063013216166955143786637478931633085736787678537413766247", 273 | "15043612058042949906959587136396856430320834576029342208816587940851697052752", 274 | "7330056066898340139347678689385900107077259949111182422329627141831221863855", 275 | "19916604609861621491722130626309103960340872015429661158412002662197451448107", 276 | "308306529997070213533139133875862443286398019572914380538015645187505823318", 277 | "16861578445042558674239888228729122953078668310622552358464602254565597746836", 278 | "2366359755939099031669574080770668941312280240662985815253933900628948645191", 279 | "8788914401574223424632781718358228468459844175515383031440552306265712071450", 280 | "9779987514099704007279027021166746630318148945176798063151889326895889174458", 281 | "13135609303826200140065220669831303027168227375851483214161433389386937614136", 282 | "11882415982617123710903704093174071260801610004108501667550482610326464450188", 283 | "4694986622157183973572682165500777613739137314689019399776788204304376634992", 284 | "2808898114262898635818480138517494241567797735868474755455378194917584076537", 285 | "5948815563212137849669729139804279433531777993372036737258762483412800936524", 286 | "8496838141077262636623013469780283761807018011829013896066741949043911303482", 287 | "13702702800086118773314822629146457886421384618509027653511022100403608735978", 288 | "128688574990165958444408798980207546660746573848805400153153989929390707086", 289 | "12715895361575110453437591483121803013655602937865783580504131050519779681504", 290 | "17179978120180957050330523849284167936336391317937861638141613431407020295629", 291 | "11588459002841336587102129061065070154347559648088906077759949716914737879289", 292 | "168729015953654988689927854892774175085256078299955360517770595983890795563", 293 | "17165830632129355357506266148952557268171444871293207481635264037052195045134", 294 | "12285138422811175780558426551904715989773712718224312428395575235466952713940", 295 | "1987154593247807270868347506717058470421782960151084491475247158475586184486", 296 | "936832494647391757736430222708683185711203089414475274201868885273866499292", 297 | "2763903754000480287708688930433393942277464250028040958350868219711066983212", 298 | "14557524245952597893636674984376061034140209715056307601099498105010811817976", 299 | "8621083546529398784671346534967410376642366851765751955141670352937262262964", 300 | "16627133697950876223520571090822797284529950315549243443213268507732589809668", 301 | "3037237623056839958281909328577143274349259163243900007898639886427035545715", 302 | "12995322444898226109150040488092296918287501169681313486450928080265859678431", 303 | "2733175139613460118331091475705229587335989342539982599327578628225987296693", 304 | "6330904024850799154241468252437391268363906860268077269679635105930910493698", 305 | "6737293883333053574354581827330797956286937344085463211929388455105153381840", 306 | "5169833748253678861646459610440007606812480863902145153341918680460199744937", 307 | "5663342152029876725337105797615457704649755088730278042317883168997297323658", 308 | "7823289338543622859281063471306327640697795333152031235270922571768144390442", 309 | "1340620010762718653929597746951102533345616951097596331852240652037854160258", 310 | "14897728869802140961598204911995631203157447569404601325674568226982309954150", 311 | "392018124866990209108785883333829486897323525593091953436038191276969589879", 312 | "435898044557960665437580260079284983588895103844486797156208731954139457048", 313 | "9993497896703025989867048646043173418345536355715949769299454237797386286833", 314 | "15450930933516186552123777405659014411420729103535031826405440554766901684012", 315 | "20903034268582375477673219601216374844076505392263195573819638253963628987677", 316 | "3482242022270674095230150345947605407241554167884470462727496514965587717020", 317 | "7327691729979824302166499451919150969294811677490604640371561147744223396077", 318 | "20320351461219902734279664936551332285309420491532838228883116430144043470224", 319 | "10080172065834582431913901068278960033644520993565022600527798146227389706243", 320 | "7585484857655643314874927439430217827126382955320388941459286281381839302612", 321 | "7020483570292313692729758704383267761829627767486597865215352770024378363713", 322 | "9412915321043344246413050595015332915226967771752410481732502466528909535915", 323 | "97172793343716602779526815707427204724123444268829991232994756079285191657", 324 | "9899367385098696963034612599611804167993369962788397262985493922791351318920", 325 | "20493102078330064462134068336666924427323601025383617983664121148357421387185", 326 | "3761041932368006457845986014429804620297172145142418054203286909040968118241", 327 | "1538739698002044525972417606140809601347518204915820728384854667109147333511", 328 | "13802243875617990810077229053159639403189861626210898918796014575383062790441", 329 | "14802416169101027248236235356384528498867388780049957297916199959694575989798", 330 | "12855744726651850023311564252614781422504814539761023618408723113057440886558", 331 | "3017365043038086323648780208528621420394032286007989775391627155784248978766", 332 | "6315674106586331242761612192226077184856834393892197849679034296526217823177", 333 | } { 334 | 335 | Arks[i].SetString(a) 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /hash/gmimc.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 5 | ) 6 | 7 | // GMimcT2 is a hasher for t = 2 8 | var GMimcT2 GMimcHasher 9 | 10 | // GMimcT4 is a hasher for t = 4 11 | var GMimcT4 GMimcHasher 12 | 13 | // GMimcT8 is a hasher for t = 8 14 | var GMimcT8 GMimcHasher 15 | 16 | func initGMimc() { 17 | GMimcT2 = GMimcHasher{t: 2, nRounds: 91} 18 | GMimcT4 = GMimcHasher{t: 4, nRounds: 91} 19 | GMimcT8 = GMimcHasher{t: 8, nRounds: 91} 20 | } 21 | 22 | // GMimcHasher contains all the parameters to describe a GMimc function 23 | type GMimcHasher struct { 24 | t int // size of Cauchy matrix 25 | nRounds int // number of rounds of the Mimc hash function 26 | } 27 | 28 | // Hash hashes a full message 29 | func (g *GMimcHasher) Hash(msg []fr.Element) fr.Element { 30 | state := make([]fr.Element, g.t) 31 | 32 | for i := 0; i < len(msg); i += g.t { 33 | block := make([]fr.Element, g.t) 34 | if i+g.t >= len(msg) { 35 | // Only zero-pad the input 36 | for j, w := range msg[i:] { 37 | block[j] = w 38 | } 39 | } else { 40 | // Take a full chunk 41 | for j, w := range msg[i : i+g.t] { 42 | block[j] = w 43 | } 44 | } 45 | g.UpdateInplace(state, block) 46 | } 47 | 48 | return state[0] 49 | } 50 | 51 | // UpdateInplace updates the state with the provided block of data 52 | func (g *GMimcHasher) UpdateInplace(state []fr.Element, block []fr.Element) { 53 | oldState := append([]fr.Element{}, state...) 54 | for i := 0; i < g.nRounds; i++ { 55 | AddArkAndKeysInplace(state, block, Arks[i]) 56 | SBoxInplace(&state[0]) 57 | InPlaceCircularPermutation(state) 58 | } 59 | 60 | // Recombine with the old state 61 | for i := range state { 62 | state[i].Add(&state[i], &oldState[i]) 63 | state[i].Add(&state[i], &block[i]) 64 | } 65 | } 66 | 67 | // InPlaceCircularPermutation moves all the element to the left and place the first element 68 | // at the end of the state 69 | // ie: [a, b, c, d] -> [b, c, d, a] 70 | func InPlaceCircularPermutation(state []fr.Element) { 71 | for i := 1; i < len(state); i++ { 72 | state[i-1], state[i] = state[i], state[i-1] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /hash/hash.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | func init() { 4 | initArk() 5 | initPoseidon() 6 | initGMimc() 7 | } 8 | -------------------------------------------------------------------------------- /hash/hash_test.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestHashes(t *testing.T) { 11 | inputs := make([]fr.Element, 100) 12 | PoseidonT2.Hash(inputs) 13 | PoseidonT4.Hash(inputs) 14 | PoseidonT8.Hash(inputs) 15 | GMimcT2.Hash(inputs) 16 | GMimcT4.Hash(inputs) 17 | GMimcT8.Hash(inputs) 18 | MimcHash(inputs) 19 | } 20 | 21 | func TestMimcCase(t *testing.T) { 22 | var x, expectedY fr.Element 23 | x.SetString("12") 24 | y := MimcHash([]fr.Element{x}) 25 | expectedY.SetString("1808205620575546259657963589762746470347087906694759866517376279978241663265") 26 | assert.Equal(t, y, expectedY, "Got %v", y.String()) 27 | } 28 | -------------------------------------------------------------------------------- /hash/mimc.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 5 | ) 6 | 7 | // MimcRounds is the number of rounds for the Mimc function 8 | const MimcRounds int = 91 9 | 10 | // MimcHash returns the hash of a slice of field element 11 | func MimcHash(input []fr.Element) fr.Element { 12 | // The state is initialized to zero 13 | var state fr.Element 14 | for _, x := range input { 15 | MimcUpdateInplace(&state, x) 16 | } 17 | return state 18 | } 19 | 20 | // MimcUpdateInplace performs a state update using the Mimc permutation 21 | // Using Miyaguchi-Preenel 22 | // In the Miyaguchi-Preenel construct, the state is used as the key of a cipher function 23 | // and the message to hash is set as the plaintext of the cipher 24 | func MimcUpdateInplace(state *fr.Element, block fr.Element) { 25 | newState := MimcBlockCipher(block, *state) 26 | state.Add(state, &newState) 27 | state.Add(state, &block) 28 | } 29 | 30 | // Iterates the Mimc rounds functions over x with key k 31 | func MimcKeyedPermutation(x fr.Element, key fr.Element) fr.Element { 32 | res := x 33 | for i := 0; i < MimcRounds; i++ { 34 | res.Add(&res, &key) 35 | res.Add(&res, &Arks[i]) 36 | SBoxInplace(&res) 37 | } 38 | return res 39 | } 40 | 41 | // MimcBlockCipherInPlace applies the mimc permutation in place 42 | // In the papier; E_k(x) = Perm_k(x) + k 43 | func MimcBlockCipher(msg fr.Element, key fr.Element) fr.Element { 44 | res := MimcKeyedPermutation(msg, key) 45 | // Re-add the state (key) to the block and put the result in the state 46 | // to update the state 47 | res.Add(&res, &key) 48 | return res 49 | } 50 | -------------------------------------------------------------------------------- /hash/poseidon.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 5 | ) 6 | 7 | // PoseidonT2 is a hasher with T = 2 8 | var PoseidonT2 PoseidonHasher 9 | 10 | // PoseidonT4 is a hasher with T = 4 11 | var PoseidonT4 PoseidonHasher 12 | 13 | // PoseidonT8 is a hasher with T = 8 14 | var PoseidonT8 PoseidonHasher 15 | 16 | // The parameters are set according to 17 | // https://eprint.iacr.org/2020/179.pdf 18 | func initPoseidon() { 19 | PoseidonT2 = newPoseidonHasher(2, 8, 82) 20 | PoseidonT4 = newPoseidonHasher(4, 8, 83) 21 | PoseidonT8 = newPoseidonHasher(8, 8, 84) 22 | } 23 | 24 | // PoseidonHasher contains all the parameters to specify a poseidon hash function 25 | type PoseidonHasher struct { 26 | t int // size of Cauchy matrix 27 | cauchy [][]fr.Element 28 | nRoundsF int 29 | nRoundsP int 30 | } 31 | 32 | // NewPoseidonHasher generates the parameters to run poseidon 33 | func newPoseidonHasher(t, nRoundsF, nRoundsP int) PoseidonHasher { 34 | return PoseidonHasher{ 35 | t: t, 36 | nRoundsF: nRoundsF, 37 | nRoundsP: nRoundsP, 38 | cauchy: GenerateMDSMatrix(t), 39 | } 40 | } 41 | 42 | // Hash hashes a full message 43 | func (p *PoseidonHasher) Hash(msg []fr.Element) fr.Element { 44 | state := make([]fr.Element, p.t) 45 | 46 | for i := 0; i < len(msg); i += p.t { 47 | block := make([]fr.Element, p.t) 48 | if i+p.t >= len(msg) { 49 | // Only zero-pad the input 50 | for j, w := range msg[i:] { 51 | block[j] = w 52 | } 53 | } else { 54 | // Take a full chunk 55 | for j, w := range msg[i : i+p.t] { 56 | block[j] = w 57 | } 58 | } 59 | p.Update(state, block) 60 | } 61 | 62 | return state[0] 63 | } 64 | 65 | // Update uses the poseidon permutation in a Miyaguchi-Preenel 66 | // construction to create the hash function. 67 | // https://en.wikipedia.org/wiki/One-way_compression_function#Miyaguchi.E2.80.93Preneel 68 | func (p *PoseidonHasher) Update(state, block []fr.Element) { 69 | 70 | // Deep-copies the state 71 | oldState := append([]fr.Element{}, state...) 72 | 73 | // Runs the cipher 74 | for i := 0; i < p.nRoundsF; i++ { 75 | AddArkAndKeysInplace(state, block, Arks[i]) 76 | FullRoundInPlace(state) 77 | state = MatrixMultiplication(p.cauchy, state) 78 | } 79 | 80 | for i := p.nRoundsF; i < p.nRoundsF+p.nRoundsP; i++ { 81 | AddArkAndKeysInplace(state, block, Arks[i]) 82 | PartialRoundInplace(state) 83 | state = MatrixMultiplication(p.cauchy, state) 84 | } 85 | 86 | for i := p.nRoundsF + p.nRoundsP; i < 2*p.nRoundsF+p.nRoundsP; i++ { 87 | AddArkAndKeysInplace(state, block, Arks[i]) 88 | FullRoundInPlace(state) 89 | state = MatrixMultiplication(p.cauchy, state) 90 | } 91 | 92 | // Recombine with the old state 93 | for i := range state { 94 | state[i].Add(&state[i], &oldState[i]) 95 | state[i].Add(&state[i], &block[i]) 96 | } 97 | } 98 | 99 | // GenerateMDSMatrix returns the MDS matrix for a given size 100 | func GenerateMDSMatrix(t int) [][]fr.Element { 101 | result := make([][]fr.Element, t) 102 | for i := range result { 103 | result[i] = make([]fr.Element, t) 104 | for j := range result[i] { 105 | result[i][j].Set(&xArr[i]) 106 | result[i][j].Add(&result[i][j], &yArr[j]) 107 | result[i][j].Inverse(&result[i][j]) 108 | } 109 | } 110 | return result 111 | } 112 | 113 | // MatrixMultiplication by a vector 114 | // The dimensions are mat[k][n] * vec[n] = res[k] 115 | func MatrixMultiplication(mat [][]fr.Element, vec []fr.Element) []fr.Element { 116 | res := make([]fr.Element, len(mat)) 117 | var tmp fr.Element 118 | for i, col := range mat { 119 | for j, el := range col { 120 | tmp.Set(&vec[j]) 121 | tmp.Mul(&tmp, &el) 122 | res[i].Add(&res[i], &tmp) 123 | } 124 | } 125 | return res 126 | } 127 | 128 | // SBoxInplace computes x^7 in-place 129 | func SBoxInplace(x *fr.Element) { 130 | tmp := *x 131 | x.Square(x) 132 | x.Mul(x, &tmp) 133 | x.Square(x) 134 | x.Mul(x, &tmp) 135 | } 136 | 137 | // FullRoundInPlace applies the SBox on all entries 138 | // of the state 139 | func FullRoundInPlace(state []fr.Element) { 140 | for i := range state { 141 | SBoxInplace(&state[i]) 142 | } 143 | } 144 | 145 | // AddArkAndKeysInplace adds the 146 | func AddArkAndKeysInplace(state []fr.Element, keys []fr.Element, ark fr.Element) { 147 | for i := range state { 148 | state[i].Add(&state[i], &keys[i]) 149 | state[i].Add(&state[i], &ark) 150 | } 151 | } 152 | 153 | // PartialRoundInplace applies the SBox on the first entry 154 | func PartialRoundInplace(state []fr.Element) { 155 | SBoxInplace(&state[0]) 156 | } 157 | -------------------------------------------------------------------------------- /poly/eq.go: -------------------------------------------------------------------------------- 1 | package poly 2 | 3 | import ( 4 | "github.com/consensys/gkr-mimc/common" 5 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 6 | ) 7 | 8 | // EvalEq computes Eq(q1', ... , qn', h1', ... , hn') = Π_1^n Eq(qi', hi') 9 | // where Eq(x,y) = xy + (1-x)(1-y) = 1 - x - y + xy + xy interpolates 10 | // _________________ 11 | // | | | 12 | // | 0 | 1 | 13 | // |_______|_______| 14 | // y | | | 15 | // | 1 | 0 | 16 | // |_______|_______| 17 | // 18 | // x 19 | func EvalEq(qPrime, nextQPrime []fr.Element) fr.Element { 20 | var res, nxt, one, sum fr.Element 21 | one.SetOne() 22 | res.SetOne() 23 | for i := 0; i < len(qPrime); i++ { 24 | nxt.Mul(&qPrime[i], &nextQPrime[i]) // nxt <- qi' * hi' 25 | nxt.Add(&nxt, &nxt) // nxt <- 2 * qi' * hi' 26 | nxt.Add(&nxt, &one) // nxt <- 1 + 2 * qi' * hi' 27 | sum.Add(&qPrime[i], &nextQPrime[i]) // sum <- qi' + hi' 28 | nxt.Sub(&nxt, &sum) // nxt <- 1 + 2 * qi' * hi' - qi' - hi' 29 | res.Mul(&res, &nxt) // res <- res * nxt 30 | } 31 | return res 32 | } 33 | 34 | // FoldedEqTable ought to start life as a sparse bookkeepingtable 35 | // depending on 2n variables and containing 2^n ones only 36 | // to be folded n times according to the values in qPrime. 37 | // The resulting table will no longer be sparse. 38 | // Instead we directly compute the folded array of length 2^n 39 | // containing the values of Eq(q1, ... , qn, *, ... , *) 40 | // where qPrime = [q1 ... qn]. 41 | func FoldedEqTable(preallocated MultiLin, qPrime []fr.Element, multiplier ...fr.Element) (eq MultiLin) { 42 | n := len(qPrime) 43 | 44 | preallocated[0].SetOne() 45 | if len(multiplier) > 0 { 46 | preallocated[0] = multiplier[0] 47 | } 48 | 49 | for i, r := range qPrime { 50 | for j := 0; j < (1 << i); j++ { 51 | J := j << (n - i) 52 | JNext := J + 1<<(n-1-i) 53 | preallocated[JNext].Mul(&r, &preallocated[J]) 54 | preallocated[J].Sub(&preallocated[J], &preallocated[JNext]) 55 | } 56 | } 57 | 58 | return preallocated 59 | } 60 | 61 | // Computes only a chunk of the eqTable for a given chunkSize and chunkID 62 | func ChunkOfEqTable(preallocatedEq []fr.Element, chunkID, chunkSize int, qPrime []fr.Element, multiplier ...fr.Element) { 63 | nChunks := (1 << len(qPrime)) / chunkSize 64 | logNChunks := common.Log2Ceil(nChunks) 65 | one := fr.One() 66 | var tmp fr.Element 67 | 68 | r := one 69 | 70 | if len(multiplier) > 0 { 71 | r = multiplier[0] 72 | } 73 | 74 | for k := 0; k < logNChunks; k++ { 75 | _rho := &qPrime[logNChunks-k-1] 76 | if chunkID>>k&1 == 1 { // If the k-th bit of i is 1 77 | r.Mul(&r, _rho) 78 | } else { 79 | tmp.Sub(&one, _rho) 80 | r.Mul(&r, &tmp) 81 | } 82 | } 83 | 84 | FoldedEqTable( 85 | preallocatedEq[chunkID*chunkSize:(chunkID+1)*chunkSize], 86 | qPrime[logNChunks:], 87 | r, 88 | ) 89 | } 90 | -------------------------------------------------------------------------------- /poly/eq_test.go: -------------------------------------------------------------------------------- 1 | package poly 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/consensys/gkr-mimc/common" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestGetFoldedEqTable(t *testing.T) { 13 | 14 | for bn := 0; bn < 15; bn++ { 15 | qPrime := common.RandomFrArray(bn) 16 | hPrime := common.RandomFrArray(bn) 17 | 18 | a := EvalEq(qPrime, hPrime) 19 | 20 | eq := make(MultiLin, 1<= 0; i-- { 35 | result.Mul(&result, &x) 36 | result.Add(&result, &coeffs[i]) 37 | } 38 | return result 39 | } 40 | 41 | // LagrangeCoefficient returns the matrix of Lagrange polynomials for the domain [[0; n - 1]] 42 | func LagrangeCoefficient(domainSize int) [][]fr.Element { 43 | // Declare the binomials 44 | binomials := make([][2]fr.Element, domainSize) 45 | for i := uint64(0); i < uint64(domainSize); i++ { 46 | var interceipts fr.Element 47 | interceipts.SetUint64(i) 48 | binomials[i][0].Neg(&interceipts) 49 | binomials[i][1].SetOne() 50 | } 51 | 52 | result := make([][]fr.Element, domainSize) 53 | 54 | for l := 0; l < domainSize; l++ { 55 | // Each iteration computes the the l-th Lagrange polynomial 56 | // on range [0, domainSize-1] 57 | accumulator := make([]fr.Element, domainSize) 58 | accumulator[0].SetOne() 59 | var tmp fr.Element 60 | 61 | for i := 0; i < domainSize; i++ { 62 | if i == l { 63 | // Skip the monomial 64 | continue 65 | } 66 | // Computes X(X-1)(X-2)..(X-i)..(X-domainSize-1) for i != l 67 | updated := make([]fr.Element, domainSize) 68 | for j := 0; j < domainSize; j++ { 69 | for k := 0; k < common.Min(2, domainSize-j); k++ { 70 | tmp.Set(&accumulator[j]) 71 | tmp.Mul(&tmp, &binomials[i][k]) 72 | updated[j+k].Add(&updated[j+k], &tmp) 73 | } 74 | } 75 | accumulator = updated 76 | } 77 | // Normalize the polynomial to have P(l) = 1. 78 | // Order to do so, we compute normalizationFactor = P(l), 79 | // and divide each coefficent by normalizationFactor 80 | var lFieldElement fr.Element 81 | lFieldElement.SetUint64(uint64(l)) 82 | normalizationFactor := EvalUnivariate(accumulator, lFieldElement) 83 | // Now divide all coefficients 84 | normalizationFactor.Inverse(&normalizationFactor) 85 | for i := range accumulator { 86 | accumulator[i].Mul(&accumulator[i], &normalizationFactor) 87 | } 88 | result[l] = accumulator 89 | } 90 | 91 | return result 92 | } 93 | 94 | // InterpolateOnRange performs the interpolation of the given list of elements 95 | // On the range [0, 1,..., len(values) - 1] 96 | func InterpolateOnRange(values []fr.Element) []fr.Element { 97 | nEvals := len(values) 98 | lagrange := GetLagrangePolynomial(nEvals) 99 | result := make([]fr.Element, nEvals) 100 | var tmp fr.Element 101 | 102 | for i, value := range values { 103 | for j, lagrangeCoeff := range lagrange[i] { 104 | tmp.Set(&lagrangeCoeff) 105 | tmp.Mul(&tmp, &value) 106 | result[j].Add(&result[j], &tmp) 107 | } 108 | } 109 | 110 | return result 111 | } 112 | -------------------------------------------------------------------------------- /poly/lagrange_test.go: -------------------------------------------------------------------------------- 1 | package poly 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestLagrangeCoefficients(t *testing.T) { 11 | domain := 7 12 | value := 2 13 | 14 | var zero fr.Element 15 | var one fr.Element 16 | one.SetOne() 17 | 18 | lagrangePolynomial := GetLagrangePolynomial(domain)[value] 19 | for i := 0; i < domain; i++ { 20 | var x fr.Element 21 | x.SetUint64(uint64(i)) 22 | y := EvalUnivariate(lagrangePolynomial, x) 23 | if i == value { 24 | assert.Equal(t, y, one, "Should have been one") 25 | } else { 26 | assert.Equal(t, y, zero, "Should have been zero") 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /poly/multilin.go: -------------------------------------------------------------------------------- 1 | package poly 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/consensys/gkr-mimc/common" 7 | 8 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 9 | ) 10 | 11 | // MultiLin tracks the values of a (dense i.e. not sparse) multilinear polynomial 12 | type MultiLin []fr.Element 13 | 14 | func (bkt MultiLin) String() string { 15 | return fmt.Sprintf("%v", common.FrSliceToString(bkt)) 16 | } 17 | 18 | // Fold folds the table on its first coordinate using the given value r 19 | func (bkt *MultiLin) Fold(r fr.Element) { 20 | mid := len(*bkt) / 2 21 | bkt.FoldChunk(r, 0, mid) 22 | *bkt = (*bkt)[:mid] 23 | } 24 | 25 | // Folds one part of the table 26 | func (bkt *MultiLin) FoldChunk(r fr.Element, start, stop int) { 27 | mid := len(*bkt) / 2 28 | bottom, top := (*bkt)[:mid], (*bkt)[mid:] 29 | for i := start; i < stop; i++ { 30 | // updating bookkeeping table 31 | // table[i] <- table[i] + r (table[i + mid] - table[i]) 32 | top[i].Sub(&top[i], &bottom[i]) 33 | top[i].Mul(&top[i], &r) 34 | bottom[i].Add(&bottom[i], &top[i]) 35 | } 36 | } 37 | 38 | // DeepCopy creates a deep copy of a book-keeping table. 39 | // Both ultilinear interpolation and sumcheck require folding an underlying 40 | // array, but folding changes the array. To do both one requires a deep copy 41 | // of the book-keeping table. 42 | func (bkt MultiLin) DeepCopy() MultiLin { 43 | tableDeepCopy := make([]fr.Element, len(bkt)) 44 | copy(tableDeepCopy, bkt) 45 | return tableDeepCopy 46 | } 47 | 48 | // DeepCopy creates a deep copy of a multi-linear table. 49 | func (bkt MultiLin) DeepCopyLarge() MultiLin { 50 | tableDeepCopy := MakeLarge(len(bkt)) 51 | copy(tableDeepCopy, bkt) 52 | return tableDeepCopy 53 | } 54 | 55 | // Evaluate takes a dense book-keeping table, deep copies it, folds it along the 56 | // variables on which the table depends by substituting the corresponding coordinate 57 | // from relevantCoordinates. After folding, bkCopy is reduced to a one item slice 58 | // containing the evaluation of the original bkt at relevantCoordinates. This is returned. 59 | func (bkt MultiLin) Evaluate(coordinates []fr.Element) fr.Element { 60 | bkCopy := bkt.DeepCopy() 61 | for _, r := range coordinates { 62 | bkCopy.Fold(r) 63 | } 64 | 65 | return bkCopy[0] 66 | } 67 | 68 | // Add two bookKeepingTable 69 | func (bkt MultiLin) Add(left, right MultiLin) { 70 | size := len(left) 71 | // Check that left and right have the same size 72 | if len(right) != size { 73 | panic("Left and right do not have the right size") 74 | } 75 | // Reallocate the table if necessary 76 | if cap(bkt) < size { 77 | bkt = make([]fr.Element, size) 78 | } 79 | // Resize the destination table 80 | bkt = bkt[:size] 81 | // Then performs the addition 82 | for i := 0; i < size; i++ { 83 | bkt[i].Add(&left[i], &right[i]) 84 | } 85 | } 86 | 87 | // RandomFrArray returns a random array 88 | func RandMultiLin(size int) MultiLin { 89 | return common.RandomFrArray(size) 90 | } 91 | -------------------------------------------------------------------------------- /poly/multilin_test.go: -------------------------------------------------------------------------------- 1 | package poly 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/consensys/gkr-mimc/common" 7 | 8 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestFold(t *testing.T) { 13 | // [0, 1, 2, 3] 14 | bkt := make(MultiLin, 4) 15 | for i := 0; i < 4; i++ { 16 | bkt[i].SetUint64(uint64(i)) 17 | } 18 | 19 | var r fr.Element 20 | r.SetUint64(uint64(5)) 21 | 22 | // Folding on 5 should yield [10, 11] 23 | bkt.Fold(r) 24 | 25 | var ten, eleven fr.Element 26 | ten.SetUint64(uint64(10)) 27 | eleven.SetUint64(uint64(11)) 28 | 29 | assert.Equal(t, ten, bkt[0], "Mismatch on 0") 30 | assert.Equal(t, eleven, bkt[1], "Mismatch on 1") 31 | } 32 | 33 | func TestFoldChunk(t *testing.T) { 34 | // [0, 1, 2, 3] 35 | bkt := make(MultiLin, 4) 36 | for i := 0; i < 4; i++ { 37 | bkt[i].SetUint64(uint64(i)) 38 | } 39 | 40 | var r fr.Element 41 | r.SetUint64(uint64(5)) 42 | 43 | bktBis := append(MultiLin{}, bkt...) 44 | 45 | // Folding on 5 should yield [10, 11] 46 | bkt.Fold(r) 47 | // It should yield the same result 48 | bktBis.FoldChunk(r, 0, 1) 49 | bktBis.FoldChunk(r, 1, 2) 50 | bktBis = bktBis[:2] 51 | 52 | assert.Equal(t, bkt, bktBis) 53 | } 54 | 55 | func BenchmarkFolding(b *testing.B) { 56 | 57 | size := 1 << 25 58 | 59 | // [0, 1, 2, 3] 60 | bkt := make(MultiLin, size) 61 | for i := 0; i < size; i++ { 62 | bkt[i].SetUint64(uint64(i)) 63 | } 64 | 65 | var r fr.Element 66 | r.SetUint64(uint64(5)) 67 | 68 | // Folding on 5 should yield [10, 11] 69 | 70 | b.ResetTimer() 71 | for k := 0; k < b.N; k++ { 72 | 73 | bkt2 := bkt.DeepCopy() 74 | common.ProfileTrace(b, false, false, func() { 75 | bkt2.Fold(r) 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /poly/pool.go: -------------------------------------------------------------------------------- 1 | package poly 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sync" 7 | "unsafe" 8 | 9 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 10 | ) 11 | 12 | // Sets a maximum for the array size we keep in pool 13 | const maxNForLargePool int = 1 << 24 14 | const maxNForSmallPool int = 256 15 | 16 | // Aliases because it is annoying to use arrays in all the places 17 | type largeArr = [maxNForLargePool]fr.Element 18 | type smallArr = [maxNForSmallPool]fr.Element 19 | 20 | var rC sync.Map = sync.Map{} 21 | 22 | var ( 23 | largePool = sync.Pool{ 24 | New: func() interface{} { 25 | var res largeArr 26 | return &res 27 | }, 28 | } 29 | smallPool = sync.Pool{ 30 | New: func() interface{} { 31 | var res smallArr 32 | return &res 33 | }, 34 | } 35 | ) 36 | 37 | // Clear the pool completely, shields against memory leaks 38 | // Eg: if we forgot to dump a polynomial at some point, this will ensure the value get dumped eventually 39 | // Returns how many polynomials were cleared that way 40 | func ClearPool() int { 41 | res := 0 42 | rC.Range(func(k, _ interface{}) bool { 43 | switch ptr := k.(type) { 44 | case *largeArr: 45 | largePool.Put(ptr) 46 | case *smallArr: 47 | smallPool.Put(ptr) 48 | default: 49 | panic(fmt.Sprintf("tried to clear %v", reflect.TypeOf(ptr))) 50 | } 51 | res++ 52 | return true 53 | }) 54 | return res 55 | } 56 | 57 | // Returns the number of element in the pool 58 | // Does not mutate it 59 | func CountPool() int { 60 | res := 0 61 | rC.Range(func(_, _ interface{}) bool { 62 | res++ 63 | return true 64 | }) 65 | return res 66 | } 67 | 68 | // Tries to find a reusable MultiLin or allocate a new one 69 | func MakeLarge(n int) MultiLin { 70 | if n > maxNForLargePool { 71 | panic(fmt.Sprintf("been provided with size of %v but the maximum is %v", n, maxNForLargePool)) 72 | } 73 | 74 | ptr := largePool.Get().(*largeArr) 75 | rC.Store(ptr, struct{}{}) // remember we allocated the pointer is being used 76 | return (*ptr)[:n] 77 | } 78 | 79 | // Dumps a set of polynomials into the pool 80 | // Returns the number of deallocated large polys 81 | func DumpLarge(arrs ...MultiLin) int { 82 | cnt := 0 83 | for _, arr := range arrs { 84 | ptr := arr.ptrLarge() 85 | // If the rC did not registers, then 86 | // either the array was allocated somewhere else and its fine to ignore 87 | // otherwise a double put and we MUST ignore 88 | if _, ok := rC.Load(ptr); ok { 89 | largePool.Put(ptr) 90 | // And deregisters the ptr 91 | rC.Delete(ptr) 92 | cnt++ 93 | } 94 | } 95 | return cnt 96 | } 97 | 98 | // Tries to find a reusable MultiLin or allocate a new one 99 | func MakeSmall(n int) MultiLin { 100 | if n > maxNForSmallPool { 101 | panic(fmt.Sprintf("want size of %v but the maximum is %v", n, maxNForSmallPool)) 102 | } 103 | 104 | ptr := smallPool.Get().(*smallArr) 105 | rC.Store(ptr, struct{}{}) // registers the pointer being used 106 | return (*ptr)[:n] 107 | } 108 | 109 | // Dumps a set of polynomials into the pool 110 | // Returns the number of deallocated small polys 111 | func DumpSmall(arrs ...MultiLin) int { 112 | cnt := 0 113 | for _, arr := range arrs { 114 | ptr := arr.ptrSmall() 115 | // If the rC did not registers, then 116 | // either the multilin was allocated somewhere else and its fine to ignore 117 | // otherwise a double put and we MUST ignore 118 | if _, ok := rC.Load(ptr); ok { 119 | smallPool.Put(ptr) 120 | // And deregisters the ptr 121 | rC.Delete(ptr) 122 | cnt++ 123 | } 124 | } 125 | return cnt 126 | } 127 | 128 | // Get the pointer from the header of the slice 129 | func (m MultiLin) ptrLarge() *largeArr { 130 | // Re-increase the array up to max capacity 131 | if cap(m) != maxNForLargePool { 132 | panic(fmt.Sprintf("can't cast to large array, the put array's is %v it should have capacity %v", cap(m), maxNForLargePool)) 133 | } 134 | return (*largeArr)(unsafe.Pointer(&m[0])) 135 | } 136 | 137 | // Get the pointer from the header of the slice 138 | func (m MultiLin) ptrSmall() *smallArr { 139 | // Re-increase the array up to max capacity 140 | if cap(m) != maxNForSmallPool { 141 | panic(fmt.Sprintf("can't cast to small array, the put array's is %v it should have capacity %v", cap(m), maxNForLargePool)) 142 | } 143 | return (*smallArr)(unsafe.Pointer(&m[0])) 144 | } 145 | -------------------------------------------------------------------------------- /profiling/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Consensys/gkr-mimc/81eada039ab4ed403b7726b535adb63026e8011f/profiling/.gitkeep -------------------------------------------------------------------------------- /prover/gadget/circuit.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "github.com/AlexandreBelling/gnark/frontend" 5 | ) 6 | 7 | // Interface to be implemented by any circuit willing to use Gkr 8 | type CircuitUsingGkr interface { 9 | Define(cs frontend.API, gadget *GkrGadget) error 10 | } 11 | 12 | // Generic wrapper circuit in which, we can plug any circuit using 13 | // Gkr. The wrapper holds all the logic for the proving etc... 14 | type Circuit struct { 15 | Gadget GkrGadget 16 | InnerCircuit CircuitUsingGkr 17 | } 18 | 19 | // Wraps the given circuit into a `Circuit` object 20 | func WrapCircuitUsingGkr(c CircuitUsingGkr, opts ...GkrOption) Circuit { 21 | w := Circuit{ 22 | InnerCircuit: c, 23 | Gadget: *NewGkrGadget(), 24 | } 25 | // Applies the options 26 | for _, opt := range opts { 27 | opt(&w) 28 | } 29 | return w 30 | } 31 | 32 | // Implements `gnark`s circuit interface 33 | func (c *Circuit) Define(cs frontend.API) error { 34 | if err := c.InnerCircuit.Define(cs, &c.Gadget); err != nil { 35 | return err 36 | } 37 | c.Gadget.Close(cs) 38 | return nil 39 | } 40 | 41 | // Assigns for the subcircuit 42 | func (c *Circuit) Assign() { 43 | c.Gadget.InitialRandomness = 0 44 | } 45 | 46 | // Options for the `Circuit` constructor 47 | type GkrOption func(c *Circuit) 48 | -------------------------------------------------------------------------------- /prover/gadget/cs.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "github.com/AlexandreBelling/gnark/backend" 5 | "github.com/AlexandreBelling/gnark/frontend" 6 | "github.com/AlexandreBelling/gnark/notinternal/backend/bn254/cs" 7 | "github.com/consensys/gnark-crypto/ecc" 8 | ) 9 | 10 | // R1CS wraps gnarks r1cs to add its own datas 11 | type R1CS struct { 12 | r1cs cs.R1CS 13 | // Maps all subparts of the multiexp to their respective inputs indices 14 | pubGkrIo, privGkrIo, pubGkrVarID []int 15 | pubNotGkrVarID, privNotGkrVarID, privGkrVarID []int 16 | // Pointer to the proving key 17 | provingKey *ProvingKey 18 | } 19 | 20 | // Wraps the gnark circuit compiler 21 | // Will only work on groth16 with bn254 22 | func (c *Circuit) Compile() (R1CS, error) { 23 | 24 | // Compile the variables 25 | r1cs, err := frontend.Compile(ecc.BN254, backend.GROTH16, c) 26 | if err != nil { 27 | return R1CS{}, err 28 | } 29 | 30 | internal, sec, pub := r1cs.GetNbVariables() 31 | priv := internal + sec 32 | 33 | // Map of the variable IDs for deduplication 34 | // And map to a position 35 | varIdToPosition := make(map[int]int) 36 | ioStore := &c.Gadget.ioStore 37 | 38 | // Map every Gkr variable IDs to their occurence in the ioStore 39 | ioVarIDs := ioStore.VarIds() 40 | ioIsConstant := ioStore.VarAreConstant() 41 | 42 | for ioPosition, varId := range ioVarIDs { 43 | // The wire IDs passed to gnark are 44 | correctedVarID := varId + pub + sec - 1 45 | constantVar := ioIsConstant[ioPosition] 46 | _, alreadySeen := varIdToPosition[correctedVarID] 47 | if !alreadySeen && !constantVar { 48 | varIdToPosition[correctedVarID] = ioPosition 49 | } 50 | } 51 | 52 | // Creates the maps to perform the multiexponentiations 53 | pubGkrIo := make([]int, 0, pub) 54 | pubGkrVarID := make([]int, 0, pub) 55 | privGkrVarID := make([]int, 0, pub) 56 | 57 | // Now the variable IDs maps 58 | privGkrIo := make([]int, 0, priv) 59 | pubNotGkrVarID := make([]int, 0, priv) 60 | privNotGkrVarID := make([]int, 0, priv) 61 | 62 | // For all possible public variable IDs 63 | // pub denotes the the "r1cs" number of variable 64 | // So it includes the constant wire = 1 65 | for varId := 0; varId < pub-1; varId++ { 66 | // There is an offset here, the WireID n corresponds to r1cs 67 | // variable n° n+1 68 | ioPosition, ok := varIdToPosition[varId] 69 | if ok { 70 | // Gkr variable 71 | pubGkrIo = append(pubGkrIo, ioPosition) 72 | pubGkrVarID = append(pubGkrVarID, varId+1) 73 | } else { 74 | // Non Gkr variable 75 | pubNotGkrVarID = append(pubNotGkrVarID, varId+1) 76 | } 77 | } 78 | 79 | // For all possible private variable IDs 80 | for varId := pub - 1; varId < pub+priv-1; varId++ { 81 | ioPosition, ok := varIdToPosition[varId] 82 | if ok { 83 | // Gkr variable 84 | privGkrIo = append(privGkrIo, ioPosition) 85 | privGkrVarID = append(privGkrVarID, varId+1) 86 | } else { 87 | // Not Gkr variable 88 | privNotGkrVarID = append(privNotGkrVarID, varId+1) 89 | } 90 | } 91 | 92 | return R1CS{ 93 | r1cs: *r1cs.(*cs.R1CS), 94 | pubGkrIo: pubGkrIo, 95 | pubGkrVarID: pubGkrVarID, 96 | pubNotGkrVarID: pubNotGkrVarID, 97 | privGkrVarID: privGkrVarID, 98 | privGkrIo: privGkrIo, 99 | privNotGkrVarID: privNotGkrVarID, 100 | }, nil 101 | } 102 | -------------------------------------------------------------------------------- /prover/gadget/gadget.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "github.com/AlexandreBelling/gnark/backend/hint" 5 | "github.com/AlexandreBelling/gnark/frontend" 6 | "github.com/consensys/gkr-mimc/circuit" 7 | "github.com/consensys/gkr-mimc/common" 8 | "github.com/consensys/gkr-mimc/examples" 9 | "github.com/consensys/gkr-mimc/hash" 10 | "github.com/consensys/gkr-mimc/snark/gkr" 11 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 12 | ) 13 | 14 | // HINTS ID that we are using for GKR 15 | const GKR_MIMC_GET_HASH_HINT_ID hint.ID = hint.ID(780000001) 16 | const GKR_MIMC_GET_INITIAL_RANDOMNESS_HINT_ID hint.ID = hint.ID(780000002) 17 | const GKR_MIMC_GKR_PROVER_HINT_ID hint.ID = hint.ID(780000003) 18 | 19 | // Caches the result of the `UpdateMimcHash(0, 0)` 20 | // For padding 21 | var hashOfZeroes fr.Element 22 | 23 | // Default chunkSize used by GKR 24 | const DEFAULT_CHUNK_SIZE int = 1024 25 | 26 | func init() { 27 | var zero fr.Element 28 | // Since it's the hash of zero, we don't need to the "newState = gkrOutput + block + state" thing 29 | // We have the equality 30 | hash.MimcUpdateInplace(&hashOfZeroes, zero) 31 | } 32 | 33 | // Helper for performing hashes using GKR 34 | type GkrGadget struct { 35 | // Pointers to variables that must have been allocated somewhere else 36 | InitialRandomness frontend.Variable `gnark:",public"` 37 | ioStore IoStore `gnark:"-"` 38 | 39 | Circuit circuit.Circuit `gnark:"-"` 40 | 41 | r1cs *R1CS `gnark:"-"` 42 | proof *Proof `gnark:"-"` 43 | } 44 | 45 | // NewGkrGadget 46 | func NewGkrGadget() *GkrGadget { 47 | // Despite the struct having a `Circuit` field, we only allow 48 | // it to work with the mimc Circuit 49 | mimc := examples.MimcCircuit() 50 | 51 | return &GkrGadget{ 52 | ioStore: NewIoStore(&mimc, 16), 53 | Circuit: mimc, 54 | } 55 | } 56 | 57 | // Used for padding dummy values. It adds constants everywhere so the result is not return 58 | // (as it is basically useless) 59 | func (g *GkrGadget) updateHasherWithZeroes(cs frontend.API) { 60 | g.ioStore.Push( 61 | cs, 62 | []frontend.Variable{frontend.Variable(0), frontend.Variable(0)}, 63 | hashOfZeroes, 64 | ) 65 | } 66 | 67 | func (g *GkrGadget) getInitialRandomness(cs frontend.API) (initialRandomness frontend.Variable, qPrime []frontend.Variable) { 68 | // Get the initial randomness 69 | ios := g.ioStore.DumpForProverMultiExp() 70 | bN := common.Log2Ceil(g.ioStore.Index()) 71 | 72 | initialRandomnessArr, err := cs.NewHint(g.InitialRandomnessHint(), ios...) 73 | initialRandomness = initialRandomnessArr[0] 74 | common.Assert(err == nil, "Unexpected error %v", err) 75 | 76 | // Expands the initial randomness into q and qPrime 77 | qPrime = make([]frontend.Variable, bN) 78 | tmp := initialRandomnessArr[0] 79 | 80 | for i := range qPrime { 81 | qPrime[i] = tmp 82 | tmp = cs.Mul(tmp, tmp) 83 | } 84 | 85 | return initialRandomness, qPrime 86 | } 87 | 88 | // Runs the Gkr Prover 89 | func (g *GkrGadget) getGkrProof(cs frontend.API, qPrime []frontend.Variable) gkr.Proof { 90 | 91 | proofInputs := g.ioStore.DumpForGkrProver(qPrime) 92 | proofVec, err := cs.NewHint(g.GkrProverHint(), proofInputs...) 93 | 94 | common.Assert(err == nil, "unexpected error in the gkr prover hint %v", err) 95 | if err != nil { 96 | panic("unexpected error in the gkr prover hint") 97 | } 98 | 99 | return g.GkrProofFromVec(proofVec) 100 | } 101 | 102 | // Pad and close GKR, run the proof then call the verifier 103 | func (g *GkrGadget) Close(cs frontend.API) { 104 | 105 | bN := common.Log2Ceil(g.ioStore.Index()) 106 | paddedLen := 1 << bN 107 | 108 | // Pad the inputs in order to get a power of two length vector 109 | for g.ioStore.Index() < paddedLen { 110 | g.updateHasherWithZeroes(cs) 111 | } 112 | 113 | initialRandomness, qPrime := g.getInitialRandomness(cs) 114 | proof := g.getGkrProof(cs, qPrime) 115 | proof.AssertValid(cs, g.Circuit, qPrime, g.ioStore.InputsForVerifier(), g.ioStore.OutputsForVerifier()) 116 | 117 | // The last thing we do is checking that the initialRandomness matches the public one 118 | cs.AssertIsEqual(g.InitialRandomness, initialRandomness) 119 | } 120 | -------------------------------------------------------------------------------- /prover/gadget/gadget_api.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "github.com/AlexandreBelling/gnark/frontend" 5 | "github.com/consensys/gkr-mimc/common" 6 | ) 7 | 8 | // Pass the update of a hasher to GKR 9 | func (g *GkrGadget) UpdateHasher( 10 | cs frontend.API, 11 | state frontend.Variable, 12 | msg frontend.Variable, 13 | ) frontend.Variable { 14 | 15 | // Call the hint to get the hash. Since the circuit does not compute the full evaluation 16 | // of Mimc, we preprocess the inputs to let the circuit work 17 | output, err := cs.NewHint(g.HashHint(), state, msg) 18 | common.Assert(err == nil, "Unexpected error") 19 | 20 | // Since the circuit does not exactly computes the hash, 21 | // we are left to "finish the computation" by readding the state 22 | g.ioStore.Push( 23 | cs, 24 | []frontend.Variable{state, msg}, 25 | output[0], 26 | ) 27 | 28 | return cs.Add(output[0], state, state, msg) 29 | } 30 | -------------------------------------------------------------------------------- /prover/gadget/hints.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | 7 | "github.com/AlexandreBelling/gnark/backend/hint" 8 | "github.com/AlexandreBelling/gnark/frontend" 9 | "github.com/consensys/gkr-mimc/common" 10 | gkrNative "github.com/consensys/gkr-mimc/gkr" 11 | "github.com/consensys/gkr-mimc/hash" 12 | "github.com/consensys/gkr-mimc/poly" 13 | "github.com/consensys/gkr-mimc/snark/gkr" 14 | "github.com/consensys/gnark-crypto/ecc" 15 | "github.com/consensys/gnark-crypto/ecc/bn254" 16 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 17 | "golang.org/x/crypto/sha3" 18 | ) 19 | 20 | const debug bool = false 21 | 22 | type HashHint struct { 23 | g *GkrGadget 24 | } 25 | 26 | type InitialRandomnessHint struct { 27 | g *GkrGadget 28 | } 29 | 30 | type GkrProverHint struct { 31 | g *GkrGadget 32 | } 33 | 34 | // Accessor for the hint from the GkrGadget 35 | func (g *GkrGadget) HashHint() *HashHint { 36 | return &HashHint{g: g} 37 | } 38 | 39 | // Accessor for the hint from the InitialRandomnessHint 40 | func (g *GkrGadget) InitialRandomnessHint() *InitialRandomnessHint { 41 | return &InitialRandomnessHint{g: g} 42 | } 43 | 44 | // Accessor for the hint from the GkrProver 45 | func (g *GkrGadget) GkrProverHint() *GkrProverHint { 46 | return &GkrProverHint{g: g} 47 | } 48 | 49 | // UUID of the hash hint 50 | func (h *HashHint) UUID() hint.ID { 51 | return 156461454 52 | } 53 | 54 | // UUID of the initial randomness hint hint 55 | func (h *InitialRandomnessHint) UUID() hint.ID { 56 | return 46842135 57 | } 58 | 59 | // UUID of the gkr prover hint hint 60 | func (h *GkrProverHint) UUID() hint.ID { 61 | return 13135755 62 | } 63 | 64 | // NbOutputs of the hash hint 65 | func (h *HashHint) NbOutputs(_ ecc.ID, nbInput int) int { 66 | return 1 67 | } 68 | 69 | // NbOutputs of the initial randomness hint 70 | func (h *InitialRandomnessHint) NbOutputs(_ ecc.ID, nbInput int) int { 71 | return 1 72 | } 73 | 74 | // NbOutputs of the gkr prover hint 75 | // Returns the number of field elements to represent the 76 | func (h *GkrProverHint) NbOutputs(_ ecc.ID, nbInput int) int { 77 | // Find the circuit 78 | circuit := h.g.Circuit 79 | 80 | // Iteratively finds the bN of the circuit from the input size 81 | // Can't guarantee that g.ioStore.index contains the right values 82 | bN := 0 83 | inputArity := circuit.InputArity() 84 | for { 85 | inputSize := (1< nbInput { 91 | panic(fmt.Sprintf("Took over the size: %v > %v. Something wrong with the formula for the size", inputSize, nbInput)) 92 | } 93 | // try with a larger bn 94 | bN += 1 95 | } 96 | 97 | sumcheckSize := 0 98 | claimsSize := 0 99 | qPrimeSize := 0 100 | 101 | for _, layer := range circuit { 102 | // If not an input gate, adds the sumchecks 103 | if layer.Gate != nil { 104 | // The degree is deg + 1 (because of the multiplication by `eq(h, q)`) 105 | // So the number of coefficients is deg + 2 106 | nbCoeffs := layer.Gate.Degree() + 2 107 | sumcheckSize += bN * nbCoeffs // size of the sumcheck for the layer 108 | } 109 | 110 | claimsSize += len(layer.Out) 111 | qPrimeSize += bN * len(layer.Out) 112 | } 113 | 114 | qPrimeSize += bN // For the output layer 115 | return sumcheckSize + claimsSize + qPrimeSize 116 | } 117 | 118 | // String of the hash hint 119 | func (h *HashHint) String() string { 120 | return "HashHint" 121 | } 122 | 123 | // String of the initial randomness hint hint 124 | func (h *InitialRandomnessHint) String() string { 125 | return "InitialRandomnessHint" 126 | } 127 | 128 | // String of the gkr prover hint hint 129 | func (h *GkrProverHint) String() string { 130 | return "GkrProverHint" 131 | } 132 | 133 | // Returns the Hint functions that can help gnark's solver figure out that 134 | // the output of the GKR should be a hash 135 | func (h *HashHint) Call(curve ecc.ID, inps []*big.Int, outputs []*big.Int) error { 136 | var state, block fr.Element 137 | state.SetBigInt(inps[0]) 138 | block.SetBigInt(inps[1]) 139 | 140 | // Properly computes the hash 141 | hashed := hash.MimcKeyedPermutation(block, state) 142 | hashed.ToBigIntRegular(outputs[0]) 143 | h.g.ioStore.index++ 144 | return nil 145 | } 146 | 147 | // Derives the initial randomness from an elliptic curve point 148 | func DeriveRandomnessFromPoint(g1 bn254.G1Affine) fr.Element { 149 | // Hash the uncompressed point, then get a field element out of it 150 | bytesG1 := g1.RawBytes() 151 | keccak := sha3.NewLegacyKeccak256() 152 | keccak.Write(bytesG1[:]) 153 | hashed := keccak.Sum(nil) 154 | 155 | // Derive the initial randomness from the hash 156 | var randomness fr.Element 157 | randomness.SetBytes(hashed) 158 | return randomness 159 | } 160 | 161 | // Hint for generating the initial randomness 162 | func (h *InitialRandomnessHint) Call(_ ecc.ID, inpss []*big.Int, oups []*big.Int) error { 163 | t := common.NewTimer("initial randomness hint") 164 | defer t.Close() 165 | // Takes a subslice and convert to fr.Element 166 | subSlice := func(array []*big.Int, indices []int, offset int) []fr.Element { 167 | res := make([]fr.Element, len(indices)) 168 | for i, idx := range indices { 169 | res[i].SetBigInt(array[idx+offset]) 170 | // Switch to regular 171 | res[i].FromMont() 172 | } 173 | return res 174 | } 175 | 176 | // Separate the scalars for the public/private parts 177 | scalarsPub := subSlice(inpss, h.g.r1cs.pubGkrIo, 0) 178 | scalarsPriv := subSlice(inpss, h.g.r1cs.privGkrIo, 0) 179 | 180 | // Compute the K associated to the gkr public/private inputs 181 | var KrsGkr, KrsGkrPriv bn254.G1Affine 182 | KrsGkr.MultiExp(h.g.r1cs.provingKey.pubKGkr, scalarsPub, ecc.MultiExpConfig{}) 183 | KrsGkrPriv.MultiExp(h.g.r1cs.provingKey.privKGkrSigma, scalarsPriv, ecc.MultiExpConfig{}) 184 | KrsGkr.Add(&KrsGkr, &KrsGkrPriv) 185 | 186 | h.g.proof = &Proof{KrsGkrPriv: KrsGkrPriv} 187 | 188 | initialRandomness := DeriveRandomnessFromPoint(KrsGkr) 189 | initialRandomness.ToBigIntRegular(oups[0]) 190 | 191 | return nil 192 | } 193 | 194 | // Returns the Hint functions that can help gnark's solver figure out that 195 | // we need to compute the GkrProof and verify 196 | // In order to return the fields one after the other, the function is built as a stateful iterator 197 | func (h *GkrProverHint) Call(_ ecc.ID, inputsBI []*big.Int, oups []*big.Int) error { 198 | bN := common.Log2Ceil(h.g.ioStore.Index()) 199 | 200 | paddedIndex := 1 << bN 201 | 202 | drain := make([]fr.Element, len(inputsBI)) 203 | for i := range drain { 204 | drain[i].SetBigInt(inputsBI[i]) 205 | } 206 | 207 | // Passes the outputs 208 | inputs := make([]poly.MultiLin, h.g.Circuit.InputArity()) 209 | qPrime, drain := drain[:bN], drain[bN:] 210 | for i := range inputs { 211 | inputs[i], drain = drain[:paddedIndex], drain[paddedIndex:] 212 | } 213 | // The output: here is passed to force the solver to wait for all the output 214 | outputs, drain := drain[:paddedIndex], drain[paddedIndex:] 215 | 216 | // Sanity check 217 | common.Assert(len(drain) == 0, "The drain was expected to emptied but there remains %v elements", len(drain)) 218 | 219 | // Runs the actual prover 220 | assignment := h.g.Circuit.Assign(inputs...) 221 | t := common.NewTimer("gkr prover hint") 222 | gkrProof := gkrNative.Prove(h.g.Circuit, assignment, qPrime) 223 | t.Close() 224 | 225 | if debug { 226 | // For debug : only -> Check that the proof verifies 227 | valid := gkrNative.Verify(h.g.Circuit, gkrProof, inputs, outputs, qPrime) 228 | common.Assert(valid == nil, "GKR proof was wrong - Bug in proof generation - %v", valid) 229 | } 230 | 231 | GkrProofToVec(gkrProof, oups) 232 | return nil 233 | } 234 | 235 | // Writes the proof in a res buffer. We assume the res buffer to be allocated a priori 236 | func GkrProofToVec(proof gkrNative.Proof, resBuff []*big.Int) { 237 | cursor := 0 238 | 239 | // Writes the sumcheck proofs 240 | for _, layer := range proof.SumcheckProofs { 241 | for _, sumcheckRound := range layer { 242 | for _, val := range sumcheckRound { 243 | val.ToBigIntRegular(resBuff[cursor]) 244 | cursor += 1 245 | } 246 | } 247 | } 248 | 249 | // Writes the claimLeft 250 | for _, layer := range proof.Claims { 251 | for _, claim := range layer { 252 | claim.ToBigIntRegular(resBuff[cursor]) 253 | cursor += 1 254 | } 255 | } 256 | 257 | // Writes the qPrimes 258 | for _, layer := range proof.QPrimes { 259 | for _, qs := range layer { 260 | for _, q := range qs { 261 | q.ToBigIntRegular(resBuff[cursor]) 262 | cursor += 1 263 | } 264 | } 265 | } 266 | 267 | // sanity check : expect to have written entirely the vector 268 | if cursor < len(resBuff) { 269 | panic("expected to have written the entire buffer") 270 | } 271 | } 272 | 273 | // Reads the proof to obtain the variable equivalent, the gadget is here 274 | // to provide the dimensions 275 | func (g *GkrGadget) GkrProofFromVec(vec []frontend.Variable) gkr.Proof { 276 | bN := common.Log2Ceil(g.ioStore.index) 277 | 278 | // At this point, all the dimension of the proof are available 279 | proof := gkr.AllocateProof(bN, g.Circuit) 280 | cursor := 0 281 | 282 | // Writes the sumcheck proofs 283 | for i, layer := range proof.SumcheckProofs { 284 | for j, sumcheckRound := range layer { 285 | for k := range sumcheckRound { 286 | proof.SumcheckProofs[i][j][k] = vec[cursor] 287 | cursor += 1 288 | } 289 | } 290 | } 291 | 292 | // Writes the claimLeft 293 | for i, layer := range proof.Claims { 294 | for j := range layer { 295 | proof.Claims[i][j] = vec[cursor] 296 | cursor += 1 297 | } 298 | } 299 | 300 | // Writes the qPrimes 301 | for i, layer := range proof.QPrimes { 302 | for j, qs := range layer { 303 | for k := range qs { 304 | proof.QPrimes[i][j][k] = vec[cursor] 305 | cursor += 1 306 | } 307 | } 308 | } 309 | 310 | // sanity check, we expect to have read the entire vector by now 311 | if cursor < len(vec) { 312 | panic("the vector was not completely read to complete the proof") 313 | } 314 | 315 | return proof 316 | 317 | } 318 | -------------------------------------------------------------------------------- /prover/gadget/io_store.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlexandreBelling/gnark/frontend" 7 | "github.com/consensys/gkr-mimc/circuit" 8 | "github.com/consensys/gkr-mimc/common" 9 | "github.com/consensys/gkr-mimc/snark/polynomial" 10 | ) 11 | 12 | const DEFAULT_IO_STORE_ALLOCATION_EPOCH int = 32 13 | 14 | // Stores the inputs and is responsible for the reordering tasks 15 | type IoStore struct { 16 | inputs []polynomial.MultiLin // The variables as Gkr inputs 17 | inputsVarIds [][]int // The variable IDs as Gkr outputs 18 | inputsIsConstant [][]bool // True if the variable is a constant 19 | outputs polynomial.MultiLin // The variables as Gkr outputs 20 | outputsVarIds []int // The ids of the variable as Gkr outputs 21 | outputsIsConstant []bool // True if the variable is a constant 22 | allocEpoch int 23 | index int 24 | inputArity int 25 | // Our implementation only support an arity of 1 for the outputs 26 | } 27 | 28 | // Creates a new ioStore for the given circuit 29 | func NewIoStore(circuit *circuit.Circuit, allocEpoch int) IoStore { 30 | 31 | inputArity := circuit.InputArity() 32 | 33 | return IoStore{ 34 | inputs: make([]polynomial.MultiLin, circuit.InputArity()), 35 | inputsVarIds: make([][]int, inputArity), 36 | inputsIsConstant: make([][]bool, inputArity), 37 | outputs: polynomial.MultiLin{}, 38 | allocEpoch: allocEpoch, 39 | inputArity: circuit.InputArity(), 40 | } 41 | } 42 | 43 | // Return the number of element allocated 44 | func (io *IoStore) Index() int { 45 | return io.index 46 | } 47 | 48 | // Add an element in the ioStack 49 | func (io *IoStore) Push(cs frontend.API, inputs []frontend.Variable, output frontend.Variable) { 50 | 51 | // Check that the dimension of the provided arrays is consistent with what was expected 52 | if len(inputs) != io.inputArity { 53 | panic(fmt.Sprintf("Expected inputs/outputs to have size %v but got %v", 54 | io.inputArity, len(inputs), 55 | )) 56 | } 57 | 58 | // Enforces everything as a wire in place 59 | for i := range inputs { 60 | inputs[i] = cs.EnforceWire(inputs[i]) 61 | } 62 | 63 | // And the output... 64 | output = cs.EnforceWire(output) 65 | 66 | // Performs an allocation if necessary 67 | io.allocateForOneMore() 68 | 69 | common.Assert(len(inputs) == 2, "Expected 2 inputs, got %v", len(inputs)) 70 | common.Assert(len(io.inputs) == 2, "There should be 2 inputs, but got %v", len(io.inputs)) 71 | 72 | // Append the inputs 73 | for i := range inputs { 74 | wire := inputs[i] 75 | wireID, wireConstant := cs.WireId(wire) 76 | io.inputs[i] = append(io.inputs[i], wire) 77 | io.inputsVarIds[i] = append(io.inputsVarIds[i], wireID) 78 | io.inputsIsConstant[i] = append(io.inputsIsConstant[i], wireConstant) 79 | } 80 | 81 | // Append the output 82 | wire := output 83 | wireID, wireConstant := cs.WireId(wire) 84 | io.outputs = append(io.outputs, wire) 85 | io.outputsVarIds = append(io.outputsVarIds, wireID) 86 | io.outputsIsConstant = append(io.outputsIsConstant, wireConstant) 87 | 88 | io.index++ 89 | } 90 | 91 | // Returns the io for the prover multiexp 92 | // Done by concatenating the two into another array 93 | func (io *IoStore) DumpForProverMultiExp() []frontend.Variable { 94 | 95 | // Allocate the result 96 | resSize := io.index * (io.inputArity + 1) 97 | res := make([]frontend.Variable, 0, resSize) 98 | 99 | // Sanity checks 100 | common.Assert(len(io.inputs[0]) == io.index, "mismatch between index and %v / %v", len(io.inputs[0]), io.index) 101 | 102 | // Filling the vector 103 | for i := range io.inputs { 104 | res = append(res, io.inputs[i]...) 105 | } 106 | 107 | res = append(res, io.outputs...) 108 | 109 | return res 110 | } 111 | 112 | // Returns the io for the prover multiexp 113 | // Done by concatenating the two into another array 114 | // The variables are returned in the form of a buffer of interfaces 115 | // 4 empty entry are appended to the result : they are used by the hint to figure out which 116 | // res = qPrime || inputs || outputs 117 | func (io *IoStore) DumpForGkrProver(qPrimeArg []frontend.Variable) []frontend.Variable { 118 | 119 | // Allocate the result 120 | nInputs, nOutputs, bN := len(io.inputs[0])*io.inputArity, len(io.outputs), len(qPrimeArg) 121 | resSize := nInputs + nOutputs + bN 122 | res := make([]frontend.Variable, 0, resSize) 123 | 124 | // Sanity checks: as we can assume to be in the Mimc case 125 | common.Assert(len(io.inputs[0]) == io.index, "The input arity is inconsistent %v / %v", len(io.inputs), io.index) 126 | common.Assert(1<= io.allocEpoch { 172 | 173 | // Double the size 174 | incInputs := io.index 175 | for i := range io.inputs { 176 | io.inputs[i] = IncreaseCapVariable(io.inputs[i], incInputs) 177 | io.inputsVarIds[i] = IncreaseCapInts(io.inputsVarIds[i], incInputs) 178 | io.inputsIsConstant[i] = IncreaseCapBools(io.inputsIsConstant[i], incInputs) 179 | } 180 | 181 | incOutputs := io.index 182 | io.outputs = IncreaseCapVariable(io.outputs, incOutputs) 183 | io.outputsVarIds = IncreaseCapInts(io.outputsVarIds, incOutputs) 184 | io.outputsIsConstant = IncreaseCapBools(io.outputsIsConstant, incOutputs) 185 | 186 | io.allocEpoch *= 2 187 | } 188 | } 189 | 190 | // Increase the capacity of a slice of frontend variable 191 | func IncreaseCapVariable(arr []frontend.Variable, by int) []frontend.Variable { 192 | res := make([]frontend.Variable, 0, len(arr)+by) 193 | res = append(res, arr...) 194 | return res 195 | } 196 | 197 | // Increase the capacity of a slice of integers 198 | func IncreaseCapInts(arr []int, by int) []int { 199 | res := make([]int, 0, len(arr)+by) 200 | res = append(res, arr...) 201 | return res 202 | } 203 | 204 | // Increase the capacity of a slice of boolean 205 | func IncreaseCapBools(arr []bool, by int) []bool { 206 | res := make([]bool, 0, len(arr)+by) 207 | res = append(res, arr...) 208 | return res 209 | } 210 | -------------------------------------------------------------------------------- /prover/gadget/performances_test.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/AlexandreBelling/gnark/backend" 8 | "github.com/AlexandreBelling/gnark/backend/groth16" 9 | "github.com/AlexandreBelling/gnark/frontend" 10 | "github.com/consensys/gkr-mimc/common" 11 | "github.com/consensys/gkr-mimc/poly" 12 | "github.com/consensys/gkr-mimc/snark/hash" 13 | "github.com/consensys/gnark-crypto/ecc" 14 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 15 | ) 16 | 17 | func BenchmarkCircuitWithGKR(b *testing.B) { 18 | for size := 18; size < 25; size++ { 19 | n := 1 << size 20 | benchCircuitWithGkr(n, b) 21 | } 22 | 23 | for size := 13; size < 19; size++ { 24 | n := 1 << size 25 | benchCircuitBaseline(n, b) 26 | } 27 | } 28 | 29 | type CircuitBaseline struct { 30 | X []frontend.Variable 31 | } 32 | 33 | type CircuitWithGkr struct { 34 | X []frontend.Variable 35 | } 36 | 37 | func AllocateCircuitBaseline(n int) CircuitBaseline { 38 | return CircuitBaseline{X: make([]frontend.Variable, n)} 39 | } 40 | 41 | func AllocateCircuitWithGkr(n int) CircuitUsingGkr { 42 | return &CircuitWithGkr{X: make([]frontend.Variable, n)} 43 | } 44 | 45 | func (c *CircuitBaseline) Define(cs frontend.API) error { 46 | for _, x := range c.X { 47 | _ = hash.MimcHash(cs, x) 48 | } 49 | return nil 50 | } 51 | 52 | func (c *CircuitWithGkr) Define(cs frontend.API, gadget *GkrGadget) error { 53 | for _, x := range c.X { 54 | _ = gadget.UpdateHasher(cs, frontend.Variable(0), x) 55 | } 56 | return nil 57 | } 58 | 59 | func AssignCircuitBaseline(n int) CircuitBaseline { 60 | res := CircuitBaseline{X: make([]frontend.Variable, n)} 61 | var rnd fr.Element 62 | rnd.SetRandom() 63 | for i := range res.X { 64 | res.X[i] = rnd 65 | } 66 | return res 67 | } 68 | 69 | func AssignCircuitWithGkr(n int) CircuitWithGkr { 70 | res := CircuitWithGkr{X: make([]frontend.Variable, n)} 71 | var rnd fr.Element 72 | rnd.SetRandom() 73 | for i := range res.X { 74 | res.X[i] = rnd 75 | } 76 | return res 77 | } 78 | 79 | func benchCircuitWithGkr(n int, b *testing.B) { 80 | 81 | defer poly.ClearPool() 82 | 83 | circ := AllocateCircuitWithGkr(n) 84 | circuit := WrapCircuitUsingGkr(circ) 85 | 86 | var r1cs R1CS 87 | var err error 88 | 89 | r1cs, err = circuit.Compile() 90 | if err != nil { 91 | panic(fmt.Sprintf("Could not compile = %v", err)) 92 | } 93 | 94 | pk, _, err := DummySetup(&r1cs) 95 | 96 | if err != nil { 97 | panic(fmt.Sprintf("Could not setup = %v", err)) 98 | } 99 | 100 | ass := AssignCircuitWithGkr(n) 101 | assignment := WrapCircuitUsingGkr(&ass) 102 | assignment.Assign() 103 | 104 | b.Run(fmt.Sprintf("prover-size-%v", n), func(b *testing.B) { 105 | common.ProfileTrace(b, true, false, func() { 106 | // for i := 0; i < b.N; i++ { 107 | _, err = Prove(&r1cs, &pk, &assignment) 108 | common.Assert(err == nil, "Prover failed %v", err) 109 | }) 110 | }) 111 | } 112 | 113 | func benchCircuitBaseline(n int, b *testing.B) { 114 | circuit := AllocateCircuitBaseline(n) 115 | r1cs, err := frontend.Compile(ecc.BN254, backend.GROTH16, &circuit) 116 | 117 | if err != nil { 118 | panic(fmt.Sprintf("Could not compile = %v", err)) 119 | } 120 | 121 | pk, err := groth16.DummySetup(r1cs) 122 | 123 | if err != nil { 124 | panic(fmt.Sprintf("Could not setup = %v", err)) 125 | } 126 | 127 | assignment := AssignCircuitBaseline(n) 128 | 129 | b.Run(fmt.Sprintf("baseline-size-%v", n), func(b *testing.B) { 130 | for i := 0; i < b.N; i++ { 131 | witness, err := frontend.NewWitness(&assignment, ecc.BN254) 132 | common.Assert(err == nil, "Parsing assignment to witness failed %v", err) 133 | _, err = groth16.Prove(r1cs, pk, witness) 134 | common.Assert(err == nil, "Prover failed %v", err) 135 | } 136 | }) 137 | } 138 | -------------------------------------------------------------------------------- /prover/gadget/prove.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "math/big" 5 | "runtime" 6 | 7 | "github.com/AlexandreBelling/gnark/notinternal/backend/bn254/cs" 8 | "github.com/AlexandreBelling/gnark/notinternal/backend/bn254/groth16" 9 | "github.com/AlexandreBelling/gnark/notinternal/utils" 10 | "github.com/consensys/gkr-mimc/common" 11 | "github.com/consensys/gnark-crypto/ecc" 12 | "github.com/consensys/gnark-crypto/ecc/bn254" 13 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 14 | "github.com/consensys/gnark-crypto/ecc/bn254/fr/fft" 15 | ) 16 | 17 | // Extend proof for GKR-enabled SNARK 18 | type Proof struct { 19 | Ar, Krs bn254.G1Affine 20 | Bs bn254.G2Affine 21 | KrsGkrPriv bn254.G1Affine 22 | InitialRandomness fr.Element 23 | } 24 | 25 | // Solve and compute the proof 26 | func Prove(r1cs *R1CS, pk *ProvingKey, assignment *Circuit) (*Proof, error) { 27 | t := common.NewTimer("solver") 28 | solution, err := assignment.Solve(*r1cs) 29 | if err != nil { 30 | return nil, err 31 | } 32 | t.Close() 33 | 34 | t = common.NewTimer("compute proof") 35 | proof, err := ComputeProof(r1cs, pk, solution, assignment.Gadget.proof) 36 | if err != nil { 37 | return nil, err 38 | } 39 | t.Close() 40 | 41 | return proof, nil 42 | } 43 | 44 | // Compute the proof 45 | func ComputeProof( 46 | r1cs *R1CS, 47 | pk *ProvingKey, solution Solution, 48 | // Computed during the solving 49 | proof *Proof, 50 | ) (*Proof, error) { 51 | 52 | proof.InitialRandomness = solution.Wires[1] 53 | 54 | // By now, the Gkr part of the proof should be processed 55 | common.Assert(proof != nil, "Passed an empty proof to the prover") 56 | common.Assert(proof.KrsGkrPriv != bn254.G1Affine{}, "The proof misses the GkrPriv") 57 | 58 | // Get the number of public variables 59 | // _, _, pub := r1cs.r1cs.GetNbVariables() 60 | 61 | // Takes a subslice and convert to fr.Element 62 | subSlice := func(array []fr.Element, indices []int, offset int) []fr.Element { 63 | res := make([]fr.Element, len(indices)) 64 | for i, idx := range indices { 65 | res[i] = array[idx+offset] 66 | // Also set the result in regular from 67 | res[i].FromMont() 68 | } 69 | return res 70 | } 71 | 72 | // Deduplicate and separate the non gkr inputs 73 | // As the GKR one where already processed by the Hint 74 | privNotGkrVars := subSlice(solution.Wires, r1cs.privNotGkrVarID, 0) 75 | var krsNotGkr bn254.G1Affine 76 | krsNotGkr.MultiExp(pk.privKNotGkr, privNotGkrVars, ecc.MultiExpConfig{NbTasks: runtime.NumCPU()}) 77 | 78 | // Will perform all the computations beside the one involving `K` 79 | grothProof, err := ComputeGroth16Proof(&r1cs.r1cs, &pk.pk, 80 | solution.A, solution.B, solution.C, solution.Wires) 81 | if err != nil { 82 | panic(err) 83 | } 84 | 85 | // Complete our proof with the result of groth16 86 | proof.Ar = grothProof.Ar 87 | proof.Bs = grothProof.Bs 88 | 89 | // Processes the non-GKR priv part of the multiexp. 90 | var KrsPrivNotGkr bn254.G1Affine 91 | KrsPrivNotGkr.MultiExp(pk.privKNotGkr, privNotGkrVars, ecc.MultiExpConfig{NbTasks: runtime.NumCPU()}) 92 | 93 | // Complete the Krs part with the part we calculated 94 | proof.Krs.Add(&grothProof.Krs, &KrsPrivNotGkr) 95 | 96 | return proof, err 97 | } 98 | 99 | // Modified SNARK prover: we additionally passes a puncturedVersion of the values 100 | func ComputeGroth16Proof(r1cs *cs.R1CS, pk *groth16.ProvingKey, a, b, c, wireValues []fr.Element) (*Proof, error) { 101 | // Changes the capacity of a vector of fr.Element 102 | // Panic if the input capacity is below the length of the slice 103 | setCapacity := func(vec *[]fr.Element, newCap int) { 104 | res := make([]fr.Element, len(*vec), newCap) 105 | copy(res, *vec) 106 | *vec = res 107 | 108 | } 109 | 110 | // Increase the capacity of a, b, c 111 | setCapacity(&a, int(pk.Domain.Cardinality)) 112 | setCapacity(&b, int(pk.Domain.Cardinality)) 113 | setCapacity(&c, int(pk.Domain.Cardinality)) 114 | 115 | // set the wire values in regular form 116 | utils.Parallelize(len(wireValues), func(start, end int) { 117 | for i := start; i < end; i++ { 118 | wireValues[i].FromMont() 119 | } 120 | }) 121 | 122 | // H (witness reduction / FFT part) 123 | var h []fr.Element 124 | chHDone := make(chan struct{}, 1) 125 | go func() { 126 | h = computeH(a, b, c, &pk.Domain) 127 | a = nil 128 | b = nil 129 | c = nil 130 | chHDone <- struct{}{} 131 | }() 132 | 133 | // we need to copy and filter the wireValues for each multi exp 134 | // as pk.G1.A, pk.G1.B and pk.G2.B may have (a significant) number of point at infinity 135 | var wireValuesA, wireValuesB []fr.Element 136 | chWireValuesA, chWireValuesB := make(chan struct{}, 1), make(chan struct{}, 1) 137 | 138 | go func() { 139 | wireValuesA = make([]fr.Element, len(wireValues)-int(pk.NbInfinityA)) 140 | for i, j := 0, 0; j < len(wireValuesA); i++ { 141 | if pk.InfinityA[i] { 142 | continue 143 | } 144 | wireValuesA[j] = wireValues[i] 145 | j++ 146 | } 147 | close(chWireValuesA) 148 | }() 149 | go func() { 150 | wireValuesB = make([]fr.Element, len(wireValues)-int(pk.NbInfinityB)) 151 | for i, j := 0, 0; j < len(wireValuesB); i++ { 152 | if pk.InfinityB[i] { 153 | continue 154 | } 155 | wireValuesB[j] = wireValues[i] 156 | j++ 157 | } 158 | close(chWireValuesB) 159 | }() 160 | 161 | // sample random r and s 162 | var r, s big.Int 163 | var _r, _s, _kr fr.Element 164 | if _, err := _r.SetRandom(); err != nil { 165 | return nil, err 166 | } 167 | if _, err := _s.SetRandom(); err != nil { 168 | return nil, err 169 | } 170 | _kr.Mul(&_r, &_s).Neg(&_kr) 171 | 172 | _r.FromMont() 173 | _s.FromMont() 174 | _kr.FromMont() 175 | _r.ToBigInt(&r) 176 | _s.ToBigInt(&s) 177 | 178 | // computes r[δ], s[δ], kr[δ] 179 | deltas := bn254.BatchScalarMultiplicationG1(&pk.G1.Delta, []fr.Element{_r, _s, _kr}) 180 | 181 | proof := &Proof{} 182 | var bs1, ar bn254.G1Jac 183 | 184 | n := runtime.NumCPU() 185 | 186 | chBs1Done := make(chan error, 1) 187 | computeBS1 := func() { 188 | <-chWireValuesB 189 | if _, err := bs1.MultiExp(pk.G1.B, wireValuesB, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { 190 | chBs1Done <- err 191 | close(chBs1Done) 192 | return 193 | } 194 | bs1.AddMixed(&pk.G1.Beta) 195 | bs1.AddMixed(&deltas[1]) 196 | chBs1Done <- nil 197 | } 198 | 199 | chArDone := make(chan error, 1) 200 | computeAR1 := func() { 201 | <-chWireValuesA 202 | if _, err := ar.MultiExp(pk.G1.A, wireValuesA, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { 203 | chArDone <- err 204 | close(chArDone) 205 | return 206 | } 207 | ar.AddMixed(&pk.G1.Alpha) 208 | ar.AddMixed(&deltas[0]) 209 | proof.Ar.FromJacobian(&ar) 210 | chArDone <- nil 211 | } 212 | 213 | chKrsDone := make(chan error, 1) 214 | computeKRS := func() { 215 | // we could NOT split the Krs multiExp in 2, and just append pk.G1.K and pk.G1.Z 216 | // however, having similar lengths for our tasks helps with parallelism 217 | 218 | var krs, krs2, p1 bn254.G1Jac 219 | chKrs2Done := make(chan error, 1) 220 | go func() { 221 | _, err := krs2.MultiExp(pk.G1.Z, h, ecc.MultiExpConfig{NbTasks: n / 2}) 222 | chKrs2Done <- err 223 | }() 224 | 225 | // WE REMOVED THE Krs multiplication here, as it is simpler to handle it separately in our case 226 | // --------------- 227 | 228 | // if _, err := krs.MultiExp(pk.G1.K, puncturedPrivWiresValues, ecc.MultiExpConfig{NbTasks: n / 2}); err != nil { 229 | // chKrsDone <- err 230 | // return 231 | // } 232 | 233 | // --------------- 234 | 235 | krs.AddMixed(&deltas[2]) 236 | n := 3 237 | for n != 0 { 238 | select { 239 | case err := <-chKrs2Done: 240 | if err != nil { 241 | chKrsDone <- err 242 | return 243 | } 244 | krs.AddAssign(&krs2) 245 | case err := <-chArDone: 246 | if err != nil { 247 | chKrsDone <- err 248 | return 249 | } 250 | p1.ScalarMultiplication(&ar, &s) 251 | krs.AddAssign(&p1) 252 | case err := <-chBs1Done: 253 | if err != nil { 254 | chKrsDone <- err 255 | return 256 | } 257 | p1.ScalarMultiplication(&bs1, &r) 258 | krs.AddAssign(&p1) 259 | } 260 | n-- 261 | } 262 | 263 | proof.Krs.FromJacobian(&krs) 264 | chKrsDone <- nil 265 | } 266 | 267 | computeBS2 := func() error { 268 | // Bs2 (1 multi exp G2 - size = len(wires)) 269 | var Bs, deltaS bn254.G2Jac 270 | 271 | nbTasks := n 272 | if nbTasks <= 16 { 273 | // if we don't have a lot of CPUs, this may artificially split the MSM 274 | nbTasks *= 2 275 | } 276 | <-chWireValuesB 277 | if _, err := Bs.MultiExp(pk.G2.B, wireValuesB, ecc.MultiExpConfig{NbTasks: nbTasks}); err != nil { 278 | return err 279 | } 280 | 281 | deltaS.FromAffine(&pk.G2.Delta) 282 | deltaS.ScalarMultiplication(&deltaS, &s) 283 | Bs.AddAssign(&deltaS) 284 | Bs.AddMixed(&pk.G2.Beta) 285 | 286 | proof.Bs.FromJacobian(&Bs) 287 | return nil 288 | } 289 | 290 | // wait for FFT to end, as it uses all our CPUs 291 | <-chHDone 292 | 293 | // schedule our proof part computations 294 | go computeKRS() 295 | go computeAR1() 296 | go computeBS1() 297 | if err := computeBS2(); err != nil { 298 | return nil, err 299 | } 300 | 301 | // wait for all parts of the proof to be computed. 302 | if err := <-chKrsDone; err != nil { 303 | return nil, err 304 | } 305 | 306 | return proof, nil 307 | 308 | } 309 | 310 | func computeH(a, b, c []fr.Element, domain *fft.Domain) []fr.Element { 311 | // H part of Krs 312 | // Compute H (hz=ab-c, where z=-2 on ker X^n+1 (z(x)=x^n-1)) 313 | // 1 - _a = ifft(a), _b = ifft(b), _c = ifft(c) 314 | // 2 - ca = fft_coset(_a), ba = fft_coset(_b), cc = fft_coset(_c) 315 | // 3 - h = ifft_coset(ca o cb - cc) 316 | 317 | n := len(a) 318 | 319 | // add padding to ensure input length is domain cardinality 320 | padding := make([]fr.Element, int(domain.Cardinality)-n) 321 | a = append(a, padding...) 322 | b = append(b, padding...) 323 | c = append(c, padding...) 324 | n = len(a) 325 | 326 | domain.FFTInverse(a, fft.DIF, 0) 327 | domain.FFTInverse(b, fft.DIF, 0) 328 | domain.FFTInverse(c, fft.DIF, 0) 329 | 330 | domain.FFT(a, fft.DIT, 1) 331 | domain.FFT(b, fft.DIT, 1) 332 | domain.FFT(c, fft.DIT, 1) 333 | 334 | var minusTwoInv fr.Element 335 | minusTwoInv.SetUint64(2) 336 | minusTwoInv.Neg(&minusTwoInv). 337 | Inverse(&minusTwoInv) 338 | 339 | // h = ifft_coset(ca o cb - cc) 340 | // reusing a to avoid unecessary memalloc 341 | utils.Parallelize(n, func(start, end int) { 342 | for i := start; i < end; i++ { 343 | a[i].Mul(&a[i], &b[i]). 344 | Sub(&a[i], &c[i]). 345 | Mul(&a[i], &minusTwoInv) 346 | } 347 | }) 348 | 349 | // ifft_coset 350 | domain.FFTInverse(a, fft.DIF, 1) 351 | 352 | utils.Parallelize(len(a), func(start, end int) { 353 | for i := start; i < end; i++ { 354 | a[i].FromMont() 355 | } 356 | }) 357 | 358 | return a 359 | } 360 | -------------------------------------------------------------------------------- /prover/gadget/prover_test.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/AlexandreBelling/gnark/frontend" 7 | "github.com/consensys/gkr-mimc/hash" 8 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | // Small circuit to perform a few hashes : simplest test case 13 | type TestGadgetCircuit struct { 14 | Preimages []frontend.Variable 15 | Hashes []frontend.Variable 16 | } 17 | 18 | // Allocate the test gadget 19 | func AllocateTestGadgetCircuit(n int) TestGadgetCircuit { 20 | return TestGadgetCircuit{ 21 | Preimages: make([]frontend.Variable, n), 22 | Hashes: make([]frontend.Variable, n), 23 | } 24 | } 25 | 26 | func (t *TestGadgetCircuit) Define(cs frontend.API, gadget *GkrGadget) error { 27 | for i := range t.Preimages { 28 | y := gadget.UpdateHasher(cs, frontend.Variable(0), t.Preimages[i]) 29 | cs.AssertIsEqual(t.Hashes[i], y) 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func (t *TestGadgetCircuit) Assign(preimages, hashes []fr.Element) { 36 | for i := range preimages { 37 | t.Preimages[i] = preimages[i] 38 | t.Hashes[i] = hashes[i] 39 | } 40 | } 41 | 42 | func TestFullProver(t *testing.T) { 43 | n := 10 44 | preimages := make([]fr.Element, n) 45 | hashes := make([]fr.Element, n) 46 | 47 | for i := range preimages { 48 | preimages[i].SetUint64(uint64(i)) 49 | hash.MimcUpdateInplace(&hashes[i], preimages[i]) 50 | } 51 | 52 | innerCircuit := AllocateTestGadgetCircuit(n) 53 | circuit := WrapCircuitUsingGkr(&innerCircuit) 54 | 55 | r1cs, err := circuit.Compile() 56 | assert.NoError(t, err) 57 | 58 | pk, vk, err := Setup(&r1cs) 59 | assert.NoError(t, err, "Error during the setup") 60 | 61 | innerAssignment := AllocateTestGadgetCircuit(n) 62 | innerAssignment.Assign(preimages, hashes) 63 | assignment := WrapCircuitUsingGkr(&innerAssignment) 64 | assignment.Assign() 65 | 66 | solution, err := assignment.Solve(r1cs) 67 | assert.NoError(t, err) 68 | 69 | proof, err := ComputeProof( 70 | &r1cs, 71 | &pk, 72 | solution, 73 | assignment.Gadget.proof, 74 | ) 75 | assert.NoError(t, err) 76 | 77 | err = Verify(proof, &vk, []fr.Element{}) 78 | assert.NoError(t, err) 79 | } 80 | -------------------------------------------------------------------------------- /prover/gadget/random_test.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/AlexandreBelling/gnark/frontend" 7 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | // RandomCircuit tests all possibilities that the circuit can 12 | type RandomCircuit struct { 13 | Public []frontend.Variable `gnark:",public"` 14 | Private []frontend.Variable 15 | } 16 | 17 | func AllocateRandomCircuit(n int) RandomCircuit { 18 | return RandomCircuit{ 19 | Public: make([]frontend.Variable, n), 20 | Private: make([]frontend.Variable, n), 21 | } 22 | } 23 | 24 | // In order to simplify the test, we make the calculation feed-forward 25 | func (c *RandomCircuit) Define(cs frontend.API, gadget *GkrGadget) error { 26 | tmp := frontend.Variable(1) 27 | for i := range c.Public { 28 | tmp := gadget.UpdateHasher(cs, tmp, c.Public[i]) 29 | tmp = gadget.UpdateHasher(cs, tmp, tmp) 30 | tmp = gadget.UpdateHasher(cs, tmp, c.Private[i]) 31 | _ = cs.Mul(tmp, tmp) 32 | 33 | } 34 | return nil 35 | } 36 | 37 | // Assigns deterministic values 38 | func (c *RandomCircuit) Assign() []fr.Element { 39 | res := make([]fr.Element, len(c.Public)) 40 | for i := range c.Private { 41 | res[i].SetRandom() 42 | c.Public[i] = res[i] 43 | c.Private[i] = i + 970797 44 | } 45 | return res 46 | } 47 | 48 | func TestWithRandomCircuit(t *testing.T) { 49 | n := 10 50 | 51 | innerCircuit := AllocateRandomCircuit(n) 52 | circuit := WrapCircuitUsingGkr(&innerCircuit) 53 | 54 | r1cs, err := circuit.Compile() 55 | assert.NoError(t, err) 56 | 57 | pk, vk, err := Setup(&r1cs) 58 | assert.NoError(t, err) 59 | 60 | innerAssignment := AllocateRandomCircuit(n) 61 | pubWitness := innerAssignment.Assign() 62 | assignment := WrapCircuitUsingGkr(&innerAssignment) 63 | assignment.Assign() 64 | 65 | solution, err := assignment.Solve(r1cs) 66 | assert.NoError(t, err) 67 | 68 | proof, err := ComputeProof(&r1cs, &pk, solution, assignment.Gadget.proof) 69 | assert.NoError(t, err) 70 | 71 | err = Verify(proof, &vk, pubWitness) 72 | assert.NoError(t, err) 73 | 74 | } 75 | -------------------------------------------------------------------------------- /prover/gadget/setup.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "math/big" 5 | 6 | grothBack "github.com/AlexandreBelling/gnark/backend/groth16" 7 | "github.com/AlexandreBelling/gnark/frontend" 8 | "github.com/AlexandreBelling/gnark/notinternal/backend/bn254/groth16" 9 | "github.com/consensys/gkr-mimc/common" 10 | "github.com/consensys/gnark-crypto/ecc/bn254" 11 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 12 | ) 13 | 14 | func init() { 15 | Setup = wrapSetupFunc(grothBack.Setup) 16 | DummySetup = wrapSetupFunc(groth16DummySetup) 17 | } 18 | 19 | // The setup and dummy setups are initialized by applying our wrapper 20 | var Setup outerSetupFunc 21 | var DummySetup outerSetupFunc 22 | 23 | // inner function type to capture both Setup and DummySetup 24 | type innerSetupFunc func(frontend.CompiledConstraintSystem) (grothBack.ProvingKey, grothBack.VerifyingKey, error) 25 | 26 | // outer setup function type 27 | type outerSetupFunc func(*R1CS) (ProvingKey, VerifyingKey, error) 28 | 29 | // Wrappers around the proving key 30 | type ProvingKey struct { 31 | pk groth16.ProvingKey 32 | privKNotGkr, pubKGkr, privKGkrSigma []bn254.G1Affine 33 | } 34 | 35 | // Wrapper around the verifying key 36 | type VerifyingKey struct { 37 | vk groth16.VerifyingKey 38 | deltaSigmaInvNeg bn254.G2Affine 39 | pubKNotGkr, pubKGkr []bn254.G1Affine 40 | pubGkrVarID, pubNotGkrVarID []int 41 | } 42 | 43 | func wrapSetupFunc(innerF innerSetupFunc) outerSetupFunc { 44 | return func(r1cs *R1CS) (ProvingKey, VerifyingKey, error) { 45 | // Runs the groth16 setup 46 | pk, vk, err := innerF(&r1cs.r1cs) 47 | if err != nil { 48 | return ProvingKey{}, VerifyingKey{}, err 49 | } 50 | 51 | pkRes, vkRes := PreInitializePublicParams(pk, vk) 52 | SubSlicesPublicParams(r1cs, &pkRes, &vkRes) 53 | MarkWithSigma(&pkRes, &vkRes) 54 | EraseOldK(&pkRes, &vkRes) 55 | 56 | // Injects the proving key inside the R1CS 57 | r1cs.provingKey = &pkRes 58 | 59 | return pkRes, vkRes, err 60 | } 61 | } 62 | 63 | // Wraps the groth16's dummy setup so it also returns a verification key 64 | func groth16DummySetup(r1cs frontend.CompiledConstraintSystem) (grothBack.ProvingKey, grothBack.VerifyingKey, error) { 65 | pk, err := grothBack.DummySetup(r1cs) 66 | _, _, pub := r1cs.GetNbVariables() 67 | 68 | _, _, _, g2 := bn254.Generators() 69 | 70 | // Creates an empty verifying key 71 | vk := groth16.VerifyingKey{} 72 | vk.G1.K = make([]bn254.G1Affine, pub) 73 | 74 | // In order to not trivialise our test we also set deltaNeg to a random point 75 | var rng big.Int 76 | var rngFr fr.Element 77 | rngFr.SetRandom() 78 | rngFr.ToBigIntRegular(&rng) 79 | vk.G2.DeltaNeg.ScalarMultiplication(&g2, &rng) 80 | 81 | return pk, &vk, err 82 | } 83 | 84 | // Setup then wrap the keys 85 | func PreInitializePublicParams(pk grothBack.ProvingKey, vk grothBack.VerifyingKey) (ProvingKey, VerifyingKey) { 86 | pkType := pk.(*groth16.ProvingKey) 87 | vkType := vk.(*groth16.VerifyingKey) 88 | 89 | pkRes := ProvingKey{ 90 | pk: *pkType, 91 | } 92 | 93 | vkRes := VerifyingKey{ 94 | vk: *vkType, 95 | } 96 | 97 | return pkRes, vkRes 98 | } 99 | 100 | func SubSlicesPublicParams(r1cs *R1CS, pk *ProvingKey, vk *VerifyingKey) { 101 | // Subslices a slice and returns the element in the order of subIndices 102 | // offset allows to take (array[index + offset]) 103 | subSlice := func(array []bn254.G1Affine, subIndices []int, offset int) []bn254.G1Affine { 104 | res := make([]bn254.G1Affine, 0, len(subIndices)) 105 | for _, idx := range subIndices { 106 | res = append(res, array[idx+offset]) 107 | } 108 | return res 109 | } 110 | 111 | // Marks the privGkrSigma with a sigma to prevent malicious users 112 | // to mix it with non-GKR inputs 113 | privGkrSigma := subSlice(pk.pk.G1.K, r1cs.privGkrVarID, -vk.vk.NbPublicWitness()-1) 114 | privKNotGkr := subSlice(pk.pk.G1.K, r1cs.privNotGkrVarID, -vk.vk.NbPublicWitness()-1) 115 | pubKNotGkr := append([]bn254.G1Affine{vk.vk.G1.K[0]}, subSlice(vk.vk.G1.K, r1cs.pubNotGkrVarID, 0)...) 116 | pubKGkr := subSlice(vk.vk.G1.K, r1cs.pubGkrVarID, 0) 117 | 118 | // Passes the subslices arrays 119 | pk.privKGkrSigma = privGkrSigma 120 | pk.privKNotGkr = privKNotGkr 121 | vk.pubKGkr = pubKGkr 122 | vk.pubKNotGkr = pubKNotGkr 123 | 124 | // Passes also the subslices indexes 125 | vk.pubGkrVarID = r1cs.pubGkrVarID 126 | vk.pubNotGkrVarID = r1cs.pubNotGkrVarID 127 | } 128 | 129 | // Marks the public and private key with a random field element sigma 130 | func MarkWithSigma(pk *ProvingKey, vk *VerifyingKey) { 131 | 132 | // Sigma and its inverse are toxic wastes 133 | var sigma, sigmaInv fr.Element 134 | var sigmaBI, sigmaInvBI big.Int 135 | sigma.SetRandom() 136 | sigmaInv.Inverse(&sigma) 137 | sigma.ToBigIntRegular(&sigmaBI) 138 | sigmaInv.ToBigIntRegular(&sigmaInvBI) 139 | 140 | common.Parallelize(len(pk.privKGkrSigma), func(start, stop int) { 141 | for i := start; i < stop; i++ { 142 | pk.privKGkrSigma[i].ScalarMultiplication(&pk.privKGkrSigma[i], &sigmaBI) 143 | } 144 | }) 145 | 146 | // Also marks deltaNeg in the verification key 147 | vk.deltaSigmaInvNeg.ScalarMultiplication(&vk.vk.G2.DeltaNeg, &sigmaInvBI) 148 | } 149 | 150 | func EraseOldK(pk *ProvingKey, vk *VerifyingKey) { 151 | pk.pk.G1.K = []bn254.G1Affine{} 152 | vk.vk.G1.K = []bn254.G1Affine{} 153 | } 154 | -------------------------------------------------------------------------------- /prover/gadget/setup_test.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/AlexandreBelling/gnark/backend/groth16" 7 | "github.com/consensys/gkr-mimc/hash" 8 | "github.com/consensys/gnark-crypto/ecc/bn254" 9 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestActualSetup(t *testing.T) { 14 | setupTestGen(t, groth16.Setup) 15 | } 16 | 17 | func TestDummySetup(t *testing.T) { 18 | setupTestGen(t, groth16DummySetup) 19 | } 20 | 21 | func setupTestGen(t *testing.T, fun innerSetupFunc) { 22 | n := 10 23 | preimages := make([]fr.Element, n) 24 | hashes := make([]fr.Element, n) 25 | 26 | for i := range preimages { 27 | preimages[i].SetUint64(uint64(i)) 28 | hash.MimcUpdateInplace(&hashes[i], preimages[i]) 29 | } 30 | 31 | innerCircuit := AllocateTestGadgetCircuit(n) 32 | circuit := WrapCircuitUsingGkr(&innerCircuit) 33 | 34 | r1cs, err := circuit.Compile() 35 | assert.NoError(t, err) 36 | assert.Equal(t, len(r1cs.pubNotGkrVarID), 1) // One for the initial randomness 37 | 38 | _, _, pub := r1cs.r1cs.GetNbVariables() 39 | assert.Equal(t, pub, 2) // One for the initial randomness + 1 for the constant = 1 40 | 41 | // We need to at least run the dummy setup so that the solver knows what to do 42 | // So that the proving key can attach itself to the R1CS 43 | initialPk, initialVk, err := fun(&r1cs.r1cs) 44 | assert.NoError(t, err, "Error during the setup") 45 | 46 | pk, vk := PreInitializePublicParams(initialPk, initialVk) 47 | SubSlicesPublicParams(&r1cs, &pk, &vk) 48 | 49 | assert.Equal(t, pk.privKGkrSigma[0].String(), pk.pk.G1.K[20].String(), "Misalignement") 50 | 51 | { // Tests that the subslicing works as intended 52 | var krsSplitted, krs0 bn254.G1Affine 53 | for _, g := range pk.privKGkrSigma { 54 | krsSplitted.Add(&krsSplitted, &g) 55 | } 56 | 57 | for _, g := range pk.privKNotGkr { 58 | krsSplitted.Add(&krsSplitted, &g) 59 | } 60 | 61 | for _, g := range pk.pk.G1.K { 62 | krs0.Add(&krs0, &g) 63 | } 64 | 65 | assert.Equal(t, krs0, krsSplitted) 66 | } 67 | 68 | MarkWithSigma(&pk, &vk) 69 | 70 | // For debugging only : we split the proving key but we should still have that 71 | { 72 | var krsSumPKGroth16, krsSumGkr, krsSumNotGkr bn254.G1Affine 73 | for _, k := range pk.pk.G1.K { 74 | krsSumPKGroth16.Add(&krsSumPKGroth16, &k) 75 | } 76 | 77 | for _, k := range pk.privKGkrSigma { 78 | krsSumGkr.Add(&krsSumGkr, &k) 79 | } 80 | 81 | for _, k := range pk.privKNotGkr { 82 | krsSumNotGkr.Add(&krsSumNotGkr, &k) 83 | } 84 | 85 | left, err := bn254.Pair([]bn254.G1Affine{krsSumPKGroth16}, []bn254.G2Affine{vk.vk.G2.DeltaNeg}) 86 | assert.NoError(t, err, "Error during the pairing") 87 | 88 | right, err := bn254.Pair( 89 | []bn254.G1Affine{krsSumNotGkr, krsSumGkr}, 90 | []bn254.G2Affine{vk.vk.G2.DeltaNeg, vk.deltaSigmaInvNeg}, 91 | ) 92 | 93 | assert.NoError(t, err, "Error during the second pairing") 94 | assert.Equal(t, left, right, "%v != %v", left.String(), right.String()) 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /prover/gadget/solution.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlexandreBelling/gnark/backend" 7 | "github.com/AlexandreBelling/gnark/frontend" 8 | "github.com/AlexandreBelling/gnark/notinternal/backend/bn254/cs" 9 | "github.com/AlexandreBelling/gnark/notinternal/backend/bn254/groth16" 10 | witness_bn254 "github.com/AlexandreBelling/gnark/notinternal/backend/bn254/witness" 11 | "github.com/consensys/gnark-crypto/ecc" 12 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 13 | ) 14 | 15 | type Solution struct { 16 | Wires, A, B, C []fr.Element 17 | } 18 | 19 | // Dump the solution vector : usefull for debugging 20 | func (s Solution) Dump() { 21 | for i := range s.A { 22 | fmt.Printf("n = %v a = %v, b = %v, c = %v \n", i, s.A[i].String(), s.B[i].String(), s.C[i].String()) 23 | } 24 | } 25 | 26 | // Returns a solution to the circuit 27 | func (c *Circuit) Solve(compiled R1CS, opt ...backend.ProverOption) (Solution, error) { 28 | 29 | if compiled.provingKey == nil { 30 | return Solution{}, fmt.Errorf("solving was called prior to running the setup" + 31 | "\n run either DummySetup(&r1cs) or Setup(&r1cs) prior to calling Solve") 32 | } 33 | 34 | // Re-inject the R1CS into the circuit 35 | c.Gadget.r1cs = &compiled 36 | 37 | solution, solverError, err := c.partialSolve(&compiled.r1cs, opt...) 38 | if err != nil { 39 | // Got an unexpected error 40 | return Solution{}, err 41 | } 42 | 43 | if err = solution.fixSolution(); err != nil { 44 | // The solver had a non fixable error, we return it 45 | return Solution{}, fmt.Errorf("%v \n %v", solverError, err) 46 | } 47 | 48 | return solution, err 49 | } 50 | 51 | // Fixes the partial solution delivered by the solver 52 | func (s *Solution) fixSolution() error { 53 | nConstraint := len(s.A) 54 | errString := "" 55 | 56 | if s.A[nConstraint-1] != fr.One() { 57 | errString += fmt.Sprintf("a[nConstraint] should =1 but got %v \n", s.A[nConstraint-1].String()) 58 | } 59 | 60 | if s.B[nConstraint-1] != fr.NewElement(0) { 61 | errString += fmt.Sprintf("b[nConstraint] should =0 but got %v \n", s.B[nConstraint-1].String()) 62 | } 63 | 64 | if s.C[nConstraint-1] == fr.NewElement(0) { 65 | errString += fmt.Sprintf("c[nConstraint] should != 0 but got %v \n", s.C[nConstraint-1].String()) 66 | } 67 | 68 | if s.Wires[1] != fr.NewElement(0) { 69 | errString += fmt.Sprintf("w[1] should be 0 but got %v \n", s.Wires[1].String()) 70 | } 71 | 72 | if len(errString) > 0 { 73 | return fmt.Errorf(errString) 74 | } 75 | 76 | // Fixes the solution with the right initial randomnes 77 | // Which contained in CN 78 | s.B[nConstraint-1] = s.C[nConstraint-1] 79 | s.Wires[1] = s.C[nConstraint-1] 80 | 81 | return nil 82 | } 83 | 84 | // The first error it returns is the "solver error". So we expect it. 85 | // The second one is for unexected errors 86 | func (c *Circuit) partialSolve(compiled frontend.CompiledConstraintSystem, opts ...backend.ProverOption) (Solution, error, error) { 87 | 88 | witness, err := frontend.NewWitness(c, ecc.BN254) 89 | if err != nil { 90 | return Solution{}, nil, err 91 | } 92 | 93 | opts = append(opts, backend.WithHints(c.Gadget.InitialRandomnessHint(), c.Gadget.HashHint(), c.Gadget.GkrProverHint())) 94 | proverOption, err := backend.NewProverConfig(opts...) 95 | 96 | if err != nil { 97 | return Solution{}, nil, err 98 | } 99 | 100 | r1csHard := compiled.(*cs.R1CS) 101 | 102 | _w := witness.Vector.(*witness_bn254.Witness) 103 | wires, aSol, bSol, cSol, _ := groth16.Solve(r1csHard, *_w, proverOption) 104 | return Solution{A: aSol, B: bSol, C: cSol, Wires: wires}, err, nil 105 | } 106 | -------------------------------------------------------------------------------- /prover/gadget/solver_test.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/AlexandreBelling/gnark/backend" 7 | "github.com/AlexandreBelling/gnark/frontend" 8 | "github.com/AlexandreBelling/gnark/notinternal/backend/bn254/groth16" 9 | witness_bn254 "github.com/AlexandreBelling/gnark/notinternal/backend/bn254/witness" 10 | "github.com/consensys/gkr-mimc/hash" 11 | "github.com/consensys/gnark-crypto/ecc" 12 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestGadgetSolver(t *testing.T) { 17 | n := 10 18 | preimages := make([]fr.Element, n) 19 | hashes := make([]fr.Element, n) 20 | 21 | for i := range preimages { 22 | preimages[i].SetUint64(uint64(i)) 23 | hash.MimcUpdateInplace(&hashes[i], preimages[i]) 24 | } 25 | 26 | innerCircuit := AllocateTestGadgetCircuit(n) 27 | circuit := WrapCircuitUsingGkr(&innerCircuit) 28 | 29 | r1cs, err := circuit.Compile() 30 | assert.NoError(t, err) 31 | 32 | // Test that the length of the arrays is consistent with what we expect 33 | _, _, pub := r1cs.r1cs.GetNbVariables() 34 | assert.Equal(t, pub, 2) // One for the initial randomness + 1 for the constant = 1 35 | assert.Equal(t, 0, len(r1cs.pubGkrVarID), "Got %v", r1cs.pubGkrVarID) 36 | assert.Equal(t, []int{1}, r1cs.pubNotGkrVarID, "Got %v", r1cs.pubNotGkrVarID) 37 | assert.Equal(t, 2*n, len(r1cs.privGkrVarID), "Got %v", r1cs.privGkrVarID) 38 | 39 | // Test that no variable is contained twice and that all variables are contained exactly once 40 | sumVarIds := 0 41 | for _, id := range r1cs.privGkrVarID { 42 | sumVarIds += id 43 | } 44 | 45 | for _, id := range r1cs.privNotGkrVarID { 46 | sumVarIds += id 47 | } 48 | 49 | for _, id := range r1cs.pubGkrVarID { 50 | sumVarIds += id 51 | } 52 | 53 | for _, id := range r1cs.pubNotGkrVarID { 54 | sumVarIds += id 55 | } 56 | 57 | max := r1cs.privNotGkrVarID[len(r1cs.privNotGkrVarID)-1] 58 | assert.Equal(t, (max*(max+1))/2, sumVarIds, "The sum should have matched") 59 | 60 | // We need to at least run the dummy setup so that the solver knows what to do 61 | // So that the proving key can attach itself to the R1CS 62 | _, _, err = DummySetup(&r1cs) 63 | assert.NoError(t, err) 64 | 65 | innerAssignment := AllocateTestGadgetCircuit(n) 66 | innerAssignment.Assign(preimages, hashes) 67 | assignment := WrapCircuitUsingGkr(&innerAssignment) 68 | assignment.Assign() 69 | 70 | solution, err := assignment.Solve(r1cs) 71 | assert.NoError(t, err) 72 | assert.Equal(t, solution.Wires[0], fr.NewElement(1), "It should be the constant wire") 73 | 74 | // If everything works as intender, it should be possible 75 | // to completely solve the circuit at once by 76 | witness, err := frontend.NewWitness(&assignment, ecc.BN254) 77 | assert.NoError(t, err) 78 | _w := *witness.Vector.(*witness_bn254.Witness) 79 | 80 | // If we are not mistaken, this should be zero at this point 81 | assert.Equal(t, _w[0], fr.NewElement(0)) 82 | // And then, we complete the solution with the initial randomness 83 | _w[0] = solution.Wires[1] 84 | 85 | // Reset the index, 86 | assignment.Gadget.ioStore.index = 0 87 | 88 | opts := backend.WithHints( 89 | assignment.Gadget.InitialRandomnessHint(), 90 | assignment.Gadget.HashHint(), 91 | assignment.Gadget.GkrProverHint(), 92 | ) 93 | proverOption, err := backend.NewProverConfig(opts) 94 | assert.NoError(t, err) 95 | 96 | _, _, _, _, err = groth16.Solve(&r1cs.r1cs, _w, proverOption) 97 | assert.NoError(t, err) 98 | } 99 | -------------------------------------------------------------------------------- /prover/gadget/verify.go: -------------------------------------------------------------------------------- 1 | package gadget 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/AlexandreBelling/gnark/notinternal/backend/bn254/witness" 8 | "github.com/consensys/gkr-mimc/common" 9 | "github.com/consensys/gnark-crypto/ecc" 10 | "github.com/consensys/gnark-crypto/ecc/bn254" 11 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 12 | ) 13 | 14 | // Verify verifies a proof with given VerifyingKey and publicWitness 15 | func Verify(proof *Proof, vk *VerifyingKey, publicWitness witness.Witness) error { 16 | 17 | // Readd the public randomness in there 18 | publicWitness = append([]fr.Element{proof.InitialRandomness}, publicWitness...) 19 | 20 | // Takes a subslice and convert to fr.Element 21 | subSlice := func(array []fr.Element, indices []int, offset int) []fr.Element { 22 | res := make([]fr.Element, len(indices)) 23 | for i, idx := range indices { 24 | res[i] = array[idx+offset] 25 | res[i].FromMont() 26 | } 27 | return res 28 | } 29 | 30 | // Separate Gkrs / not Gkrs 31 | pubVarGkr := subSlice(publicWitness, vk.pubGkrVarID, 0) 32 | pubVarNotGkr := subSlice(publicWitness, vk.pubNotGkrVarID, -1) // -1 for the public input 33 | 34 | // The initial randomness should have been passes by the prover as part of the public witness 35 | common.Assert(proof.InitialRandomness != fr.NewElement(0), "The initial randomness should be known at this point") 36 | 37 | // Computes separately the priv/pub GKRs 38 | var KrsGkr, KrsPub, KrsGkrPub bn254.G1Affine 39 | KrsGkrPub.MultiExp(vk.pubKGkr, pubVarGkr, ecc.MultiExpConfig{NbTasks: runtime.NumCPU()}) 40 | KrsGkr.Add(&KrsGkrPub, &proof.KrsGkrPriv) 41 | 42 | // Check the initial randomness 43 | initialRandomness := DeriveRandomnessFromPoint(KrsGkr) 44 | if initialRandomness != proof.InitialRandomness { 45 | return fmt.Errorf( 46 | "the initial randomness is incorrect. Provided %v != recovered %v", 47 | pubVarNotGkr[0].String(), 48 | initialRandomness.String(), 49 | ) 50 | } 51 | 52 | // Processes the Krs pub 53 | // 1) Non-Gkr stuffs 54 | KrsPub.MultiExp(vk.pubKNotGkr[1:], pubVarNotGkr, ecc.MultiExpConfig{NbTasks: runtime.NumCPU()}) 55 | // 2) Complete with the GKR stuffs and the constant "1" 56 | KrsPub.Add(&KrsPub, &KrsGkrPub) 57 | KrsPub.Add(&KrsPub, &vk.pubKNotGkr[0]) 58 | 59 | // Run the pairing-checks 60 | right, err := bn254.Pair( 61 | []bn254.G1Affine{KrsPub, proof.Krs, proof.KrsGkrPriv, proof.Ar}, 62 | []bn254.G2Affine{vk.vk.G2.GammaNeg, vk.vk.G2.DeltaNeg, vk.deltaSigmaInvNeg, proof.Bs}, 63 | ) 64 | 65 | if err != nil { 66 | return fmt.Errorf("error in the miller loop %v", err) 67 | } 68 | 69 | if !vk.vk.E.Equal(&right) { 70 | return fmt.Errorf("the pairing failed") 71 | } 72 | 73 | return nil 74 | 75 | } 76 | -------------------------------------------------------------------------------- /snark/gkr/gkr.go: -------------------------------------------------------------------------------- 1 | package gkr 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | "github.com/consensys/gkr-mimc/circuit" 8 | "github.com/consensys/gkr-mimc/gkr" 9 | poly "github.com/consensys/gkr-mimc/snark/polynomial" 10 | "github.com/consensys/gkr-mimc/snark/sumcheck" 11 | 12 | "github.com/AlexandreBelling/gnark/frontend" 13 | ) 14 | 15 | // Proof represents a GKR proof 16 | // Only valid for the MiMC circuit 17 | type Proof struct { 18 | SumcheckProofs []sumcheck.Proof 19 | Claims [][]frontend.Variable 20 | QPrimes [][][]frontend.Variable 21 | } 22 | 23 | // AllocateProof allocates a new proof gadget 24 | func AllocateProof(bN int, c circuit.Circuit) (proof Proof) { 25 | proof.SumcheckProofs = make([]sumcheck.Proof, len(c)) 26 | proof.Claims = make([][]frontend.Variable, len(c)) 27 | proof.QPrimes = make([][][]frontend.Variable, len(c)) 28 | 29 | for layer := range c { 30 | // When the Gate is nil, then it's an input layer 31 | if c[layer].Gate != nil { 32 | proof.SumcheckProofs[layer] = sumcheck.AllocateProof(bN, c[layer].Gate) 33 | } 34 | 35 | // We might also allocate qPrime and the claim for the last layer 36 | // But remember that they are passed by the user anyway, so they are 37 | // guaranteed to be allocated prior to 38 | proof.Claims[layer] = make([]frontend.Variable, len(c[layer].Out)) 39 | proof.QPrimes[layer] = make([][]frontend.Variable, len(c[layer].Out)) 40 | 41 | for j := range proof.QPrimes[layer] { 42 | proof.QPrimes[layer][j] = make([]frontend.Variable, bN) 43 | } 44 | } 45 | 46 | // Special case : output layers are , they have no outputs 47 | // But they need one qPrime and no claims 48 | proof.Claims[len(proof.Claims)-1] = []frontend.Variable{} 49 | proof.QPrimes[len(proof.Claims)-1] = [][]frontend.Variable{make([]frontend.Variable, bN)} 50 | 51 | return proof 52 | } 53 | 54 | // Assign the proof object 55 | func (p *Proof) Assign(proof gkr.Proof) { 56 | // sanity-check 57 | if len(p.SumcheckProofs) != len(proof.SumcheckProofs) { 58 | panic("not the same number of layers") 59 | } 60 | 61 | for layer := range proof.SumcheckProofs { 62 | p.SumcheckProofs[layer].Assign(proof.SumcheckProofs[layer]) 63 | 64 | if len(proof.Claims[layer]) != len(p.Claims[layer]) { 65 | panic(fmt.Sprintf( 66 | "panicked in Assign : at layer %v the gnark proof expects %v claims but the assignment contains %v", 67 | layer, len(p.Claims[layer]), len(proof.Claims[layer]), 68 | )) 69 | } 70 | 71 | // We might also allocate qPrime and the claim for the last layer 72 | // But remember that they are passed by the user anyway, so they are 73 | // guaranteed to be allocated aside from the verification runtime 74 | for j := range proof.Claims[layer] { 75 | p.Claims[layer][j] = proof.Claims[layer][j] 76 | } 77 | 78 | for j := range proof.QPrimes[layer] { 79 | for k := range proof.QPrimes[layer][j] { 80 | p.QPrimes[layer][j][k] = proof.QPrimes[layer][j][k] 81 | } 82 | } 83 | } 84 | } 85 | 86 | // AssertValid runs the GKR verifier 87 | func (proof *Proof) AssertValid( 88 | cs frontend.API, 89 | c circuit.Circuit, 90 | qPrime []frontend.Variable, 91 | inputs []poly.MultiLin, 92 | outputs poly.MultiLin, 93 | ) { 94 | 95 | nLayers := len(c) 96 | 97 | for k := range qPrime { 98 | cs.AssertIsEqual(proof.QPrimes[nLayers-1][0][k], qPrime[k]) 99 | } 100 | 101 | // keep the old vector of claims in a variable, that we can put back in the proof 102 | // the goal here, is to not modify the proof when calling `Define` 103 | oldClaim := proof.Claims[nLayers-1] 104 | // this re-allocates 105 | proof.Claims[nLayers-1] = append(proof.Claims[nLayers-1], outputs.Eval(cs, qPrime)) 106 | 107 | for layer := nLayers - 1; layer >= 0; layer-- { 108 | if len(c[layer].In) < 1 { 109 | // It's an input layer 110 | // No, more sumcheck to verify 111 | break 112 | } 113 | 114 | proof.testSumcheck(cs, c, layer) 115 | 116 | } 117 | 118 | for layer := range inputs { 119 | proof.testInitialRound(cs, inputs, layer) 120 | } 121 | 122 | // re-erase the claim. we added midway to revert the change and keep the proof invariant 123 | proof.Claims[nLayers-1] = oldClaim 124 | 125 | } 126 | 127 | func (proof Proof) testSumcheck(cs frontend.API, c circuit.Circuit, layer int) { 128 | // First thing, test the sumcheck 129 | nextQprime, nextClaim, recombChal := proof.SumcheckProofs[layer].AssertValid(cs, proof.Claims[layer]) 130 | // 2 is because in practice, a gate cannot have more than two inputs with our designs 131 | subClaims := make([]frontend.Variable, 0, 2) 132 | 133 | for _, inpL := range c[layer].In { 134 | // Seach the position of `l` as an output of layer `inpL` 135 | // It works because `c[inpL].Out` is guaranteed to be sorted. 136 | readAt := sort.SearchInts(c[inpL].Out, layer) 137 | 138 | // Since `SearchInts` does not answer whether the `int` is contained or not 139 | // but returns the position if it "were" inside. We need to test inclusion 140 | if c[inpL].Out[readAt] != layer { 141 | panic(fmt.Sprintf("circuit misformatted, In and Out are inconsistent between layers %v and %v", layer, inpL)) 142 | } 143 | 144 | for k := range nextQprime { 145 | cs.AssertIsEqual(proof.QPrimes[inpL][readAt][k], nextQprime[k]) 146 | } 147 | 148 | subClaims = append(subClaims, proof.Claims[inpL][readAt]) 149 | } 150 | 151 | // Run the gate to compute the expected claim 152 | expectedClaim := c[layer].Gate.GnarkEval(cs, subClaims...) 153 | 154 | // Evaluation of eq to be used for testing the consistency with the challenges 155 | // Recombines the eq evaluations into a single challenge 156 | tmpEvals := make(poly.Univariate, len(proof.QPrimes[layer])) 157 | for i := range proof.QPrimes[layer] { 158 | tmpEvals[i] = poly.EqEval(cs, proof.QPrimes[layer][i], nextQprime) 159 | } 160 | eqEval := tmpEvals.Eval(cs, recombChal) 161 | expectedClaim = cs.Mul(expectedClaim, eqEval) 162 | cs.AssertIsEqual(expectedClaim, nextClaim) 163 | } 164 | 165 | func (proof Proof) testInitialRound(cs frontend.API, inps []poly.MultiLin, layer int) error { 166 | actual := inps[layer].Eval(cs, proof.QPrimes[layer][0]) 167 | cs.AssertIsEqual(actual, proof.Claims[layer][0]) 168 | return nil 169 | } 170 | -------------------------------------------------------------------------------- /snark/gkr/gkr_test.go: -------------------------------------------------------------------------------- 1 | package gkr 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/consensys/gkr-mimc/circuit" 9 | "github.com/consensys/gkr-mimc/common" 10 | "github.com/consensys/gkr-mimc/examples" 11 | "github.com/consensys/gkr-mimc/gkr" 12 | polyFr "github.com/consensys/gkr-mimc/poly" 13 | poly "github.com/consensys/gkr-mimc/snark/polynomial" 14 | 15 | "github.com/AlexandreBelling/gnark/backend" 16 | "github.com/AlexandreBelling/gnark/backend/groth16" 17 | "github.com/AlexandreBelling/gnark/frontend" 18 | "github.com/AlexandreBelling/gnark/test" 19 | "github.com/consensys/gnark-crypto/ecc" 20 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 21 | ) 22 | 23 | type GKRMimcTestCircuit struct { 24 | Circuit circuit.Circuit 25 | Proof Proof 26 | QInitialprime []frontend.Variable 27 | Inputs []poly.MultiLin 28 | Output poly.MultiLin 29 | } 30 | 31 | func AllocateGKRMimcTestCircuit(bN int) GKRMimcTestCircuit { 32 | circuit := examples.MimcCircuit() 33 | return GKRMimcTestCircuit{ 34 | Circuit: circuit, 35 | Proof: AllocateProof(bN, circuit), 36 | QInitialprime: make([]frontend.Variable, bN), 37 | Output: poly.AllocateMultilinear(bN), 38 | Inputs: []poly.MultiLin{ 39 | poly.AllocateMultilinear(bN), 40 | poly.AllocateMultilinear(bN), 41 | }, 42 | } 43 | } 44 | 45 | func (c *GKRMimcTestCircuit) Assign( 46 | proof gkr.Proof, 47 | inputs []polyFr.MultiLin, 48 | outputs polyFr.MultiLin, 49 | qInitialprime []fr.Element, 50 | ) { 51 | c.Proof.Assign(proof) 52 | for i := range qInitialprime { 53 | c.QInitialprime[i] = qInitialprime[i] 54 | } 55 | 56 | for i := range inputs { 57 | c.Inputs[i].Assign(inputs[i]) 58 | } 59 | c.Output.Assign(outputs) 60 | } 61 | 62 | func (c *GKRMimcTestCircuit) Define(cs frontend.API) error { 63 | c.Proof.AssertValid(cs, c.Circuit, c.QInitialprime, c.Inputs, c.Output) 64 | return nil 65 | } 66 | 67 | func TestGkrCircuit(t *testing.T) { 68 | 69 | for bn := 0; bn < 12; bn++ { 70 | 71 | mimcCircuit := AllocateGKRMimcTestCircuit(bn) 72 | 73 | // Attempt to compile the circuit 74 | _, err := frontend.Compile(ecc.BN254, backend.GROTH16, &mimcCircuit) 75 | if err != nil { 76 | panic(err) 77 | } 78 | 79 | // Create witness values 80 | c := examples.MimcCircuit() 81 | inputs := []polyFr.MultiLin{ 82 | common.RandomFrArray(1 << bn), 83 | common.RandomFrArray(1 << bn), 84 | } 85 | qPrime := common.RandomFrArray(bn) 86 | 87 | a := c.Assign(inputs...) 88 | outputs := a[93].DeepCopy() 89 | gkrProof := gkr.Prove(c, a, qPrime) 90 | 91 | err = gkr.Verify(c, gkrProof, inputs, outputs, qPrime) 92 | if err != nil { 93 | panic(err) 94 | } 95 | 96 | // Assigns the witness 97 | witness := AllocateGKRMimcTestCircuit(bn) 98 | witness.Assign(gkrProof, inputs, outputs, qPrime) 99 | 100 | err = test.IsSolved(&mimcCircuit, &witness, ecc.BN254, backend.GROTH16) 101 | if err != nil { 102 | panic(err) 103 | } 104 | 105 | polyFr.DumpLarge(a...) 106 | } 107 | 108 | } 109 | 110 | func BenchmarkMimcCircuit(b *testing.B) { 111 | // This will run the benchmark until, a SIGKILL happens 112 | // Or there is enough memory to run 32M hashes (=> impossible) 113 | for bn := 0; bn < 26; bn++ { 114 | 115 | fmt.Printf("bN = %v\n", bn) 116 | 117 | mimcCircuit := AllocateGKRMimcTestCircuit(bn) 118 | // Attempt to compile the circuit 119 | r1cs, _ := frontend.Compile(ecc.BN254, backend.GROTH16, &mimcCircuit) 120 | 121 | inte, sec, publ := r1cs.GetNbVariables() 122 | 123 | fmt.Printf("Nb constraints = %v\n", r1cs.GetNbConstraints()) 124 | fmt.Printf("Nb constraints = int %v sec %v pub %v\n", inte, sec, publ) 125 | 126 | // Create witness values 127 | c := examples.MimcCircuit() 128 | inputs := []polyFr.MultiLin{ 129 | common.RandomFrArray(1 << bn), 130 | common.RandomFrArray(1 << bn), 131 | } 132 | qPrime := common.RandomFrArray(bn) 133 | 134 | // Assignment - Benchmark 135 | t := time.Now() 136 | assignment := c.Assign(inputs...) 137 | fmt.Printf("gkr assignment took %v ms\n", time.Since(t).Milliseconds()) 138 | 139 | // Keeps the output for later assignment : not sure if actually needed 140 | outputs := assignment[93].DeepCopyLarge() 141 | t = time.Now() 142 | proof := gkr.Prove(c, assignment, qPrime) 143 | fmt.Printf("gkr prover took %v ms\n", time.Since(t).Milliseconds()) 144 | 145 | // Assigns the values 146 | witness := AllocateGKRMimcTestCircuit(bn) 147 | t = time.Now() 148 | witness.Assign(proof, inputs, outputs, qPrime) 149 | fmt.Printf("post gkr assignment took %v ms\n", time.Since(t).Milliseconds()) 150 | 151 | pk, _ := groth16.DummySetup(r1cs) 152 | 153 | t = time.Now() 154 | w, _ := frontend.NewWitness(&witness, ecc.BN254) 155 | _, err := groth16.Prove(r1cs, pk, w) 156 | if err != nil { 157 | panic(err) 158 | } 159 | fmt.Printf("gnark prover took %v ms\n", time.Since(t).Milliseconds()) 160 | polyFr.DumpLarge(assignment...) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /snark/hash/mimc.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "github.com/consensys/gkr-mimc/hash" 5 | 6 | "github.com/AlexandreBelling/gnark/frontend" 7 | ) 8 | 9 | // MimcHash returns the result of the hashing function 10 | func MimcHash(cs frontend.API, stream ...frontend.Variable) frontend.Variable { 11 | state := frontend.Variable(0) 12 | for _, m := range stream { 13 | newM := m 14 | for i := 0; i < hash.MimcRounds; i++ { 15 | newM = cs.Add(newM, state) 16 | newM = cs.Add(newM, hash.Arks[i]) 17 | // Raise to the power 7 18 | tmp := cs.Mul(newM, newM) // ^2 19 | tmp = cs.Mul(newM, tmp) // ^3 20 | tmp = cs.Mul(tmp, tmp) // ^6 21 | newM = cs.Mul(newM, tmp) // ^7 22 | } 23 | state = cs.Add(state, newM, state, m) 24 | } 25 | return state 26 | } 27 | -------------------------------------------------------------------------------- /snark/hash/mimc_test.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/consensys/gkr-mimc/common" 8 | "github.com/consensys/gkr-mimc/hash" 9 | 10 | "github.com/AlexandreBelling/gnark/backend" 11 | "github.com/AlexandreBelling/gnark/backend/groth16" 12 | "github.com/AlexandreBelling/gnark/frontend" 13 | "github.com/AlexandreBelling/gnark/test" 14 | "github.com/consensys/gnark-crypto/ecc" 15 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 16 | ) 17 | 18 | type TestMimcCircuit struct { 19 | X [][]frontend.Variable 20 | Y []frontend.Variable 21 | } 22 | 23 | func (c *TestMimcCircuit) Define(cs frontend.API) error { 24 | for k := range c.X { 25 | y := MimcHash(cs, c.X[k]...) 26 | cs.AssertIsEqual(c.Y[k], y) 27 | } 28 | return nil 29 | } 30 | 31 | func Allocate(nTests, testSize int) TestMimcCircuit { 32 | X := make([][]frontend.Variable, nTests) 33 | Y := make([]frontend.Variable, nTests) 34 | for k := range X { 35 | X[k] = make([]frontend.Variable, testSize) 36 | } 37 | 38 | return TestMimcCircuit{ 39 | X: X, 40 | Y: Y, 41 | } 42 | } 43 | 44 | func (c *TestMimcCircuit) Assign(x [][]fr.Element) { 45 | for k := range x { 46 | for n := range x[k] { 47 | c.X[k][n] = x[k][n] 48 | } 49 | y := hash.MimcHash(x[k]) 50 | c.Y[k] = y 51 | } 52 | } 53 | 54 | func TestMimc(t *testing.T) { 55 | 56 | assert := test.NewAssert(t) 57 | c := Allocate(5, 5) 58 | 59 | // Creates a random test vector 60 | x := [][]fr.Element{ 61 | common.RandomFrArray(5), 62 | common.RandomFrArray(5), 63 | common.RandomFrArray(5), 64 | common.RandomFrArray(5), 65 | common.RandomFrArray(5), 66 | } 67 | 68 | witness := Allocate(5, 5) 69 | witness.Assign(x) 70 | assert.SolvingSucceeded(&c, &witness, test.WithBackends(backend.GROTH16), test.WithCurves(ecc.BN254)) 71 | } 72 | 73 | func BenchmarkMimc(b *testing.B) { 74 | 75 | // This will expand the benchmark until, a SEGFAULT happens 76 | // Or there is enough memory to run 32M hashes (=> impossible) 77 | for bn := 10; bn < 25; bn++ { 78 | fmt.Printf("Baseline Mimc7 benchmark bN = %v\n", bn) 79 | 80 | c := Allocate(1< {a0, a1, ... , ad} 11 | type Univariate []frontend.Variable 12 | 13 | // NewUnivariate is the default constructor 14 | func NewUnivariate(coeffs []frontend.Variable) Univariate { 15 | return coeffs 16 | } 17 | 18 | // AllocateUnivariate returns an empty multilinear with a given size 19 | func AllocateUnivariate(degree int) Univariate { 20 | return NewUnivariate(make([]frontend.Variable, degree+1)) 21 | } 22 | 23 | // Assign value to a previously allocated univariate 24 | func (u Univariate) Assign(coeffs []fr.Element) { 25 | if len(coeffs) != len(u) { 26 | panic(fmt.Sprintf("Inconsistent assignment for univariate poly %v != %v", len(coeffs), len(u))) 27 | } 28 | for i, c := range coeffs { 29 | u[i] = c 30 | } 31 | } 32 | 33 | // Eval returns p(x) 34 | func (u Univariate) Eval(cs frontend.API, x frontend.Variable) (res frontend.Variable) { 35 | 36 | res = frontend.Variable(0) 37 | aux := frontend.Variable(0) 38 | 39 | for i := len(u) - 1; i >= 0; i-- { 40 | if i != len(u)-1 { 41 | res = cs.Mul(aux, x) 42 | } 43 | aux = cs.Add(res, u[i]) 44 | } 45 | 46 | // TODO why mul by 1 ? 47 | return cs.Mul(aux, 1) 48 | } 49 | 50 | // ZeroAndOne returns p(0) + p(1) 51 | func (u Univariate) ZeroAndOne(cs frontend.API) frontend.Variable { 52 | res := cs.Add(u[0], u[0], u[1:]...) 53 | return res 54 | } 55 | -------------------------------------------------------------------------------- /snark/polynomial/univariate_test.go: -------------------------------------------------------------------------------- 1 | package polynomial 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/AlexandreBelling/gnark/backend" 7 | "github.com/AlexandreBelling/gnark/frontend" 8 | "github.com/AlexandreBelling/gnark/test" 9 | "github.com/consensys/gnark-crypto/ecc" 10 | ) 11 | 12 | type univariateTestCircuit struct { 13 | Poly Univariate 14 | ZnO frontend.Variable // for testing purposes only 15 | Expected frontend.Variable // for testing purposes only 16 | } 17 | 18 | func (pc *univariateTestCircuit) Define(cs frontend.API) error { 19 | 20 | zno := pc.Poly.ZeroAndOne(cs) 21 | x := 5 22 | PAtX := pc.Poly.Eval(cs, x) 23 | 24 | cs.AssertIsEqual(zno, pc.ZnO) 25 | cs.AssertIsEqual(PAtX, pc.Expected) 26 | 27 | return nil 28 | } 29 | 30 | func TestUnivariate(t *testing.T) { 31 | 32 | degree := 3 33 | var pc univariateTestCircuit 34 | pc.Poly = AllocateUnivariate(degree) 35 | assert := test.NewAssert(t) 36 | 37 | var witness univariateTestCircuit 38 | witness.Poly = make([]frontend.Variable, 4) 39 | // witness <---> X^3 + 2X^2 + 3X + 4 40 | witness.Poly[0] = 4 41 | witness.Poly[1] = 3 42 | witness.Poly[2] = 2 43 | witness.Poly[3] = 1 44 | witness.ZnO = 14 45 | witness.Expected = 194 46 | 47 | assert.ProverSucceeded(&pc, &witness, test.WithBackends(backend.GROTH16), test.WithCurves(ecc.BN254)) 48 | 49 | } 50 | -------------------------------------------------------------------------------- /snark/sumcheck/sumcheck.go: -------------------------------------------------------------------------------- 1 | package sumcheck 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/consensys/gkr-mimc/circuit" 7 | "github.com/consensys/gkr-mimc/snark/hash" 8 | "github.com/consensys/gkr-mimc/snark/polynomial" 9 | "github.com/consensys/gkr-mimc/sumcheck" 10 | 11 | "github.com/AlexandreBelling/gnark/frontend" 12 | ) 13 | 14 | // Proof contains the circuit data of a sumcheck run EXCEPT WHAT IS REQUIRED FOR THE FINAL CHECK. 15 | type Proof []polynomial.Univariate 16 | 17 | // AllocateProof allocates an empty sumcheck verifier 18 | func AllocateProof(bN int, gate circuit.Gate) Proof { 19 | proof := make(Proof, bN) 20 | for i := 0; i < bN; i++ { 21 | proof[i] = polynomial.AllocateUnivariate(gate.Degree() + 1) 22 | } 23 | return proof 24 | } 25 | 26 | // Assign values for the sumcheck verifier 27 | func (p Proof) Assign(proof sumcheck.Proof) { 28 | if len(proof) != len(p) { 29 | panic( 30 | fmt.Sprintf("Inconsistent assignment lenght: expected %v, but got %v", len(p), len(proof)), 31 | ) 32 | } 33 | for i, poly := range proof { 34 | p[i].Assign(poly) 35 | } 36 | } 37 | 38 | // AssertValid verifies a sumcheck instance EXCEPT FOR THE FINAL VERIFICATION. 39 | func (p Proof) AssertValid(cs frontend.API, initialClaim []frontend.Variable) ( 40 | qPrime []frontend.Variable, finalClaim, recombChal frontend.Variable, 41 | ) { 42 | // initialize current claim: 43 | claimCurr, recombChal := recombineMultiClaims(cs, initialClaim) 44 | hs := make([]frontend.Variable, len(p)) 45 | 46 | for i, pol := range p { 47 | zeroAndOne := pol.ZeroAndOne(cs) 48 | cs.AssertIsEqual(zeroAndOne, claimCurr) 49 | hs[i] = hash.MimcHash(cs, pol...) // Hash the polynomial 50 | claimCurr = pol.Eval(cs, hs[i]) // Get new current claim 51 | } 52 | 53 | return hs, claimCurr, recombChal 54 | } 55 | 56 | func recombineMultiClaims(cs frontend.API, claims []frontend.Variable) (claim, challenge frontend.Variable) { 57 | if len(claims) < 1 { 58 | // No recombination 59 | return claims[0], nil 60 | } 61 | challenge = hash.MimcHash(cs, claims...) 62 | return polynomial.Univariate(claims).Eval(cs, challenge), challenge 63 | } 64 | -------------------------------------------------------------------------------- /snark/sumcheck/sumcheck_test.go: -------------------------------------------------------------------------------- 1 | package sumcheck 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/consensys/gkr-mimc/circuit" 7 | "github.com/consensys/gkr-mimc/poly" 8 | "github.com/consensys/gkr-mimc/sumcheck" 9 | 10 | "github.com/AlexandreBelling/gnark/backend" 11 | "github.com/AlexandreBelling/gnark/frontend" 12 | "github.com/AlexandreBelling/gnark/test" 13 | "github.com/consensys/gnark-crypto/ecc" 14 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 15 | ) 16 | 17 | type SumcheckCircuit struct { 18 | InitialClaim []frontend.Variable 19 | Proof Proof 20 | ExpectedQPrime []frontend.Variable 21 | } 22 | 23 | func AllocateSumcheckCircuit(bN, nInstance int, gate circuit.Gate) SumcheckCircuit { 24 | return SumcheckCircuit{ 25 | Proof: AllocateProof(bN, gate), 26 | ExpectedQPrime: make([]frontend.Variable, bN), 27 | InitialClaim: make([]frontend.Variable, nInstance), 28 | } 29 | } 30 | 31 | func (scc *SumcheckCircuit) Define(cs frontend.API) error { 32 | hPrime, _, _ := scc.Proof.AssertValid(cs, scc.InitialClaim) 33 | 34 | for i := range hPrime { 35 | cs.AssertIsEqual(hPrime[i], scc.ExpectedQPrime[i]) 36 | } 37 | 38 | return nil 39 | } 40 | 41 | func (scc *SumcheckCircuit) Assign( 42 | proof sumcheck.Proof, 43 | initialClaim []fr.Element, 44 | expectedQPrime []fr.Element, 45 | ) error { 46 | scc.Proof.Assign(proof) 47 | 48 | for i := range initialClaim { 49 | scc.InitialClaim[i] = initialClaim[i] 50 | } 51 | 52 | for i := range expectedQPrime { 53 | scc.ExpectedQPrime[i] = expectedQPrime[i] 54 | } 55 | 56 | return nil 57 | } 58 | 59 | func genericTest(t *testing.T, X []poly.MultiLin, claims []fr.Element, qs [][]fr.Element, gate circuit.Gate) { 60 | proof, expectedQPrime, _ := sumcheck.Prove(X, qs, claims, gate) 61 | circ := AllocateSumcheckCircuit(len(qs[0]), len(claims), gate) 62 | 63 | _, err := frontend.Compile(ecc.BN254, backend.GROTH16, &circ) 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | witness := AllocateSumcheckCircuit(len(qs[0]), len(claims), gate) 69 | witness.Assign(proof, claims, expectedQPrime) 70 | 71 | err = test.IsSolved(&circ, &witness, ecc.BN254, backend.GROTH16) 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | } 77 | 78 | func TestSumcheckCircuit(t *testing.T) { 79 | 80 | for bn := 0; bn < 15; bn++ { 81 | X, claims, qs, gate := sumcheck.InitializeCipherGateInstance(bn) 82 | genericTest(t, X, claims, qs, gate) 83 | 84 | ninstance := 5 85 | X, claims, qs, gate = sumcheck.InitializeMultiInstance(bn, ninstance) 86 | genericTest(t, X, claims, qs, gate) 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /sumcheck/algo.go: -------------------------------------------------------------------------------- 1 | package sumcheck 2 | 3 | import ( 4 | "github.com/consensys/gkr-mimc/common" 5 | "github.com/consensys/gkr-mimc/poly" 6 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 7 | ) 8 | 9 | const evalSubChunkSize int = 128 10 | 11 | // Returns a closure to perform a chunk of folding 12 | func createFoldingJob(inst *instance, callback chan []fr.Element, r fr.Element, start, stop int) func() { 13 | return func() { 14 | inst.foldChunk(r, start, stop) 15 | // We pass an empty array as a callback 16 | callback <- []fr.Element{} 17 | } 18 | } 19 | 20 | // Returns a closure to perform a chunk of partial evaluation 21 | func createPartialEvalJob(inst *instance, callback chan []fr.Element, start, stop int) func() { 22 | return func() { 23 | // Defers to the chunked method and write the result in the callback channel 24 | callback <- inst.getPartialPolyChunk(start, stop) 25 | } 26 | } 27 | 28 | // Returns a closure to perform a chunk of eqTable job 29 | func createEqTableJob(inst *instance, callback chan []fr.Element, qPrime []fr.Element, start, stop int, multiplier ...fr.Element) func() { 30 | return func() { 31 | // Defers the chunked method for computing the eq table 32 | inst.computeEqTableJob(qPrime, start, stop, multiplier...) 33 | callback <- []fr.Element{} 34 | } 35 | } 36 | 37 | // Returns a closure to perform a partial summation of two bookeeping tables 38 | func createAdditionJob(callback chan []fr.Element, a, b poly.MultiLin, start, stop int) func() { 39 | return func() { 40 | addInPlace(a, b, start, stop) 41 | callback <- []fr.Element{} 42 | } 43 | } 44 | 45 | // Performs the folding on a chunk synchronously 46 | func (inst *instance) foldChunk(r fr.Element, start, stop int) { 47 | inst.Eq.FoldChunk(r, start, stop) 48 | for _, x := range inst.X { 49 | x.FoldChunk(r, start, stop) 50 | } 51 | } 52 | 53 | // Returns the partial poly only on a given portion 54 | func (inst *instance) getPartialPolyChunk(start, stop int) []fr.Element { 55 | 56 | // Define usefull constants 57 | nEvals := inst.degree + 1 58 | nInputs := len(inst.X) 59 | mid := len(inst.Eq) / 2 60 | 61 | // Contains the output of the algo 62 | evals := make([]fr.Element, nEvals) 63 | 64 | // The computation is done by sub-chunks, to allow trading memory for indirections 65 | // Here are the preallocations 66 | tmpEvals := poly.MakeSmall(evalSubChunkSize) 67 | tmpEqs := poly.MakeSmall(evalSubChunkSize) 68 | dEqs := poly.MakeSmall(evalSubChunkSize) 69 | tmpXs := poly.MakeSmall(evalSubChunkSize * nInputs) 70 | dXs := poly.MakeSmall(evalSubChunkSize * nInputs) 71 | 72 | defer poly.DumpSmall(tmpEvals) 73 | defer poly.DumpSmall(tmpEqs) 74 | defer poly.DumpSmall(dEqs) 75 | defer poly.DumpSmall(tmpXs) 76 | defer poly.DumpSmall(dXs) 77 | 78 | // Set of pointers to tmpXs that can be passed directly 79 | // to `gate.Evals` 80 | evaluationBuffer := make([][]fr.Element, nInputs) 81 | 82 | // For each subchunk 83 | for subChunkStart := start; subChunkStart < stop; subChunkStart += evalSubChunkSize { 84 | 85 | // Accounts for the fact that stop -start may not be divided by the sumc 86 | subChunkEnd := common.Min(subChunkStart+evalSubChunkSize, stop) 87 | subChunkLen := subChunkEnd - subChunkStart 88 | 89 | // Precomputations to save a few additions 90 | subChunkStartPlusMid := subChunkStart + mid 91 | subChunkEndPlusMid := subChunkEnd + mid 92 | nInputsSubChunkLen := nInputs * subChunkLen 93 | 94 | if subChunkLen < evalSubChunkSize { 95 | // Can only happen at the last iteration 96 | // Truncate the preallocated tables 97 | tmpEvals = tmpEvals[:subChunkLen] 98 | tmpEqs = tmpEqs[:subChunkLen] 99 | dEqs = dEqs[:subChunkLen] 100 | tmpXs = tmpXs[:subChunkLen*nInputs] 101 | dXs = dXs[:subChunkLen*nInputs] 102 | } 103 | 104 | // Special case: evaluation at t = 0 105 | 106 | // => directly use the values given in inst for Eq 107 | // So we don't do copies of Eq 108 | 109 | for k := 0; k < nInputs; k++ { 110 | // Redirect the evaluation table directly to inst 111 | // So we don't copy into tmpXs 112 | evaluationBuffer[k] = inst.X[k][subChunkStart:subChunkEnd] 113 | } 114 | 115 | // evaluate the gate with inputs pointed to by the evaluation buffer 116 | inst.gate.EvalBatch(tmpEvals, evaluationBuffer...) 117 | 118 | // Then update the evalsValue 119 | evalPtr := &evals[0] // 0 because t = 0 120 | var v fr.Element 121 | eqChunk := inst.Eq[subChunkStart:subChunkEnd] 122 | 123 | for x := 0; x < subChunkLen; x++ { 124 | v.Mul(&eqChunk[x], &tmpEvals[x]) 125 | evalPtr.Add(evalPtr, &v) 126 | } 127 | 128 | // Second special case : evaluation at t = 1 129 | 130 | // => directly use the values given in inst for Eq 131 | // So we don't do copies of Eq 132 | 133 | for k := range inst.X { 134 | // Redirect the evaluation table directly to inst 135 | // So we don't copy into tmpXs 136 | evaluationBuffer[k] = inst.X[k][subChunkStartPlusMid:subChunkEndPlusMid] 137 | } 138 | 139 | // Recall that evaluationBuffer is a set of pointers to subslices of tmpXs 140 | inst.gate.EvalBatch(tmpEvals, evaluationBuffer...) 141 | 142 | // Then update the evalsValue 143 | evalPtr = &evals[1] // 1 because t = 1 144 | eqChunk = inst.Eq[subChunkStartPlusMid:subChunkEndPlusMid] 145 | 146 | for x := 0; x < subChunkLen; x++ { 147 | v.Mul(&eqChunk[x], &tmpEvals[x]) 148 | evalPtr.Add(evalPtr, &v) 149 | } 150 | 151 | // Then regular case t >= 2 152 | 153 | // Initialize the eq and dEq table, at the value for t = 1 154 | // (We get the next values for t by adding dEqs) 155 | copy(tmpEqs, inst.Eq[subChunkStartPlusMid:subChunkEndPlusMid]) 156 | for x := 0; x < subChunkLen; x++ { 157 | dEqs[x].Sub(&inst.Eq[subChunkStartPlusMid+x], &inst.Eq[subChunkStart+x]) 158 | } 159 | 160 | for k := range inst.X { 161 | kOffset := k * subChunkLen 162 | 163 | // Initializes the dXs as P(t=1, x) - P(t=0, x) 164 | for x := 0; x < subChunkLen; x++ { 165 | dXs[kOffset+x].Sub(&inst.X[k][subChunkStartPlusMid+x], &inst.X[k][subChunkStart+x]) 166 | } 167 | 168 | // As for eq, we initialize each input table `X` with the value for t = 1 169 | // (We get the next values for t by adding dXs) 170 | copy(tmpXs[kOffset:kOffset+subChunkLen], inst.X[k][subChunkStartPlusMid:subChunkEndPlusMid]) 171 | 172 | // Also, we redirect the evaluation buffer over each subslice of tmpXs 173 | // So we can easily pass each of these values of to the `gates.EvalBatch` table 174 | evaluationBuffer[k] = tmpXs[kOffset : kOffset+subChunkLen] 175 | } 176 | 177 | for t := 2; t < nEvals; t++ { 178 | 179 | // Then update the evals at position t 180 | evalPtr = &evals[t] 181 | 182 | for x := 0; x < subChunkLen; x++ { 183 | tmpEqs[x].Add(&tmpEqs[x], &dEqs[x]) 184 | } 185 | 186 | // Update the value of tmpXs : as dXs and tmpXs have the same layout, 187 | // no need to make a double loop on k : the index of the separate inputs 188 | // We can do this, because P is multilinear so P(t+1,x) = P(t, x) + dX(x) 189 | for kx := 0; kx < nInputsSubChunkLen; kx++ { 190 | tmpXs[kx].Add(&tmpXs[kx], &dXs[kx]) 191 | } 192 | 193 | // Recall that evaluationBuffer is a set of pointers to subslices of tmpXs 194 | inst.gate.EvalBatch(tmpEvals, evaluationBuffer...) 195 | 196 | for x := 0; x < subChunkLen; x++ { 197 | v.Mul(&tmpEqs[x], &tmpEvals[x]) 198 | evalPtr.Add(evalPtr, &v) 199 | } 200 | 201 | } 202 | } 203 | 204 | return evals 205 | } 206 | 207 | // The size of a chunk is always assumed to be 1 << 12 208 | // This matters because we need powers of two for this to work 209 | func (inst *instance) computeEqTableJob(qPrime []fr.Element, start, stop int, multiplier ...fr.Element) { 210 | preallocatedEq := inst.Eq 211 | for chunkID := start; chunkID < stop; chunkID++ { 212 | // Just defers to the dedicated function 213 | poly.ChunkOfEqTable(preallocatedEq, chunkID, eqTableChunkSize, qPrime, multiplier...) 214 | } 215 | } 216 | 217 | // Add the second table into the first one for a given chunk 218 | // b is unchanged 219 | func addInPlace(a, b poly.MultiLin, start, stop int) { 220 | for i := start; i < stop; i++ { 221 | a[i].Add(&a[i], &b[i]) 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /sumcheck/instance.go: -------------------------------------------------------------------------------- 1 | package sumcheck 2 | 3 | import ( 4 | "github.com/consensys/gkr-mimc/circuit" 5 | "github.com/consensys/gkr-mimc/poly" 6 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 7 | ) 8 | 9 | // instance represents a sumcheck multivariate polynomials evaluation 10 | // gi(X) = \sum_{Y} (eq(X, Y) Gi(ai(Y), bi(Y)) 11 | // X is the evaluation point 12 | type instance struct { 13 | X []poly.MultiLin 14 | Eq poly.MultiLin 15 | gate circuit.Gate 16 | // Overall degree of the sumcheck instance 17 | degree int 18 | } 19 | 20 | // Evaluate a sumcheck's sum. IT IS ONLY USED FOR TESTING. 21 | // The sum "proven" by the sumchecks is the following, for all `j` 22 | // 23 | // \sum_{i} eq(qPrime[j], i) * Gate(X[1][i], ..., X[n][i]) 24 | // 25 | // For multiple qPrimes, the protocol does not return multiple values 26 | // but instead a `deterministic` random linear combination. 27 | // 28 | // INPUTS 29 | // 30 | // - `gate` is a `circuit.Gate` object. It represents a low-degree multivariate polynomial. 31 | // - `qPrime[{j}]` is a multilinear variable (so a tuple of field element). For all `k` and `j` we must have 32 | // `2 ^ len(qPrime[j]) == len(X[k]) 33 | // - `the claims`, this makes the function useless. But in practice it helps for testing because 34 | // it will try to get the same result 35 | // - `x[{k}][{i}]` is a double slice of field element. Each subslice `X[{k}]` represent a (multilinear) polynomial 36 | // being part of the sumcheck. Each of those is expressed as a slice of evaluation over the hypercube 37 | // in lexicographic order. 38 | // 39 | // OUTPUT 40 | // 41 | // - a single field element, the random linear combination of all the obtained claim 42 | // 43 | // IMPROVEMENTS 44 | // 45 | // The function could be improved by using `transcript.FiatShamir` from gnark. Or just take 46 | // the coefficient of the random linear combination as inputs. Or just return all the claim 47 | // instead of taking them as inputs. 48 | // 49 | func Evaluation(gate circuit.Gate, qPrime [][]fr.Element, claims []fr.Element, x ...poly.MultiLin) (res fr.Element) { 50 | 51 | inst_ := instance{X: x, gate: gate, degree: gate.Degree() + 1, Eq: poly.MakeLarge(1 << len(qPrime[0]))} 52 | makeEqTable(&inst_, claims, qPrime, nil) 53 | defer poly.DumpLarge(inst_.Eq) 54 | 55 | var tmp fr.Element 56 | buf := make([]*fr.Element, len(x)) 57 | 58 | for n := range x[0] { 59 | for k := range x { 60 | buf[k] = &x[k][n] 61 | } 62 | gate.Eval(&tmp, buf...) 63 | tmp.Mul(&tmp, &inst_.Eq[n]) 64 | res.Add(&res, &tmp) 65 | } 66 | 67 | return res 68 | } 69 | -------------------------------------------------------------------------------- /sumcheck/prover.go: -------------------------------------------------------------------------------- 1 | package sumcheck 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/consensys/gkr-mimc/circuit" 8 | "github.com/consensys/gkr-mimc/common" 9 | "github.com/consensys/gkr-mimc/poly" 10 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 11 | ) 12 | 13 | // Minimal size of chunks before considering parallelization for a task 14 | const ( 15 | foldingMinTaskSize int = 1 << 10 16 | partialEvalMinTaskSize int = 1 << 6 17 | eqTableChunkSize int = 1 << 8 18 | addInplaceMinChunkSize int = 1 << 10 19 | ) 20 | 21 | // Proof is the object produced by the prover 22 | type Proof [][]fr.Element 23 | 24 | // Prove contains the coordination logic for all workers contributing to a (multi)-sumcheck proof 25 | // The sum "proven" by the sumchecks is the following, for all `j` 26 | // 27 | // \sum_{i} eq(qPrime[j], i) * Gate(X[1][i], ..., X[n][i]) 28 | // 29 | // INPUTS 30 | // 31 | // - `X[{k}][{i}]` is a double slice of field element. Each subslice `X[{k}]` represent a (multilinear) polynomial 32 | // being part of the sumcheck. Each of those is expressed as a slice of evaluation over the hypercube 33 | // in lexicographic order. 34 | // - `qPrime[{j}]` is a multilinear variable (so a tuple of field element). For all `k` and `j` we must have 35 | // `2 ^ len(qPrime[j]) == len(X[k]) 36 | // - `claims` is the list of alleged values of each of the sum. We must have `len(claims) == len(qPrime)`. It is 37 | // only used for Fiat-Shamir 38 | // - `gate` is a `circuit.Gate` object. It represents a low-degree multivariate polynomial. 39 | // 40 | // OUTPUTS 41 | // 42 | // - `proof` contains all the intermediate prover messages generated during the sumcheck protocol 43 | // - `challenges` the challenges generated during the protocol 44 | // - `claims` contains the evaluation of each X_{i}(challenges). The last entry is the evaluation 45 | // of `eq(qPrime, challenges)` 46 | func Prove(X []poly.MultiLin, qPrimes [][]fr.Element, claims []fr.Element, gate circuit.Gate) (proof Proof, challenges, finalClaims []fr.Element) { 47 | 48 | // Define usefull constants & initializes the instance 49 | bN := len(qPrimes[0]) 50 | 51 | // Sanity-checks : all X should have length 1< To be sure, this will never clog 64 | callback := make(chan []fr.Element, 8*runtime.NumCPU()) 65 | 66 | // Precomputes the eq table 67 | makeEqTable(inst, claims, qPrimes, callback) 68 | 69 | // Run on hPrime 70 | for k := 0; k < bN; k++ { 71 | evals := dispatchPartialEvals(inst, callback) 72 | proof[k] = poly.InterpolateOnRange(evals) 73 | r := common.GetChallenge(proof[k]) 74 | dispatchFolding(inst, r, callback) 75 | challenges[k] = r 76 | } 77 | 78 | // Save the final claims on each poly and dump the polys 79 | finalClaims = make([]fr.Element, 0, len(inst.X)+1) 80 | finalClaims = append(finalClaims, inst.Eq[0]) 81 | poly.DumpLarge(inst.Eq) 82 | 83 | for _, x := range inst.X { 84 | finalClaims = append(finalClaims, x[0]) 85 | poly.DumpLarge(x) 86 | } 87 | 88 | return proof, challenges, finalClaims 89 | 90 | } 91 | 92 | // initializeInstance returns an instance with L, R, gates, and degree sets 93 | func makeInstance(X []poly.MultiLin, gate circuit.Gate) *instance { 94 | n := len(X[0]) 95 | return &instance{X: X, Eq: poly.MakeLarge(n), gate: gate, degree: gate.Degree() + 1} 96 | 97 | } 98 | 99 | // creates the eq table for the current instance, with possibly many claims at different points 100 | // If there are multiple claims and evaluations points, then it returns a random linear combination's 101 | // coefficients of the claims and qPrime 102 | func makeEqTable( 103 | inst *instance, 104 | claims []fr.Element, qPrimes [][]fr.Element, 105 | callback chan []fr.Element, 106 | ) (rnd fr.Element) { 107 | 108 | if callback == nil { 109 | // If no callback is provided, create one on the spot 110 | callback = make(chan []fr.Element, 8*runtime.NumCPU()) 111 | } 112 | 113 | if len(claims) != len(qPrimes) && len(qPrimes) > 1 { 114 | panic(fmt.Sprintf("provided a multi-instance %v but only the number of claim does not match %v", len(qPrimes), len(claims))) 115 | } 116 | 117 | // First generate the eq table for the first qPrime directly inside the instance 118 | dispatchEqTable(inst, qPrimes[0], callback) 119 | 120 | // Only one claim => no random linear combinations 121 | if len(claims) < 1 { 122 | return fr.Element{} 123 | } 124 | 125 | // Else generate a random coefficient x 126 | // The random linear combination will be of the form 127 | // C = a0 + a1*r + a2*r^2 + a3*r^3 which will be enough 128 | initialMultiplier := common.GetChallenge(claims) 129 | multiplier := initialMultiplier 130 | 131 | // Initializes a dummy instance with just an eqTable 132 | tmpInst := &instance{Eq: poly.MakeLarge(1 << len(qPrimes[0]))} 133 | 134 | for i := 1; i < len(qPrimes); i++ { 135 | dispatchEqTable(tmpInst, qPrimes[i], callback, multiplier) 136 | dispatchAdditions(callback, inst.Eq, tmpInst.Eq) 137 | multiplier.Mul(&multiplier, &initialMultiplier) 138 | } 139 | 140 | poly.DumpLarge(tmpInst.Eq) 141 | 142 | // Returns the seed of the linear combination 143 | return initialMultiplier 144 | } 145 | 146 | // Calls the partial evals by calling the worker pool if that's usefull 147 | // evalChan is passed for reuse purpose 148 | func dispatchPartialEvals(inst *instance, callback chan []fr.Element) []fr.Element { 149 | mid := len(inst.Eq) / 2 150 | 151 | nTasks := common.TryDispatch(mid, partialEvalMinTaskSize, func(start, stop int) { 152 | jobQueue <- createPartialEvalJob(inst, callback, start, stop) 153 | }) 154 | 155 | // `0` means the tasks where not dispatched as it 156 | // deemed unprofitable to parallelize this task 157 | if nTasks < 1 { 158 | return inst.getPartialPolyChunk(0, mid) 159 | } 160 | 161 | // Otherwise consumes happily the callback channel and return the eval 162 | return consumeAccumulate(callback, nTasks) 163 | } 164 | 165 | // Calls the folding by either passing to the worker pool if this is deemed usefull 166 | // or synchronously if not 167 | func dispatchFolding(inst *instance, r fr.Element, callback chan []fr.Element) { 168 | mid := len(inst.Eq) / 2 169 | 170 | nbTasks := common.TryDispatch(mid, foldingMinTaskSize, func(start, stop int) { 171 | jobQueue <- createFoldingJob(inst, callback, r, start, stop) 172 | }) 173 | 174 | // `0` means the tasks where not dispatched as it 175 | // deemed unprofitable to parallelize this task 176 | if nbTasks < 1 { 177 | inst.foldChunk(r, 0, mid) 178 | } else { 179 | // Otherwise, wait for all callback to be done 180 | for i := 0; i < nbTasks; i++ { 181 | <-callback 182 | } 183 | } 184 | 185 | // Finallly cut in half the tables 186 | inst.Eq = inst.Eq[:mid] 187 | for i := range inst.X { 188 | inst.X[i] = inst.X[i][:mid] 189 | } 190 | } 191 | 192 | // Computes the eq table for the comming round 193 | func dispatchEqTable(inst *instance, qPrime []fr.Element, callback chan []fr.Element, multiplier ...fr.Element) { 194 | nbChunks := len(inst.Eq) / eqTableChunkSize 195 | 196 | // No need to fix limit size of the batch as it already done 197 | minTaskSize := 1 198 | nbTasks := common.TryDispatch(nbChunks, minTaskSize, func(start, stop int) { 199 | jobQueue <- createEqTableJob(inst, callback, qPrime, start, stop, multiplier...) 200 | }) 201 | 202 | if nbTasks < 1 { 203 | // All in one chunk 204 | poly.FoldedEqTable(inst.Eq, qPrime, multiplier...) 205 | return 206 | } 207 | 208 | // Otherwise, wait for all callback to be done 209 | for i := 0; i < nbTasks; i++ { 210 | <-callback 211 | } 212 | } 213 | 214 | // Dispatch the addition of two bookkeeping table to be run in parallel 215 | func dispatchAdditions(callback chan []fr.Element, a, b poly.MultiLin) { 216 | // Attempts running in the pool 217 | nbTasks := common.TryDispatch(len(a), addInplaceMinChunkSize, func(start, stop int) { 218 | jobQueue <- createAdditionJob(callback, a, b, start, stop) 219 | }) 220 | 221 | // The pool returning 0 means you need to run it monothreaded 222 | if nbTasks < 1 { 223 | // All in one chunk 224 | addInPlace(a, b, 0, len(a)) 225 | return 226 | } 227 | 228 | // Otherwise, wait for all callback to be done 229 | for i := 0; i < nbTasks; i++ { 230 | <-callback 231 | } 232 | } 233 | 234 | // ConsumeAccumulate consumes `nToConsume` elements from `ch`, 235 | // and return their sum Element-wise 236 | func consumeAccumulate(ch chan []fr.Element, nToConsume int) []fr.Element { 237 | res := <-ch 238 | for i := 0; i < nToConsume-1; i++ { 239 | tmp := <-ch 240 | for i := range res { 241 | res[i].Add(&res[i], &tmp[i]) 242 | } 243 | } 244 | return res 245 | } 246 | -------------------------------------------------------------------------------- /sumcheck/prover_test.go: -------------------------------------------------------------------------------- 1 | package sumcheck 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "testing" 7 | 8 | "github.com/consensys/gkr-mimc/circuit" 9 | "github.com/consensys/gkr-mimc/common" 10 | "github.com/consensys/gkr-mimc/poly" 11 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestFolding(t *testing.T) { 16 | for bn := 2; bn < 15; bn++ { 17 | X, _, qPrime, gate := InitializeCipherGateInstance(bn) 18 | instance := makeInstance(X, gate) 19 | 20 | callback := make(chan []fr.Element, 100000) 21 | 22 | // Test that the Eq function agrees 23 | dispatchEqTable(instance, qPrime[0], callback) 24 | eqBis := poly.MakeLarge(len(X[0])) 25 | eqBis = poly.FoldedEqTable(eqBis, qPrime[0]) 26 | 27 | assert.Equal(t, eqBis.String(), instance.Eq.String(), "eq tables do not match after being prefolded") 28 | 29 | // Test that the folding agrees 30 | dispatchFolding(instance, qPrime[0][0], callback) 31 | eqBis.Fold(qPrime[0][0]) 32 | 33 | assert.Equal(t, eqBis.String(), instance.Eq.String(), "eq tables do not match after folding") 34 | 35 | poly.DumpLarge(X[0]) 36 | poly.DumpLarge(X[1]) 37 | poly.DumpLarge(instance.Eq) 38 | poly.DumpLarge(eqBis) 39 | } 40 | } 41 | 42 | func genericTest(t *testing.T, X []poly.MultiLin, claims []fr.Element, qs [][]fr.Element, gate circuit.Gate) { 43 | 44 | instance := makeInstance(X, gate) 45 | 46 | // Then initializes the eq table 47 | rnd := makeEqTable(instance, claims, qs, nil) 48 | claimTest := Evaluation(gate, qs, claims, X...) 49 | 50 | if !rnd.IsZero() { 51 | eval := poly.EvalUnivariate(claims, rnd) 52 | 53 | if eval != claimTest { 54 | panic(fmt.Sprintf("the random linear combination did not match de claim %v != %v \nthe claims are %v\nrecomb chal is %v", 55 | claimTest.String(), eval.String(), common.FrSliceToString(claims), rnd.String())) 56 | } 57 | } 58 | 59 | proof, challenges, fClm := Prove(X, qs, claims, gate) 60 | challengesV, expectedValue, recombChal, err := Verify(claims, proof) 61 | 62 | assert.NoErrorf(t, err, "sumcheck was not deemed valid %v", err) 63 | assert.Equal(t, challenges, challengesV, "prover's and verifier challenges do not match") 64 | assert.Equal(t, rnd, recombChal, "recombination challenges do not match") 65 | 66 | var expVal fr.Element 67 | 68 | // Makes an array of pointer from the array 69 | ptrArr := make([]*fr.Element, len(fClm)-1) 70 | for k := range ptrArr { 71 | ptrArr[k] = &fClm[k+1] 72 | } 73 | 74 | gate.Eval(&expVal, ptrArr...) 75 | expVal.Mul(&expVal, &fClm[0]) 76 | assert.Equal(t, expectedValue.String(), expVal.String(), "inconsistency of the final values for the verifier") 77 | 78 | } 79 | 80 | func TestWithMultiIdentity(t *testing.T) { 81 | for bn := 0; bn < 15; bn++ { 82 | ninstance := 10 83 | X, claims, qs, gate := InitializeMultiInstance(bn, ninstance) 84 | genericTest(t, X, claims, qs, gate) 85 | } 86 | } 87 | 88 | func TestWithCipherGate(t *testing.T) { 89 | 90 | for bn := 0; bn < 15; bn++ { 91 | X, claims, qs, gate := InitializeCipherGateInstance(bn) 92 | genericTest(t, X, claims, qs, gate) 93 | } 94 | } 95 | 96 | func BenchmarkWithCipherGate(b *testing.B) { 97 | bn := 22 98 | b.Run(fmt.Sprintf("sumcheck-bn-%v", bn), func(b *testing.B) { 99 | common.ProfileTrace(b, false, false, func() { 100 | for c_ := 0; c_ < b.N; c_++ { 101 | b.StopTimer() 102 | X, claims, qPrime, gate := InitializeCipherGateInstance(bn) 103 | b.StartTimer() 104 | _, _, _ = Prove(X, qPrime, claims, gate) 105 | } 106 | b.StopTimer() 107 | }) 108 | }) 109 | } 110 | 111 | func BenchmarkMultiIdentity(b *testing.B) { 112 | bn := 22 113 | nInstance := 91 114 | b.Run(fmt.Sprintf("sumcheck-bn-%v", bn), func(b *testing.B) { 115 | common.ProfileTrace(b, false, true, func() { 116 | for c_ := 0; c_ < b.N; c_++ { 117 | b.StopTimer() 118 | X, claims, qPrime, gate := InitializeMultiInstance(bn, nInstance) 119 | b.StartTimer() 120 | _, _, _ = Prove(X, qPrime, claims, gate) 121 | } 122 | b.StopTimer() 123 | }) 124 | }) 125 | } 126 | 127 | func BenchmarkPartialEvalWithCipher(b *testing.B) { 128 | bn := 15 129 | b.Run(fmt.Sprintf("sumcheck-bn-%v", bn), func(b *testing.B) { 130 | common.ProfileTrace(b, true, false, func() { 131 | 132 | // Prepare the benchmark 133 | X, claims, qPrime, gate := InitializeCipherGateInstance(bn) 134 | inst := makeInstance(X, gate) 135 | callback := make(chan []fr.Element, 8*runtime.NumCPU()) 136 | makeEqTable(inst, claims, qPrime, callback) 137 | 138 | b.ResetTimer() 139 | for c_ := 0; c_ < b.N; c_++ { 140 | for _i := 0; _i < 30000; _i++ { 141 | dispatchPartialEvals(inst, callback) 142 | } 143 | } 144 | b.StopTimer() 145 | }) 146 | }) 147 | } 148 | -------------------------------------------------------------------------------- /sumcheck/testing.go: -------------------------------------------------------------------------------- 1 | package sumcheck 2 | 3 | import ( 4 | "github.com/consensys/gkr-mimc/circuit" 5 | "github.com/consensys/gkr-mimc/circuit/gates" 6 | "github.com/consensys/gkr-mimc/common" 7 | "github.com/consensys/gkr-mimc/poly" 8 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 9 | ) 10 | 11 | func InitializeCipherGateInstance(bn int) (X []poly.MultiLin, claims []fr.Element, qPrime [][]fr.Element, gate circuit.Gate) { 12 | 13 | q := common.RandomFrArray(bn) 14 | gate = gates.NewCipherGate(fr.NewElement(145646)) 15 | 16 | L := poly.MakeLarge(1 << bn) 17 | R := poly.MakeLarge(1 << bn) 18 | 19 | for i := range L { 20 | L[i].SetUint64(uint64(i)) 21 | R[i].SetUint64(uint64(i)) 22 | } 23 | 24 | claim := Evaluation(gate, [][]fr.Element{q}, []fr.Element{}, L, R) 25 | return []poly.MultiLin{L, R}, []fr.Element{claim}, [][]fr.Element{q}, gate 26 | } 27 | 28 | func InitializeMultiInstance(bn, ninstance int) (X []poly.MultiLin, claims []fr.Element, qPrime [][]fr.Element, gate circuit.Gate) { 29 | 30 | n := 1 << bn 31 | gate = gates.IdentityGate{} 32 | 33 | // Create the qs 34 | qs := make([][]fr.Element, ninstance) 35 | for i := range qs { 36 | q := make([]fr.Element, bn) 37 | for j := range q { 38 | q[j].SetUint64(uint64(i*j + i)) 39 | } 40 | qs[i] = q 41 | } 42 | 43 | L := poly.MakeLarge(n) 44 | R := poly.MakeLarge(n) 45 | 46 | for i := range L { 47 | L[i].SetUint64(uint64(i)) 48 | R[i].SetUint64(uint64(i)) 49 | } 50 | 51 | claims = make([]fr.Element, ninstance) 52 | for i := range claims { 53 | claims[i] = Evaluation(gate, [][]fr.Element{qs[i]}, []fr.Element{}, L, R) 54 | } 55 | 56 | return []poly.MultiLin{L, R}, claims, qs, gate 57 | } 58 | -------------------------------------------------------------------------------- /sumcheck/verifier.go: -------------------------------------------------------------------------------- 1 | package sumcheck 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/consensys/gkr-mimc/common" 7 | "github.com/consensys/gkr-mimc/poly" 8 | "github.com/consensys/gnark-crypto/ecc/bn254/fr" 9 | ) 10 | 11 | // Verifier of a sumcheck protocol. Performs all the steps except the 12 | // last one where the verifier is required to evaluate the inputs polynomial 13 | // at a single point. 14 | // 15 | // INPUTS 16 | // 17 | // claims : alleged value of the sum. Can be multiple values for multiple sums 18 | // proofs : the prover's message during the execution of the sumcheck protocol 19 | // 20 | // RETURNS 21 | // 22 | // - challenge : the new challenges 23 | // - recombChal : the Fiat-Shamir challenge used to compute the linear combination 24 | // of the `eq`s. Empty if len(qPrime) == 1 25 | // - err : nil if the verifier passes 26 | // - final claims : alleged evaluations of the inputs of the polynomial. 27 | // 28 | func Verify(claims []fr.Element, proof Proof) (challenges []fr.Element, finalClaim, recombChal fr.Element, err error) { 29 | // Initalize the structures 30 | bn := len(proof) 31 | challenges = make([]fr.Element, bn) 32 | 33 | var expectedValue fr.Element 34 | expectedValue, recombChal = recombineMultiClaims(claims) 35 | 36 | var actualValue, r, zero, one, evalAtOne fr.Element 37 | one.SetOne() 38 | 39 | for i := 0; i < bn; i++ { 40 | // Check P_i(0) + P_i(1) == expected 41 | actualValue = poly.EvalUnivariate(proof[i], zero) 42 | evalAtOne = poly.EvalUnivariate(proof[i], one) 43 | actualValue.Add(&actualValue, &evalAtOne) 44 | 45 | if expectedValue != actualValue { 46 | return nil, fr.Element{}, fr.Element{}, fmt.Errorf("at round %v verifier eval at 0 + 1 = %v || expected = %v", i, actualValue.String(), expectedValue.String()) 47 | } 48 | 49 | r = common.GetChallenge(proof[i]) 50 | challenges[i] = r 51 | // expectedValue = P_i(r) 52 | expectedValue = poly.EvalUnivariate(proof[i], r) 53 | } 54 | 55 | return challenges, expectedValue, recombChal, nil 56 | } 57 | 58 | func recombineMultiClaims(claims []fr.Element) (claim, challenge fr.Element) { 59 | if len(claims) < 1 { 60 | // No recombination 61 | return claims[0], fr.Element{} 62 | } 63 | challenge = common.GetChallenge(claims) 64 | return poly.EvalUnivariate(claims, challenge), challenge 65 | } 66 | -------------------------------------------------------------------------------- /sumcheck/worker.go: -------------------------------------------------------------------------------- 1 | package sumcheck 2 | 3 | import ( 4 | "runtime" 5 | ) 6 | 7 | // Closing the jobQueue schedules the end of the pool 8 | var jobQueue chan func() 9 | 10 | func init() { 11 | startWorkerPool() 12 | } 13 | 14 | func startWorkerPool() { 15 | // Initialize the jobQueue 16 | jobQueue = make(chan func(), 8*runtime.NumCPU()) 17 | 18 | nbWorkers := runtime.NumCPU() 19 | for i := 0; i < nbWorkers; i++ { 20 | go func() { 21 | for job := range jobQueue { 22 | job() 23 | } 24 | }() 25 | } 26 | } 27 | --------------------------------------------------------------------------------