├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── task.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── build.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── abstractor ├── abstractor.go └── interface.go ├── extractor ├── extractor.go ├── interface.go ├── lean_export.go ├── misc.go └── test │ ├── another_circuit_test.go │ ├── circuit_with_parameter_test.go │ ├── deletion_mbu_circuit_test.go │ ├── merkle_recover_test.go │ ├── my_circuit_test.go │ ├── slices_optimisation_test.go │ ├── to_binary_circuit_test.go │ ├── two_gadgets_test.go │ └── utils_test.go ├── go.mod ├── go.sum └── test ├── TestAnotherCircuit.lean ├── TestCircuitWithParameter.lean ├── TestDeletionMbuCircuit.lean ├── TestExtractCircuits.lean ├── TestExtractGadgets.lean ├── TestExtractGadgetsVectors.lean ├── TestGadgetExtraction.lean ├── TestMerkleRecover.lean ├── TestMyCircuit.lean ├── TestSlicesOptimisation.lean ├── TestToBinaryCircuit.lean └── TestTwoGadgets.lean /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # General Code 2 | * @reilabs/formal 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Found something wrong? Let us know. 4 | title: "[BUG] " 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | # Describe the Bug 10 | 11 | A clear and concise description of what the bug is. 12 | 13 | # To Reproduce 14 | 15 | Steps to reproduce the behavior: 16 | 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | # Expected Behaviour 23 | 24 | A clear and concise description of what you expected to happen. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/task.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Task 3 | about: Something that needs doing. 4 | title: "[TASK] " 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | # Description 10 | 11 | What is this about? 12 | 13 | # Spec 14 | 15 | Give details. 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | 4 | 5 | # Details 6 | 7 | 8 | 9 | # Checklist 10 | 11 | - [ ] Documentation has been updated if necessary. 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: push 3 | 4 | jobs: 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout repo 10 | uses: actions/checkout@v3 11 | with: 12 | fetch-depth: 0 13 | - name: Set up Go 14 | uses: actions/setup-go@v4 15 | with: 16 | go-version-file: './go.mod' 17 | - name: Go Format 18 | run: gofmt -s -w . && git diff --exit-code 19 | - name: Go Vet 20 | run: go vet ./... 21 | - name: Go Tidy 22 | run: go mod tidy && git diff --exit-code 23 | - name: Go Mod 24 | run: go mod download 25 | - name: Go Mod Verify 26 | run: go mod verify 27 | - name: Build 28 | run: go build -v ./... 29 | - name: Test 30 | run: go test -v -count=1 -shuffle=on ./... 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This document exists as a brief introduction for how you can contribute to this 4 | repository. It includes a guide to 5 | [the structure of the repository](#repository-structure), 6 | [building and testing](#building-and-testing) and 7 | [getting your code on `main`](#getting-your-code-on-main). 8 | 9 | If you are new to Go, there is a [guide](#new-to-go) to getting started with the 10 | language that provides some basic resources for getting started. 11 | 12 | > ## Font Display in Powershell 13 | > 14 | > If using Powershell, change font to `NSimSun` to be able to see all characters 15 | > properly. This is taken from 16 | > [this stackoverflow answer](https://stackoverflow.com/a/48029600). 17 | 18 | ## Repository Structure 19 | 20 | This repository consists of two primary parts. 21 | 22 | - [`abstractor`](./abstractor): The abstractor, which is responsible for 23 | providing an abstract front-end to various ZK systems. 24 | - [`extractor`](./extractor): The extractor, which is responsible for providing 25 | the interface and tools that allow the generation of the Lean code from the 26 | circuit defined in Go. 27 | 28 | ## Building and Testing 29 | 30 | You can build and test the go project as follows. 31 | 32 | 1. Clone the repository into a location of your choice. 33 | 34 | ```sh 35 | git clone https://github.com/reilabs/gnark-lean-extractor gnark-lean-demo 36 | ``` 37 | 38 | 2. Build the go circuit project using `go` (meaning that you will need to have 39 | that toolchain set up). 40 | 41 | ```sh 42 | cd gnark-lean-abstractor 43 | go build 44 | ``` 45 | 46 | 3. To run the tests, you can also use `go`. 47 | 48 | ```sh 49 | go test 50 | ``` 51 | 52 | ## Getting Your Code on `main` 53 | 54 | This repository works on a fork and 55 | [pull request](https://github.com/reilabs/gnark-lean-demo/pulls) workflow, with 56 | code review and CI as an integral part of the process. This works as follows: 57 | 58 | 1. If necessary, you fork the repository, but if you have access to do so please 59 | create a branch. 60 | 2. You make your changes on that branch. 61 | 3. Pull request that branch against main. 62 | 4. The pull request will be reviewed and CI will be run on it. 63 | 5. Once the reviewer(s) have accepted the code and CI has passed, the code will 64 | be merged to `main`. 65 | 66 | ## New to Go? 67 | 68 | If you are new to working with [Go](https://go.dev), a great place to start is 69 | the official set of [tutorials](https://go.dev/learn/). They explain how to 70 | [install](https://go.dev/doc/install) and set the language up, as well as an 71 | [interactive tour](https://go.dev/tour/welcome/1) of how to use the language. 72 | 73 | We recommend being familiar with the language and the `go` command-line 74 | interface to the build system and compiler before interacting with the Go 75 | portion of this repository. 76 | 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2023 Reilabs Sp. z o.o. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | # Gnark Lean Extractor 10 | 11 | This repository contains a Go library that transpiles 12 | [zero-knowledge](https://en.wikipedia.org/wiki/Zero-knowledge_proof) (ZK) 13 | circuits from [Go](https://go.dev) to [Lean](https://leanprover.github.io). In 14 | particular, it deals with circuits constructed as part of the 15 | [gnark](https://github.com/ConsenSys/gnark) proof system. 16 | 17 | This makes possible to take existing gnark circuits and export them to Lean 18 | for later formal verification. 19 | 20 | For an overview of how to use this library, see both the [example](#example) and 21 | [usage guide](#how-to-use-the-library) below. If you are interested in 22 | contributing, or are new to Go, please see our 23 | [contributing guidelines](./CONTRIBUTING.md) for more information. 24 | 25 | ## Compatibility 26 | This version of `gnark-lean-extractor` is compatible with `gnark v0.9.x`. 27 | It is recommended to import [`ProvenZK-v1.4.0`](https://github.com/reilabs/proven-zk/tree/v1.4.0) in Lean4 to process the circuits extracted with this version of `gnark-lean-extractor`. 28 | 29 | For compatibility with `gnark v0.8.x`, use `gnark-lean-extractor-v2.2.0`. 30 | 31 | ## Example 32 | 33 | The following is a brief example of how to design a simple gnark circuit in 34 | conjunction with the extractor library. 35 | 36 | ```go 37 | type MyCircuit struct { 38 | In_1 frontend.Variable 39 | In_2 frontend.Variable 40 | Out frontend.Variable 41 | } 42 | 43 | func (circuit *MyCircuit) Define(api abstractor.API) error { 44 | sum := api.Add(circuit.In_1, circuit.In_2) 45 | api.AssertIsEqual(sum, circuit.Out) 46 | return nil 47 | } 48 | 49 | func (circuit MyCircuit) Define(api frontend.API) error { 50 | return abstractor.Concretize(api, &circuit) 51 | } 52 | ``` 53 | 54 | Once you export `MyCircuit` to Lean, you obtain the following definition: 55 | 56 | ```lean 57 | import ProvenZk.Gates 58 | import ProvenZk.Ext.Vector 59 | 60 | set_option linter.unusedVariables false 61 | 62 | namespace MyCircuit 63 | 64 | def Order : ℕ := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 65 | variable [Fact (Nat.Prime Order)] 66 | abbrev F := ZMod Order 67 | abbrev Gates := GatesGnark9 Order 68 | 69 | def circuit (In_1: F) (In_2: F) (Out: F): Prop := 70 | ∃gate_0, gate_0 = Gates.add In_1 In_2 ∧ 71 | Gates.eq gate_0 Out ∧ 72 | True 73 | 74 | end MyCircuit 75 | ``` 76 | 77 | Further examples of this process with various levels of complexity can be seen 78 | in [`extractor_test.go`](./extractor/extractor_test.go). You can also peruse the 79 | [Gnark Extractor Demo](https://github.com/reilabs/gnark-lean-demo), which uses 80 | this library alongside an implementation of 81 | [Semaphore](https://semaphore.appliedzkp.org). 82 | 83 | ## How to Use the Library 84 | 85 | If you are familiar with the [gnark library](https://github.com/consensys/gnark) 86 | (as you will need to be to write ZK circuits), the circuit API in this library 87 | should be familiar. 88 | 89 | Based directly on the gnark interface, this library adds "gadgets" and hence 90 | makes it easy to integrate with existing circuits. To do so, you have to 91 | implement the `AbsDefine` method for the struct that represents your circuit 92 | (`MyCircuit` in the example below). You can use the `abstractor.Concretize` 93 | function to automatically derive an implementation of `Define` for further use 94 | with gnark. 95 | 96 | After doing that, you choose a circuit curve from those present in the 97 | aforementioned gnark library, and then call the extractor function 98 | `CircuitToLean`. 99 | 100 | ```go 101 | circuit := MyCircuit{} 102 | out, err := extractor.CircuitToLean(&circuit, ecc.BN254) 103 | if err != nil { 104 | log.Fatal(err) 105 | } 106 | fmt.Println(out) 107 | ``` 108 | 109 | `CircuitToLean` returns a string which contains the circuit output in a format 110 | that can be read by the Lean language. The Lean code depends on Reilabs' 111 | [ProvenZK](https://github.com/reilabs/proven-zk) library in order to represent 112 | gates and other components of the circuit. In doing so, it makes the extracted 113 | circuit formally verifiable. 114 | 115 | -------------------------------------------------------------------------------- /abstractor/abstractor.go: -------------------------------------------------------------------------------- 1 | package abstractor 2 | 3 | import "github.com/consensys/gnark/frontend" 4 | 5 | type Gadget interface { 6 | Call(gadget GadgetDefinition) interface{} 7 | } 8 | 9 | type GadgetDefinition interface { 10 | DefineGadget(api frontend.API) interface{} 11 | } 12 | 13 | type API interface { 14 | Call(gadget GadgetDefinition) interface{} 15 | } 16 | -------------------------------------------------------------------------------- /abstractor/interface.go: -------------------------------------------------------------------------------- 1 | // This file contains the public API for using the extractor. 2 | // The Call functions are used to call gadgets and get their returnd object. 3 | // These methods are prepared for doing automated casting from interface{}. 4 | // Alternatively it's possible to do manual casting by calling 5 | // abstractor.Call() and casting the result to the needed type. 6 | package abstractor 7 | 8 | import ( 9 | "github.com/consensys/gnark/frontend" 10 | ) 11 | 12 | // Call is used to call a Gadget which returns frontend.Variable (i.e. a single element `F` in Lean) 13 | func Call(api frontend.API, gadget GadgetDefinition) frontend.Variable { 14 | if abs, ok := api.(API); ok { 15 | return abs.Call(gadget).(frontend.Variable) 16 | } else { 17 | return gadget.DefineGadget(api).(frontend.Variable) 18 | } 19 | } 20 | 21 | // CallVoid is used to call a Gadget which doesn't return anything 22 | func CallVoid(api frontend.API, gadget GadgetDefinition) { 23 | Call(api, gadget) 24 | } 25 | 26 | // Call1 is used to call a Gadget which returns []frontend.Variable (i.e. `Vector F d` in Lean) 27 | func Call1(api frontend.API, gadget GadgetDefinition) []frontend.Variable { 28 | return Call(api, gadget).([]frontend.Variable) 29 | } 30 | 31 | // Call2 is used to call a Gadget which returns a [][]frontend.Variable 32 | // (i.e. `Vector (Vector F a) b` in Lean) 33 | func Call2(api frontend.API, gadget GadgetDefinition) [][]frontend.Variable { 34 | return Call(api, gadget).([][]frontend.Variable) 35 | } 36 | 37 | // Call3 is used to call a Gadget which returns a [][][]frontend.Variable 38 | // (i.e. `Vector (Vector (Vector F a) b) c` in Lean) 39 | func Call3(api frontend.API, gadget GadgetDefinition) [][][]frontend.Variable { 40 | return Call(api, gadget).([][][]frontend.Variable) 41 | } 42 | -------------------------------------------------------------------------------- /extractor/extractor.go: -------------------------------------------------------------------------------- 1 | package extractor 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "reflect" 7 | 8 | "github.com/consensys/gnark-crypto/ecc" 9 | "github.com/consensys/gnark/constraint" 10 | "github.com/consensys/gnark/constraint/solver" 11 | "github.com/consensys/gnark/frontend" 12 | "github.com/consensys/gnark/frontend/schema" 13 | 14 | "github.com/reilabs/gnark-lean-extractor/v3/abstractor" 15 | ) 16 | 17 | type Operand interface { 18 | isOperand() 19 | } 20 | 21 | type Const struct { 22 | Value *big.Int 23 | } 24 | 25 | func (_ Const) isOperand() {} 26 | 27 | // Integer struct is used to distinguish between a constant in 28 | // place of a frontend.Variable and an integer where an integer 29 | // is the only type allowed. Integer sruct is currently only 30 | // used for the length of the result in ToBinary function. 31 | type Integer struct { 32 | Value *big.Int 33 | } 34 | 35 | func (_ Integer) isOperand() {} 36 | 37 | type Gate struct { 38 | Index int 39 | } 40 | 41 | func (_ Gate) isOperand() {} 42 | 43 | // Input is used to save the position of the argument in the 44 | // list of arguments of the circuit function. 45 | type Input struct { 46 | Index int 47 | } 48 | 49 | func (_ Input) isOperand() {} 50 | 51 | // Index is the index to be accessed in the array 52 | // Operand[Index] 53 | // Size is a placeholder to keep track of the whole 54 | // array size. It is essential to know if the whole 55 | // vector or only a slice is used as function 56 | // argument. 57 | type Proj struct { 58 | Operand Operand 59 | Index int 60 | Size int 61 | } 62 | 63 | func (_ Proj) isOperand() {} 64 | 65 | type ProjArray struct { 66 | Projs []Operand 67 | } 68 | 69 | func (_ ProjArray) isOperand() {} 70 | 71 | type Op interface { 72 | isOp() 73 | } 74 | 75 | type OpKind int 76 | 77 | const ( 78 | OpAdd OpKind = iota 79 | OpMulAcc 80 | OpNegative 81 | OpSub 82 | OpMul 83 | OpDiv 84 | OpDivUnchecked 85 | OpInverse 86 | OpToBinary 87 | OpFromBinary 88 | OpXor 89 | OpOr 90 | OpAnd 91 | OpSelect 92 | OpLookup 93 | OpIsZero 94 | OpCmp 95 | OpAssertEq 96 | OpAssertNotEq 97 | OpAssertIsBool 98 | OpAssertLessEqual 99 | ) 100 | 101 | func (_ OpKind) isOp() {} 102 | 103 | type App struct { 104 | Op Op 105 | Args []Operand 106 | } 107 | 108 | type Code struct { 109 | Gates []App 110 | } 111 | 112 | type ExGadget struct { 113 | Name string 114 | Arity int 115 | Code []App 116 | OutputsFlat []Operand 117 | Outputs interface{} 118 | Extractor *CodeExtractor 119 | Fields []schema.Field 120 | Args []ExArg 121 | } 122 | 123 | func (g *ExGadget) isOp() {} 124 | 125 | func (g *ExGadget) Call(gadget abstractor.GadgetDefinition) interface{} { 126 | args := []frontend.Variable{} 127 | 128 | rv := reflect.Indirect(reflect.ValueOf(gadget)) 129 | rt := rv.Type() 130 | // Looping through the circuit fields only. 131 | for i := 0; i < rt.NumField(); i++ { 132 | fld := rt.Field(i) 133 | v := rv.FieldByName(fld.Name) 134 | switch v.Kind() { 135 | case reflect.Slice: 136 | arg := flattenSlice(v) 137 | if len(arg) != 0 { 138 | args = append(args, arg) 139 | } 140 | case reflect.Array: 141 | // I can't convert from array to slice using Reflect because 142 | // the field is unaddressable. Therefore I recreate a slice 143 | // with the same elements as the input array. 144 | arg := arrayToSlice(v) 145 | // Checking length != 0 because I need to keep nested slices 146 | // as nested slices, but not empty slices 147 | if len(arg) != 0 { 148 | args = append(args, arg) 149 | } 150 | case reflect.Interface: 151 | args = append(args, v.Elem().Interface().(frontend.Variable)) 152 | } 153 | } 154 | gate := g.Extractor.AddApp(g, args...) 155 | 156 | res := replaceArg(g.Outputs, gate) 157 | return res 158 | } 159 | 160 | func (ce *CodeExtractor) Call(gadget abstractor.GadgetDefinition) interface{} { 161 | // Deep copying `gadget` because `DefineGadget` needs to modify the gadget fields. 162 | // This was done as a replacement to the initial method of declaring gadgets using 163 | // a direct call to `Define Gadget` within the circuit and then calling GadgetDefinition.Call 164 | clonedGadget := cloneGadget(gadget) 165 | g := ce.DefineGadget(clonedGadget) 166 | return g.Call(gadget) 167 | } 168 | 169 | type ExArgType struct { 170 | Size int 171 | Type *ExArgType 172 | } 173 | 174 | type ExArg struct { 175 | Name string 176 | Kind reflect.Kind 177 | Type ExArgType 178 | } 179 | 180 | type ExCircuit struct { 181 | Inputs []ExArg 182 | Gadgets []ExGadget 183 | Code []App 184 | Field ecc.ID 185 | } 186 | 187 | type CodeExtractor struct { 188 | Code []App 189 | Gadgets []ExGadget 190 | FieldID ecc.ID 191 | } 192 | 193 | func (ce *CodeExtractor) AssertIsCrumb(i1 frontend.Variable) { 194 | // TODO implement me 195 | panic("implement me") 196 | } 197 | 198 | func (ce *CodeExtractor) AddBlueprint(b constraint.Blueprint) constraint.BlueprintID { 199 | // TODO implement me 200 | panic("implement me") 201 | } 202 | 203 | func (ce *CodeExtractor) AddInstruction(bID constraint.BlueprintID, calldata []uint32) []uint32 { 204 | // TODO implement me 205 | panic("implement me") 206 | } 207 | 208 | func (ce *CodeExtractor) NewHintForId( 209 | id solver.HintID, nbOutputs int, inputs ...frontend.Variable, 210 | ) ([]frontend.Variable, error) { 211 | // TODO implement me 212 | panic("implement me") 213 | } 214 | 215 | func (ce *CodeExtractor) Defer(cb func(api frontend.API) error) { 216 | // TODO implement me 217 | panic("implement me") 218 | } 219 | 220 | func (ce *CodeExtractor) InternalVariable(wireID uint32) frontend.Variable { 221 | // TODO implement me 222 | panic("implement me") 223 | } 224 | 225 | func (ce *CodeExtractor) ToCanonicalVariable(variable frontend.Variable) frontend.CanonicalVariable { 226 | // TODO implement me 227 | panic("implement me") 228 | } 229 | 230 | func (ce *CodeExtractor) SetGkrInfo(info constraint.GkrInfo) error { 231 | // TODO implement me 232 | panic("implement me") 233 | } 234 | 235 | func sanitizeVars(args ...frontend.Variable) []Operand { 236 | ops := []Operand{} 237 | for _, arg := range args { 238 | switch arg.(type) { 239 | case Input, Gate, Proj, Const: 240 | ops = append(ops, arg.(Operand)) 241 | case Integer: 242 | ops = append(ops, arg.(Operand)) 243 | case int: 244 | ops = append(ops, Const{new(big.Int).SetInt64(int64(arg.(int)))}) 245 | case int8: 246 | ops = append(ops, Const{new(big.Int).SetInt64(int64(arg.(int8)))}) 247 | case int16: 248 | ops = append(ops, Const{new(big.Int).SetInt64(int64(arg.(int16)))}) 249 | case int32: 250 | ops = append(ops, Const{new(big.Int).SetInt64(int64(arg.(int32)))}) 251 | case int64: 252 | ops = append(ops, Const{new(big.Int).SetInt64(arg.(int64))}) 253 | case uint: 254 | ops = append(ops, Const{new(big.Int).SetUint64(uint64(arg.(uint)))}) 255 | case uint8: 256 | ops = append(ops, Const{new(big.Int).SetUint64(uint64(arg.(uint8)))}) 257 | case uint16: 258 | ops = append(ops, Const{new(big.Int).SetUint64(uint64(arg.(uint16)))}) 259 | case uint32: 260 | ops = append(ops, Const{new(big.Int).SetUint64(uint64(arg.(uint32)))}) 261 | case uint64: 262 | ops = append(ops, Const{new(big.Int).SetUint64(arg.(uint64))}) 263 | case big.Int: 264 | casted := arg.(big.Int) 265 | ops = append(ops, Const{&casted}) 266 | case []frontend.Variable: 267 | opsArray := sanitizeVars(arg.([]frontend.Variable)...) 268 | ops = append(ops, ProjArray{opsArray}) 269 | case nil: 270 | // This takes care of uninitialised fields that are 271 | // passed to gadgets 272 | ops = append(ops, Const{big.NewInt(int64(0))}) 273 | default: 274 | fmt.Printf("sanitizeVars invalid argument of type %T\n%#v\n", arg, arg) 275 | panic("sanitizeVars invalid argument") 276 | } 277 | } 278 | return ops 279 | } 280 | 281 | func (ce *CodeExtractor) AddApp(op Op, args ...frontend.Variable) Operand { 282 | app := App{op, sanitizeVars(args...)} 283 | ce.Code = append(ce.Code, app) 284 | return Gate{len(ce.Code) - 1} 285 | } 286 | 287 | func (ce *CodeExtractor) Add(i1, i2 frontend.Variable, in ...frontend.Variable) frontend.Variable { 288 | return ce.AddApp(OpAdd, append([]frontend.Variable{i1, i2}, in...)...) 289 | } 290 | 291 | func (ce *CodeExtractor) MulAcc(a, b, c frontend.Variable) frontend.Variable { 292 | return ce.AddApp(OpMulAcc, a, b, c) 293 | } 294 | 295 | func (ce *CodeExtractor) Neg(i1 frontend.Variable) frontend.Variable { 296 | return ce.AddApp(OpNegative, i1) 297 | } 298 | 299 | func (ce *CodeExtractor) Sub(i1, i2 frontend.Variable, in ...frontend.Variable) frontend.Variable { 300 | return ce.AddApp(OpSub, append([]frontend.Variable{i1, i2}, in...)...) 301 | } 302 | 303 | func (ce *CodeExtractor) Mul(i1, i2 frontend.Variable, in ...frontend.Variable) frontend.Variable { 304 | return ce.AddApp(OpMul, append([]frontend.Variable{i1, i2}, in...)...) 305 | } 306 | 307 | func (ce *CodeExtractor) DivUnchecked(i1, i2 frontend.Variable) frontend.Variable { 308 | return ce.AddApp(OpDivUnchecked, i1, i2) 309 | } 310 | 311 | func (ce *CodeExtractor) Div(i1, i2 frontend.Variable) frontend.Variable { 312 | return ce.AddApp(OpDiv, i1, i2) 313 | } 314 | 315 | func (ce *CodeExtractor) Inverse(i1 frontend.Variable) frontend.Variable { 316 | return ce.AddApp(OpInverse, i1) 317 | } 318 | 319 | func (ce *CodeExtractor) ToBinary(i1 frontend.Variable, n ...int) []frontend.Variable { 320 | nbBits := ce.FieldID.ScalarField().BitLen() 321 | if len(n) == 1 { 322 | nbBits = n[0] 323 | if nbBits < 0 { 324 | panic("Number of bits in ToBinary must be > 0") 325 | } 326 | } 327 | 328 | gate := ce.AddApp(OpToBinary, i1, Integer{big.NewInt(int64(nbBits))}) 329 | outs := make([]frontend.Variable, nbBits) 330 | for i := range outs { 331 | outs[i] = Proj{gate, i, len(outs)} 332 | } 333 | return outs 334 | } 335 | 336 | func (ce *CodeExtractor) FromBinary(b ...frontend.Variable) frontend.Variable { 337 | // Packs in little-endian 338 | if len(b) == 0 { 339 | panic("FromBinary has to have at least one argument!") 340 | } 341 | if reflect.TypeOf(b[0]) == reflect.TypeOf([]frontend.Variable{}) { 342 | panic("Pass operators to FromBinary using ellipsis") 343 | } 344 | return ce.AddApp(OpFromBinary, append([]frontend.Variable{}, b...)...) 345 | } 346 | 347 | func (ce *CodeExtractor) Xor(a, b frontend.Variable) frontend.Variable { 348 | return ce.AddApp(OpXor, a, b) 349 | } 350 | 351 | func (ce *CodeExtractor) Or(a, b frontend.Variable) frontend.Variable { 352 | return ce.AddApp(OpOr, a, b) 353 | } 354 | 355 | func (ce *CodeExtractor) And(a, b frontend.Variable) frontend.Variable { 356 | return ce.AddApp(OpAnd, a, b) 357 | } 358 | 359 | func (ce *CodeExtractor) Select(b frontend.Variable, i1, i2 frontend.Variable) frontend.Variable { 360 | return ce.AddApp(OpSelect, b, i1, i2) 361 | } 362 | 363 | func (ce *CodeExtractor) Lookup2(b0, b1 frontend.Variable, i0, i1, i2, i3 frontend.Variable) frontend.Variable { 364 | return ce.AddApp(OpLookup, b0, b1, i0, i1, i2, i3) 365 | } 366 | 367 | func (ce *CodeExtractor) IsZero(i1 frontend.Variable) frontend.Variable { 368 | return ce.AddApp(OpIsZero, i1) 369 | } 370 | 371 | func (ce *CodeExtractor) Cmp(i1, i2 frontend.Variable) frontend.Variable { 372 | return ce.AddApp(OpCmp, i1, i2) 373 | } 374 | 375 | func (ce *CodeExtractor) AssertIsEqual(i1, i2 frontend.Variable) { 376 | ce.AddApp(OpAssertEq, i1, i2) 377 | } 378 | 379 | func (ce *CodeExtractor) AssertIsDifferent(i1, i2 frontend.Variable) { 380 | ce.AddApp(OpAssertNotEq, i1, i2) 381 | } 382 | 383 | func (ce *CodeExtractor) AssertIsBoolean(i1 frontend.Variable) { 384 | ce.AddApp(OpAssertIsBool, i1) 385 | } 386 | 387 | func (ce *CodeExtractor) AssertIsLessOrEqual(v frontend.Variable, bound frontend.Variable) { 388 | ce.AddApp(OpAssertLessEqual, v, bound) 389 | } 390 | 391 | func (ce *CodeExtractor) Println(a ...frontend.Variable) { 392 | panic("implement me") 393 | } 394 | 395 | func (ce *CodeExtractor) Compiler() frontend.Compiler { 396 | return ce 397 | } 398 | 399 | func (ce *CodeExtractor) MarkBoolean(v frontend.Variable) { 400 | panic("implement me") 401 | } 402 | 403 | func (ce *CodeExtractor) IsBoolean(v frontend.Variable) bool { 404 | panic("implement me") 405 | } 406 | 407 | func (ce *CodeExtractor) Field() *big.Int { 408 | scalarField := ce.FieldID.ScalarField() 409 | return new(big.Int).Set(scalarField) 410 | } 411 | 412 | func (ce *CodeExtractor) FieldBitLen() int { 413 | return ce.FieldID.ScalarField().BitLen() 414 | } 415 | 416 | func (ce *CodeExtractor) Commit(...frontend.Variable) (frontend.Variable, error) { 417 | panic("implement me") 418 | } 419 | 420 | func (ce *CodeExtractor) NewHint(f solver.Hint, nbOutputs int, inputs ...frontend.Variable) ( 421 | []frontend.Variable, error, 422 | ) { 423 | panic("implement me") 424 | } 425 | 426 | func (ce *CodeExtractor) ConstantValue(v frontend.Variable) (*big.Int, bool) { 427 | switch v.(type) { 428 | case Const: 429 | return v.(Const).Value, true 430 | case Proj: 431 | { 432 | switch v.(Proj).Operand.(type) { 433 | case Const: 434 | return v.(Proj).Operand.(Const).Value, true 435 | default: 436 | return nil, false 437 | } 438 | } 439 | case int: 440 | return new(big.Int).SetInt64(int64(v.(int))), true 441 | case int8: 442 | return new(big.Int).SetInt64(int64(v.(int8))), true 443 | case int16: 444 | return new(big.Int).SetInt64(int64(v.(int16))), true 445 | case int32: 446 | return new(big.Int).SetInt64(int64(v.(int32))), true 447 | case int64: 448 | return new(big.Int).SetInt64(v.(int64)), true 449 | case uint: 450 | return new(big.Int).SetUint64(uint64(v.(uint))), true 451 | case uint8: 452 | return new(big.Int).SetUint64(uint64(v.(uint8))), true 453 | case uint16: 454 | return new(big.Int).SetUint64(uint64(v.(uint16))), true 455 | case uint32: 456 | return new(big.Int).SetUint64(uint64(v.(uint32))), true 457 | case uint64: 458 | return new(big.Int).SetUint64(v.(uint64)), true 459 | case big.Int: 460 | casted := v.(big.Int) 461 | return &casted, true 462 | default: 463 | return nil, false 464 | } 465 | } 466 | 467 | func (ce *CodeExtractor) DefineGadget(gadget abstractor.GadgetDefinition) abstractor.Gadget { 468 | if reflect.ValueOf(gadget).Kind() != reflect.Ptr { 469 | panic("DefineGadget only takes pointers to the gadget") 470 | } 471 | schema, _ := getSchema(gadget) 472 | circuitInit(gadget, schema) 473 | // Can't use `schema.NbPublic + schema.NbSecret` 474 | // for arity because each array element is considered 475 | // a parameter 476 | arity := len(schema.Fields) 477 | args := getExArgs(gadget, schema.Fields) 478 | 479 | name := generateUniqueName(gadget, args) 480 | 481 | ptr_gadget := getGadgetByName(ce.Gadgets, name) 482 | if ptr_gadget != nil { 483 | return ptr_gadget 484 | } 485 | 486 | oldCode := ce.Code 487 | ce.Code = make([]App, 0) 488 | outputs := gadget.DefineGadget(ce) 489 | 490 | // Handle gadgets returning nil. 491 | // Without the if-statement, the nil would be replaced with (0:F) 492 | // due to the case in sanitizeVars 493 | if outputs == nil { 494 | outputs = []frontend.Variable{} 495 | } 496 | 497 | // flattenSlice needs to be called only if there are nested 498 | // slices in order to generate a slice of Operand. 499 | // TODO: remove `OutputsFlat` field and use only `Outputs` 500 | flatOutput := []frontend.Variable{outputs} 501 | vOutputs := reflect.ValueOf(outputs) 502 | if vOutputs.Kind() == reflect.Slice { 503 | flatOutput = flattenSlice(vOutputs) 504 | } 505 | 506 | newCode := ce.Code 507 | ce.Code = oldCode 508 | exGadget := ExGadget{ 509 | Name: name, 510 | Arity: arity, 511 | Code: newCode, 512 | OutputsFlat: sanitizeVars(flatOutput...), 513 | Outputs: outputs, 514 | Extractor: ce, 515 | Fields: schema.Fields, 516 | Args: args, 517 | } 518 | ce.Gadgets = append(ce.Gadgets, exGadget) 519 | return &exGadget 520 | } 521 | 522 | var _ abstractor.API = &CodeExtractor{} 523 | -------------------------------------------------------------------------------- /extractor/interface.go: -------------------------------------------------------------------------------- 1 | // This file contains the public API for running the extractor. 2 | package extractor 3 | 4 | import ( 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/consensys/gnark-crypto/ecc" 9 | "github.com/consensys/gnark/frontend" 10 | "github.com/reilabs/gnark-lean-extractor/v3/abstractor" 11 | "golang.org/x/exp/slices" 12 | ) 13 | 14 | // CircuitToLeanWithName exports a `circuit` to Lean over a `field` with `namespace` 15 | // CircuitToLeanWithName and CircuitToLean aren't joined in a single function 16 | // CircuitToLean(circuit abstractor.Circuit, field ecc.ID, namespace ...string) because the long term view 17 | // is to add an optional parameter to support custom `set_option` directives in the header. 18 | func CircuitToLeanWithName(circuit frontend.Circuit, field ecc.ID, namespace string) (out string, err error) { 19 | defer recoverError() 20 | 21 | schema, err := getSchema(circuit) 22 | if err != nil { 23 | return "", err 24 | } 25 | 26 | circuitInit(circuit, schema) 27 | 28 | api := CodeExtractor{ 29 | Code: []App{}, 30 | Gadgets: []ExGadget{}, 31 | FieldID: field, 32 | } 33 | 34 | err = circuit.Define(&api) 35 | if err != nil { 36 | return "", err 37 | } 38 | 39 | extractorCircuit := ExCircuit{ 40 | Inputs: getExArgs(circuit, schema.Fields), 41 | Gadgets: api.Gadgets, 42 | Code: api.Code, 43 | Field: api.FieldID, 44 | } 45 | out = exportCircuit(extractorCircuit, namespace) 46 | return out, nil 47 | } 48 | 49 | // CircuitToLean exports a `circuit` to Lean over a `field` with the namespace being the 50 | // struct name of `circuit` 51 | // When the namespace argument is not defined, it uses the name of the struct circuit 52 | func CircuitToLean(circuit frontend.Circuit, field ecc.ID) (string, error) { 53 | name := getStructName(circuit) 54 | return CircuitToLeanWithName(circuit, field, name) 55 | } 56 | 57 | // GadgetToLeanWithName exports a `gadget` to Lean over a `field` with `namespace` 58 | // Same notes written for CircuitToLeanWithName apply to GadgetToLeanWithName and GadgetToLean 59 | func GadgetToLeanWithName(gadget abstractor.GadgetDefinition, field ecc.ID, namespace string) (out string, err error) { 60 | defer recoverError() 61 | 62 | api := CodeExtractor{ 63 | Code: []App{}, 64 | Gadgets: []ExGadget{}, 65 | FieldID: field, 66 | } 67 | 68 | api.DefineGadget(gadget) 69 | gadgets := exportGadgets(api.Gadgets) 70 | prelude := exportPrelude(namespace, api.FieldID.ScalarField()) 71 | footer := exportFooter(namespace) 72 | return fmt.Sprintf("%s\n\n%s\n\n%s", prelude, gadgets, footer), nil 73 | } 74 | 75 | // GadgetToLean exports a `gadget` to Lean over a `field` 76 | func GadgetToLean(gadget abstractor.GadgetDefinition, field ecc.ID) (string, error) { 77 | name := getStructName(gadget) 78 | return GadgetToLeanWithName(gadget, field, name) 79 | } 80 | 81 | // ExtractCircuits is used to export a series of `circuits` to Lean over a `field` under `namespace`. 82 | func ExtractCircuits(namespace string, field ecc.ID, circuits ...frontend.Circuit) (out string, err error) { 83 | defer recoverError() 84 | 85 | api := CodeExtractor{ 86 | Code: []App{}, 87 | Gadgets: []ExGadget{}, 88 | FieldID: field, 89 | } 90 | 91 | var circuits_extracted []string 92 | var past_circuits []string 93 | 94 | extractorCircuit := ExCircuit{ 95 | Inputs: []ExArg{}, 96 | Gadgets: []ExGadget{}, 97 | Code: []App{}, 98 | Field: api.FieldID, 99 | } 100 | 101 | for _, circuit := range circuits { 102 | schema, err := getSchema(circuit) 103 | if err != nil { 104 | return "", err 105 | } 106 | args := getExArgs(circuit, schema.Fields) 107 | name := generateUniqueName(circuit, args) 108 | if slices.Contains(past_circuits, name) { 109 | continue 110 | } 111 | past_circuits = append(past_circuits, name) 112 | 113 | circuitInit(circuit, schema) 114 | err = circuit.Define(&api) 115 | if err != nil { 116 | return "", err 117 | } 118 | 119 | extractorCircuit.Inputs = args 120 | extractorCircuit.Code = api.Code 121 | 122 | circ := fmt.Sprintf("def %s %s: Prop :=\n%s", name, genArgs(extractorCircuit.Inputs), genCircuitBody(extractorCircuit)) 123 | circuits_extracted = append(circuits_extracted, circ) 124 | 125 | // Resetting elements for next circuit 126 | extractorCircuit.Inputs = []ExArg{} 127 | extractorCircuit.Code = []App{} 128 | api.Code = []App{} 129 | } 130 | 131 | prelude := exportPrelude(namespace, extractorCircuit.Field.ScalarField()) 132 | gadgets := exportGadgets(api.Gadgets) 133 | footer := exportFooter(namespace) 134 | return fmt.Sprintf("%s\n\n%s\n\n%s\n\n%s", prelude, gadgets, strings.Join(circuits_extracted, "\n\n"), footer), nil 135 | } 136 | 137 | // ExtractGadgets is used to export a series of `gadgets` to Lean over a `field` under `namespace`. 138 | func ExtractGadgets(namespace string, field ecc.ID, gadgets ...abstractor.GadgetDefinition) (out string, err error) { 139 | defer recoverError() 140 | 141 | api := CodeExtractor{ 142 | Code: []App{}, 143 | Gadgets: []ExGadget{}, 144 | FieldID: field, 145 | } 146 | 147 | for _, gadget := range gadgets { 148 | api.DefineGadget(gadget) 149 | } 150 | 151 | gadgets_string := exportGadgets(api.Gadgets) 152 | prelude := exportPrelude(namespace, api.FieldID.ScalarField()) 153 | footer := exportFooter(namespace) 154 | return fmt.Sprintf("%s\n\n%s\n\n%s", prelude, gadgets_string, footer), nil 155 | } 156 | -------------------------------------------------------------------------------- /extractor/lean_export.go: -------------------------------------------------------------------------------- 1 | package extractor 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "reflect" 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/consensys/gnark/frontend" 11 | "github.com/consensys/gnark/frontend/schema" 12 | ) 13 | 14 | // isWhitespacePresent checks there are no whitespaces in the middle 15 | // of a string. It's used to avoid a user requesting a `namespace` that 16 | // contains whitespaces (which wouldn't be compliant with Lean4 syntax) 17 | func isWhitespacePresent(input string) bool { 18 | return regexp.MustCompile(`\s`).MatchString(input) 19 | } 20 | 21 | // exportPrelude generates the string to put at the beginning of the 22 | // autogenerated Lean4 code. It includes the relevant `provenZK` library 23 | // import. 24 | func exportPrelude(name string, order *big.Int) string { 25 | trimmedName := strings.TrimSpace(name) 26 | if isWhitespacePresent(trimmedName) { 27 | panic("Whitespace isn't allowed in namespace tag") 28 | } 29 | s := fmt.Sprintf(`import ProvenZk.Gates 30 | import ProvenZk.Ext.Vector 31 | 32 | set_option linter.unusedVariables false 33 | 34 | namespace %s 35 | 36 | def Order : ℕ := 0x%s 37 | variable [Fact (Nat.Prime Order)] 38 | abbrev F := ZMod Order 39 | abbrev Gates := %s Order`, trimmedName, order.Text(16), "GatesGnark9") 40 | 41 | return s 42 | } 43 | 44 | // exportFooter generates the string to put at the end of the 45 | // autogenerated Lean4 code. At the moment it only closes 46 | // the namespace. 47 | func exportFooter(name string) string { 48 | trimmedName := strings.TrimSpace(name) 49 | if isWhitespacePresent(trimmedName) { 50 | panic("Whitespace isn't allowed in namespace tag") 51 | } 52 | s := fmt.Sprintf(`end %s`, trimmedName) 53 | return s 54 | } 55 | 56 | // genKTypeSignature generates the type signature of the `k` 57 | // argument of exported gadgets. 58 | func genKTypeSignature(output reflect.Value) string { 59 | if output.Kind() != reflect.Slice { 60 | return "" 61 | } 62 | if output.Index(0).Kind() == reflect.Slice { 63 | innerType := genKTypeSignature(output.Index(0)) 64 | return fmt.Sprintf("Vector (%s) %d", innerType, output.Len()) 65 | } 66 | return fmt.Sprintf("Vector F %d", output.Len()) 67 | } 68 | 69 | // exportGadget generates the `gadget` function in Lean 70 | func exportGadget(gadget ExGadget) string { 71 | kArgs := "" 72 | if len(gadget.OutputsFlat) == 1 { 73 | kArgs = "(k: F -> Prop)" 74 | } else if len(gadget.OutputsFlat) > 1 { 75 | outputType := genKTypeSignature(reflect.ValueOf(gadget.Outputs)) 76 | kArgs = fmt.Sprintf("(k: %s -> Prop)", outputType) 77 | } 78 | inAssignment := gadget.Args 79 | 80 | return fmt.Sprintf("def %s %s %s: Prop :=\n%s", gadget.Name, genArgs(inAssignment), kArgs, genGadgetBody(inAssignment, gadget)) 81 | } 82 | 83 | func exportGadgets(exGadgets []ExGadget) string { 84 | gadgets := make([]string, len(exGadgets)) 85 | for i, gadget := range exGadgets { 86 | gadgets[i] = exportGadget(gadget) 87 | } 88 | return strings.Join(gadgets, "\n\n") 89 | } 90 | 91 | // exportCircuit generates the `circuit` function in Lean 92 | func exportCircuit(circuit ExCircuit, name string) string { 93 | gadgets := exportGadgets(circuit.Gadgets) 94 | circ := fmt.Sprintf("def circuit %s: Prop :=\n%s", genArgs(circuit.Inputs), genCircuitBody(circuit)) 95 | prelude := exportPrelude(name, circuit.Field.ScalarField()) 96 | footer := exportFooter(name) 97 | return fmt.Sprintf("%s\n\n%s\n\n%s\n\n%s", prelude, gadgets, circ, footer) 98 | } 99 | 100 | // circuitInit takes struct and a schema to populate all the 101 | // circuit/gagdget fields with Operand. 102 | func circuitInit(class any, schema *schema.Schema) { 103 | // https://stackoverflow.com/a/49704408 104 | // https://stackoverflow.com/a/14162161 105 | // https://stackoverflow.com/a/63422049 106 | 107 | // The purpose of this function is to initialise the 108 | // struct fields with Operand interfaces for being 109 | // processed by the Extractor. 110 | v := reflect.ValueOf(class) 111 | if v.Type().Kind() == reflect.Ptr { 112 | ptr := v 113 | v = ptr.Elem() 114 | } else { 115 | ptr := reflect.New(reflect.TypeOf(class)) 116 | temp := ptr.Elem() 117 | temp.Set(v) 118 | } 119 | 120 | tmp_c := reflect.ValueOf(&class).Elem().Elem() 121 | tmp := reflect.New(tmp_c.Type()).Elem() 122 | tmp.Set(tmp_c) 123 | for j, f := range schema.Fields { 124 | field_name := f.Name 125 | field := v.FieldByName(field_name) 126 | field_type := field.Type() 127 | 128 | // Can't assign an array to another array, therefore 129 | // initialise each element in the array 130 | 131 | if field_type.Kind() == reflect.Array { 132 | arrayInit(f, tmp.Elem().FieldByName(field_name), Input{j}) 133 | } else if field_type.Kind() == reflect.Slice { 134 | // Recreate a zeroed array to remove overlapping pointers if input 135 | // arguments are duplicated (i.e. `api.Call(SliceGadget{circuit.Path, circuit.Path})`) 136 | arrayZero(tmp.Elem().FieldByName(field_name)) 137 | arrayInit(f, tmp.Elem().FieldByName(field_name), Input{j}) 138 | } else if field_type.Kind() == reflect.Interface { 139 | init := Input{j} 140 | value := reflect.ValueOf(init) 141 | 142 | tmp.Elem().FieldByName(field_name).Set(value) 143 | } else { 144 | fmt.Printf("Skipped type %s\n", field_type.Kind()) 145 | } 146 | } 147 | } 148 | 149 | func circuitArgs(field schema.Field) ExArgType { 150 | // Handling only subfields which are nested arrays 151 | switch len(field.SubFields) { 152 | case 1: 153 | subType := circuitArgs(field.SubFields[0]) 154 | return ExArgType{field.ArraySize, &subType} 155 | case 0: 156 | return ExArgType{field.ArraySize, nil} 157 | default: 158 | panic("Only nested arrays supported in SubFields") 159 | } 160 | } 161 | 162 | // getExArgs generates a list of ExArg given a `circuit` and a 163 | // list of `Field`. It is used in the Circuit to Lean functions 164 | func getExArgs(circuit any, fields []schema.Field) []ExArg { 165 | args := []ExArg{} 166 | for _, f := range fields { 167 | kind := kindOfField(circuit, f.Name) 168 | arg := ExArg{f.Name, kind, circuitArgs(f)} 169 | args = append(args, arg) 170 | } 171 | return args 172 | } 173 | 174 | // getSchema is a cloned version of NewSchema without constraints 175 | func getSchema(circuit any) (*schema.Schema, error) { 176 | tVariable := reflect.ValueOf(struct{ A frontend.Variable }{}).FieldByName("A").Type() 177 | return schema.New(circuit, tVariable) 178 | } 179 | 180 | func genNestedArrays(a ExArgType) string { 181 | if a.Type != nil { 182 | return fmt.Sprintf("Vector (%s) %d", genNestedArrays(*a.Type), a.Size) 183 | } 184 | return fmt.Sprintf("Vector F %d", a.Size) 185 | } 186 | 187 | func genArgs(inAssignment []ExArg) string { 188 | args := make([]string, len(inAssignment)) 189 | for i, in := range inAssignment { 190 | switch in.Kind { 191 | case reflect.Array, reflect.Slice: 192 | args[i] = fmt.Sprintf("(%s: %s)", in.Name, genNestedArrays(in.Type)) 193 | default: 194 | args[i] = fmt.Sprintf("(%s: F)", in.Name) 195 | } 196 | } 197 | return strings.Join(args, " ") 198 | } 199 | 200 | func extractGateVars(arg Operand) []Operand { 201 | switch arg.(type) { 202 | case Proj: 203 | return extractGateVars(arg.(Proj).Operand) 204 | case ProjArray: 205 | res := []Operand{} 206 | for i := range arg.(ProjArray).Projs { 207 | res = append(res, extractGateVars(arg.(ProjArray).Projs[i])...) 208 | } 209 | return res 210 | default: 211 | return []Operand{arg} 212 | } 213 | } 214 | 215 | func assignGateVars(code []App, additional ...Operand) []string { 216 | gateVars := make([]string, len(code)) 217 | for _, app := range code { 218 | for _, arg := range app.Args { 219 | bases := extractGateVars(arg) 220 | for _, base := range bases { 221 | switch base.(type) { 222 | case Gate: 223 | ix := base.(Gate).Index 224 | if gateVars[ix] == "" { 225 | gateVars[ix] = fmt.Sprintf("gate_%d", ix) 226 | } 227 | } 228 | } 229 | } 230 | } 231 | for _, out := range additional { 232 | outBases := extractGateVars(out) 233 | for _, outBase := range outBases { 234 | switch outBase.(type) { 235 | case Gate: 236 | ix := outBase.(Gate).Index 237 | if gateVars[ix] == "" { 238 | gateVars[ix] = fmt.Sprintf("gate_%d", ix) 239 | } 240 | } 241 | } 242 | } 243 | 244 | return gateVars 245 | } 246 | 247 | func genGadgetCall(gateVar string, inAssignment []ExArg, gateVars []string, gadget *ExGadget, args []Operand) string { 248 | name := gadget.Name 249 | operands := operandExprs(args, inAssignment, gateVars) 250 | binder := "∧" 251 | if len(gadget.OutputsFlat) > 0 { 252 | binder = "fun _ =>" 253 | if gateVar != "" { 254 | binder = fmt.Sprintf("fun %s =>", gateVar) 255 | } 256 | } 257 | return fmt.Sprintf(" %s %s %s\n", name, strings.Join(operands, " "), binder) 258 | } 259 | 260 | func genGateOp(op Op) string { 261 | name := "unknown" 262 | switch op { 263 | case OpAdd: 264 | name = "add" 265 | case OpMulAcc: 266 | name = "mul_acc" 267 | case OpNegative: 268 | name = "neg" 269 | case OpSub: 270 | name = "sub" 271 | case OpMul: 272 | name = "mul" 273 | case OpDivUnchecked: 274 | name = "div_unchecked" 275 | case OpDiv: 276 | name = "div" 277 | case OpInverse: 278 | name = "inv" 279 | case OpXor: 280 | name = "xor" 281 | case OpOr: 282 | name = "or" 283 | case OpAnd: 284 | name = "and" 285 | case OpSelect: 286 | name = "select" 287 | case OpLookup: 288 | name = "lookup" 289 | case OpIsZero: 290 | name = "is_zero" 291 | case OpCmp: 292 | name = "cmp" 293 | case OpAssertEq: 294 | name = "eq" 295 | case OpAssertNotEq: 296 | name = "ne" 297 | case OpAssertIsBool: 298 | name = "is_bool" 299 | case OpAssertLessEqual: 300 | name = "le" 301 | case OpFromBinary: 302 | name = "from_binary" 303 | case OpToBinary: 304 | name = "to_binary" 305 | } 306 | 307 | return fmt.Sprintf("Gates.%s", name) 308 | } 309 | 310 | func getGateName(gateVar string, explicit bool) string { 311 | varName := "_ignored_" 312 | if gateVar != "" { 313 | varName = gateVar 314 | } 315 | if explicit { 316 | return fmt.Sprintf("(%s : F)", varName) 317 | } 318 | return varName 319 | } 320 | 321 | func genGateBinder(gateVar string) string { 322 | gateName := getGateName(gateVar, false) 323 | return fmt.Sprintf("∃%s, %s = ", gateName, gateName) 324 | } 325 | 326 | func genFunctionalGate(gateVar string, op Op, operands []string) string { 327 | return fmt.Sprintf(" %s%s %s ∧\n", genGateBinder(gateVar), genGateOp(op), strings.Join(operands, " ")) 328 | } 329 | 330 | func genCallbackGate(gateVar string, op Op, operands []string, args []Operand) string { 331 | gateName := getGateName(gateVar, false) 332 | return fmt.Sprintf(" ∃%s, %s %s %s ∧\n", gateName, genGateOp(op), strings.Join(operands, " "), gateName) 333 | } 334 | 335 | func genGenericGate(op Op, operands []string) string { 336 | return fmt.Sprintf(" %s %s ∧\n", genGateOp(op), strings.Join(operands, " ")) 337 | } 338 | 339 | func genOpCall(gateVar string, inAssignment []ExArg, gateVars []string, op Op, args []Operand) string { 340 | // functional is set to true when the op returns a value 341 | functional := false 342 | callback := false 343 | switch op { 344 | case OpDivUnchecked, OpDiv, OpInverse, OpXor, OpOr, OpAnd, OpSelect, OpLookup, OpCmp, OpIsZero, OpToBinary, OpFromBinary: 345 | callback = true 346 | case OpAdd, OpMulAcc, OpNegative, OpSub, OpMul: 347 | functional = true 348 | } 349 | 350 | operands := operandExprs(args, inAssignment, gateVars) 351 | if op == OpFromBinary { 352 | // OpFromBinary takes only one argument which is represented as list of Proj. For this reason we can 353 | // safely wrap it in a ProjArray and call operandExpr directly. 354 | projArray := ProjArray{args} 355 | operands = []string{operandExpr(projArray, inAssignment, gateVars)} 356 | } 357 | 358 | if functional { 359 | // if an operation supports infinite length of arguments, 360 | // turn it into a chain of operations 361 | switch op { 362 | case OpAdd, OpSub, OpMul: 363 | { 364 | finalStr := genFunctionalGate(gateVar, op, operands[0:2]) 365 | for len(operands) > 2 { 366 | operands = operands[1:] 367 | operands[0] = getGateName(gateVar, false) 368 | finalStr += genFunctionalGate(gateVar, op, operands[0:2]) 369 | } 370 | return finalStr 371 | } 372 | default: 373 | return genFunctionalGate(gateVar, op, operands) 374 | } 375 | } else if callback { 376 | return genCallbackGate(gateVar, op, operands, args) 377 | } else { 378 | return genGenericGate(op, operands) 379 | } 380 | } 381 | 382 | func genLine(app App, gateVar string, inAssignment []ExArg, gateVars []string) string { 383 | switch app.Op.(type) { 384 | case *ExGadget: 385 | return genGadgetCall(gateVar, inAssignment, gateVars, app.Op.(*ExGadget), app.Args) 386 | case Op: 387 | return genOpCall(gateVar, inAssignment, gateVars, app.Op.(Op), app.Args) 388 | } 389 | return "" 390 | } 391 | 392 | func genGadgetBody(inAssignment []ExArg, gadget ExGadget) string { 393 | gateVars := assignGateVars(gadget.Code, gadget.OutputsFlat...) 394 | lines := make([]string, len(gadget.Code)) 395 | for i, app := range gadget.Code { 396 | lines[i] = genLine(app, gateVars[i], inAssignment, gateVars) 397 | } 398 | 399 | switch len(gadget.OutputsFlat) { 400 | case 0: 401 | lastLine := " True" 402 | return strings.Join(append(lines, lastLine), "") 403 | case 1: 404 | // The case statement ensures there is index 0 (and only 0) 405 | result := operandExpr(gadget.OutputsFlat[0], inAssignment, gateVars) 406 | lastLine := fmt.Sprintf(" k %s", result) 407 | return strings.Join(append(lines, lastLine), "") 408 | default: 409 | // Same trick used for OpFromBinary in genOpCall 410 | result := operandExpr(ProjArray{gadget.OutputsFlat}, inAssignment, gateVars) 411 | lastLine := fmt.Sprintf(" k %s", result) 412 | return strings.Join(append(lines, lastLine), "") 413 | } 414 | } 415 | 416 | func genCircuitBody(circuit ExCircuit) string { 417 | gateVars := assignGateVars(circuit.Code) 418 | lines := make([]string, len(circuit.Code)) 419 | for i, app := range circuit.Code { 420 | lines[i] = genLine(app, gateVars[i], circuit.Inputs, gateVars) 421 | } 422 | lastLine := " True" 423 | return strings.Join(append(lines, lastLine), "") 424 | } 425 | 426 | func getArgIndex(operand ProjArray) int { 427 | if reflect.TypeOf(operand.Projs[0]) == reflect.TypeOf(Proj{}) { 428 | switch op := operand.Projs[0].(Proj).Operand.(type) { 429 | case Input: 430 | return op.Index 431 | case Gate: 432 | return op.Index 433 | case Proj: 434 | return getArgIndex(ProjArray{[]Operand{op}}) 435 | default: 436 | return -1 437 | } 438 | } else if (reflect.TypeOf(operand.Projs[0]) == reflect.TypeOf(ProjArray{})) { 439 | return getArgIndex(operand.Projs[0].(ProjArray)) 440 | } else { 441 | return -1 442 | } 443 | } 444 | 445 | func checkVector(operand ProjArray, argIdx int) (bool, Operand) { 446 | // Check correct length 447 | if operand.Projs[0].(Proj).Size != len(operand.Projs) { 448 | return false, operand 449 | } 450 | 451 | // Check index starts at 0 452 | lastIndex := operand.Projs[0].(Proj).Index 453 | if lastIndex != 0 { 454 | return false, operand 455 | } 456 | // Check always same Operand 457 | firstOperand := operand.Projs[0].(Proj).Operand 458 | 459 | // Check indices are in ascending order 460 | // on the same argIdx 461 | for _, op := range operand.Projs[1:] { 462 | if lastIndex != op.(Proj).Index-1 { 463 | return false, operand 464 | } 465 | lastIndex += 1 466 | if firstOperand != op.(Proj).Operand { 467 | return false, operand 468 | } 469 | } 470 | return true, operand.Projs[0].(Proj).Operand 471 | } 472 | 473 | // getStack returns the dimension of each of the nested ProjArray.Projs in `operand`. 474 | // Outermost dimension is at index 0 475 | func getStack(operand ProjArray) []int { 476 | if reflect.TypeOf(operand.Projs[0]) == reflect.TypeOf(ProjArray{}) { 477 | return getStack(operand.Projs[0].(ProjArray)) 478 | } else if reflect.TypeOf(operand.Projs[0]) == reflect.TypeOf(Proj{}) { 479 | proj := operand.Projs[0].(Proj) 480 | if reflect.TypeOf(proj.Operand) == reflect.TypeOf(Proj{}) { 481 | return append(getStack(ProjArray{[]Operand{proj.Operand}}), proj.Size) 482 | } else { 483 | return []int{proj.Size} 484 | } 485 | } else { 486 | return []int{} 487 | } 488 | } 489 | 490 | // expectedOperand checks that `op` has the Operand of `argIndex` 491 | // and the last element of `indices` matches `op.Index` 492 | func expectedOperand(op Proj, argIndex Operand, indices []int) bool { 493 | if op.Index != indices[len(indices)-1] { 494 | return false 495 | } 496 | if reflect.TypeOf(op.Operand) == reflect.TypeOf(Proj{}) { 497 | return expectedOperand(op.Operand.(Proj), argIndex, indices[0:len(indices)-1]) 498 | } 499 | return op.Operand == argIndex 500 | } 501 | 502 | // checkDimensions checks that the list of Proj is from the same Operand, with increasing Index, 503 | // with the first Index being 0 and with the number of elemetns in ProjArray.Projs matching length[0] 504 | func checkDimensions(operand ProjArray, length []int, argIndex Operand, pastIndices ...int) bool { 505 | if len(operand.Projs) != length[0] { 506 | return false 507 | } 508 | for i, p := range operand.Projs { 509 | if len(length[1:]) >= 1 { 510 | past := append(pastIndices, i) 511 | if !checkDimensions(p.(ProjArray), length[1:], argIndex, past...) { 512 | return false 513 | } 514 | } else { 515 | if !expectedOperand(p.(Proj), argIndex, append(pastIndices, i)) { 516 | return false 517 | } 518 | } 519 | } 520 | return true 521 | } 522 | 523 | func getFirstOperand(operand ProjArray) Operand { 524 | if reflect.TypeOf(operand.Projs[0]) == reflect.TypeOf(ProjArray{}) { 525 | return getFirstOperand(operand.Projs[0].(ProjArray)) 526 | } else if reflect.TypeOf(operand.Projs[0]) == reflect.TypeOf(Proj{}) { 527 | return operand.Projs[0].(Proj) 528 | } else { 529 | fmt.Printf("getFirstOperand %+v\n", operand) 530 | panic("Error in getFirstOperand.") 531 | } 532 | } 533 | 534 | func getIndex(operand Operand) Operand { 535 | if reflect.TypeOf(operand) != reflect.TypeOf(Proj{}) { 536 | return operand 537 | } 538 | return getIndex(operand.(Proj).Operand) 539 | } 540 | 541 | // isVectorComplete determines if `operand` can be optimised 542 | // without recreating the Vector element by element. The 543 | // Operand returned is the simplified operand for parsing 544 | // by `operandExpr` 545 | func isVectorComplete(operand ProjArray) (bool, Operand) { 546 | if len(operand.Projs) == 0 { 547 | return false, operand 548 | } 549 | 550 | // To check that ProjArray{} is complete, we first collect the Size 551 | // parameter from index 0 of the Projs list. Then we retrieve the first 552 | // Operand in ProjArray and extract the Input or Gate to use it in 553 | // `checkDimensions` to verify that it's the same across all the 554 | // elements. `checkDimensions` iterates through all the elements 555 | // in `operand` to verify that `Operand` in `Proj` are all from the 556 | // same Input/Gate, with Index starting from 0 and in ascending order 557 | // and the length of Operand matches Proj.Size. 558 | if reflect.TypeOf(operand.Projs[0]) == reflect.TypeOf(ProjArray{}) { 559 | sliceDimensions := getStack(operand) 560 | if len(sliceDimensions) == 0 { 561 | return false, operand 562 | } 563 | firstOperand := getFirstOperand(operand) 564 | argIdx := getIndex(firstOperand) 565 | if !checkDimensions(operand, sliceDimensions, argIdx) { 566 | return false, operand 567 | } 568 | return true, argIdx 569 | } 570 | 571 | // checkVector is used for Proj and it does the same checks 572 | // as for ProjArray 573 | argIdx := getArgIndex(operand) 574 | if argIdx == -1 { 575 | return false, operand 576 | } 577 | 578 | return checkVector(operand, argIdx) 579 | } 580 | 581 | func operandExpr(operand Operand, inAssignment []ExArg, gateVars []string) string { 582 | switch operand.(type) { 583 | case Input: 584 | return inAssignment[operand.(Input).Index].Name 585 | case Gate: 586 | return gateVars[operand.(Gate).Index] 587 | case Proj: 588 | return fmt.Sprintf("%s[%d]", operandExpr(operand.(Proj).Operand, inAssignment, gateVars), operand.(Proj).Index) 589 | case ProjArray: 590 | isComplete, newOperand := isVectorComplete(operand.(ProjArray)) 591 | if isComplete { 592 | return operandExpr(newOperand, inAssignment, gateVars) 593 | } 594 | opArray := operandExprs(operand.(ProjArray).Projs, inAssignment, gateVars) 595 | opArray = []string{strings.Join(opArray, ", ")} 596 | return fmt.Sprintf("vec!%s", opArray) 597 | case Const: 598 | return fmt.Sprintf("(%s:F)", operand.(Const).Value.Text(10)) 599 | case Integer: 600 | return operand.(Integer).Value.Text(10) 601 | default: 602 | fmt.Printf("Type %T\n", operand) 603 | panic("not yet supported") 604 | } 605 | } 606 | 607 | func operandExprs(operands []Operand, inAssignment []ExArg, gateVars []string) []string { 608 | exprs := []string{} 609 | for _, operand := range operands { 610 | exprs = append(exprs, operandExpr(operand, inAssignment, gateVars)) 611 | } 612 | return exprs 613 | } 614 | -------------------------------------------------------------------------------- /extractor/misc.go: -------------------------------------------------------------------------------- 1 | package extractor 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "reflect" 8 | "runtime/debug" 9 | "strings" 10 | 11 | "github.com/consensys/gnark/frontend" 12 | "github.com/consensys/gnark/frontend/schema" 13 | "github.com/mitchellh/copystructure" 14 | "github.com/reilabs/gnark-lean-extractor/v3/abstractor" 15 | ) 16 | 17 | // recoverError is used in the top level interface to prevent panic 18 | // caused by any of the methods in the extractor from propagating 19 | // When go is running in test mode, it prints the stack trace to aid 20 | // debugging. 21 | func recoverError() (err error) { 22 | if recover() != nil { 23 | if flag.Lookup("test.v") != nil { 24 | stack := string(debug.Stack()) 25 | fmt.Println(stack) 26 | } 27 | err = errors.New("Panic extracting circuit to Lean") 28 | } 29 | return nil 30 | } 31 | 32 | // arrayToSlice returns a slice of elements identical to 33 | // the input array `v` 34 | func arrayToSlice(v reflect.Value) []frontend.Variable { 35 | if v.Len() == 0 { 36 | return []frontend.Variable{} 37 | } 38 | 39 | switch v.Index(0).Kind() { 40 | case reflect.Array: 41 | args := []frontend.Variable{} 42 | for i := 0; i < v.Len(); i++ { 43 | arg := arrayToSlice(v.Index(i)) 44 | // The reason to check for len != 0 is to avoid generating 45 | // lists of empty nested lists 46 | if len(arg) != 0 { 47 | args = append(args, arg) 48 | } 49 | } 50 | return args 51 | case reflect.Interface: 52 | res := []frontend.Variable{} 53 | for i := 0; i < v.Len(); i++ { 54 | res = append(res, v.Index(i).Elem().Interface().(frontend.Variable)) 55 | } 56 | return res 57 | default: 58 | return []frontend.Variable{} 59 | } 60 | } 61 | 62 | // flattenSlice takes a slice and returns a single dimension 63 | // slice of frontend.Variable. This is needed to transform 64 | // nested slices into single dimensional slices to be 65 | // processed by sanitizeVars. 66 | func flattenSlice(value reflect.Value) []frontend.Variable { 67 | if value.Len() == 0 { 68 | return []frontend.Variable{} 69 | } 70 | if value.Index(0).Kind() == reflect.Slice { 71 | args := []frontend.Variable{} 72 | for i := 0; i < value.Len(); i++ { 73 | arg := flattenSlice(value.Index(i)) 74 | // The reason to check for len != 0 is to avoid generating 75 | // lists of empty nested lists 76 | if len(arg) != 0 { 77 | args = append(args, arg) 78 | } 79 | } 80 | return args 81 | } 82 | return value.Interface().([]frontend.Variable) 83 | } 84 | 85 | // arrayInit generates the Proj{} object for each element of v 86 | func arrayInit(f schema.Field, v reflect.Value, op Operand) error { 87 | for i := 0; i < f.ArraySize; i++ { 88 | op := Proj{op, i, f.ArraySize} 89 | switch len(f.SubFields) { 90 | case 1: 91 | arrayInit(f.SubFields[0], v.Index(i), op) 92 | case 0: 93 | if v.Len() != f.ArraySize { 94 | // Slices of this type aren't supported yet [[ ] [ ]] 95 | // gnark newSchema doesn't handle different dimensions 96 | fmt.Printf("Wrong slices dimensions %+v\n", v) 97 | panic("Only slices dimensions not matching") 98 | } 99 | value := reflect.ValueOf(op) 100 | v.Index(i).Set(value) 101 | default: 102 | panic("Only nested arrays supported in SubFields") 103 | } 104 | } 105 | return nil 106 | } 107 | 108 | // arrayZero sets all the elements of the input slice v to nil. 109 | // It is used when initialising a new circuit or gadget to ensure 110 | // the object is clean 111 | func arrayZero(v reflect.Value) { 112 | switch v.Kind() { 113 | case reflect.Slice: 114 | if v.Len() != 0 { 115 | // Check if there are nested arrays. If yes, continue recursion 116 | // until most nested array 117 | if v.Addr().Elem().Index(0).Kind() == reflect.Slice { 118 | for i := 0; i < v.Len(); i++ { 119 | arrayZero(v.Addr().Elem().Index(i)) 120 | } 121 | } else { 122 | zero_array := make([]frontend.Variable, v.Len(), v.Len()) 123 | v.Set(reflect.ValueOf(&zero_array).Elem()) 124 | } 125 | } 126 | default: 127 | panic("Only nested slices supported in SubFields of slices") 128 | } 129 | } 130 | 131 | // kindOfField returns the Kind of field in struct a 132 | func kindOfField(a any, field string) reflect.Kind { 133 | v := reflect.ValueOf(a).Elem() 134 | f := v.FieldByName(field) 135 | return f.Kind() 136 | } 137 | 138 | // getStructName returns the name of struct a 139 | func getStructName(a any) string { 140 | return reflect.TypeOf(a).Elem().Name() 141 | } 142 | 143 | // updateProj recursively creates a Proj object using the `Index` and `Size` from the 144 | // optional argument `extra`. It uses the argument `gate` as Operand for the innermost Proj. 145 | // The `extra` optional argument contains the `Index` in even indices and the `Size` in odd indices, 146 | // elements are discarded from the end. 147 | func updateProj(gate Operand, extra ...int) Proj { 148 | if len(extra) == 2 { 149 | return Proj{gate, extra[0], extra[1]} 150 | } else if len(extra) > 0 && len(extra)%2 == 0 { 151 | return Proj{updateProj(gate, extra[:len(extra)-2]...), extra[len(extra)-2], extra[len(extra)-1]} 152 | } 153 | fmt.Printf("updateProj gate: %#v | extra: %+v", gate, extra) 154 | panic("updateProj called with wrong number of elements in extra") 155 | } 156 | 157 | // replaceArg generates the object returned when calling the gadget in a circuit. 158 | // The object returned has the same structure as ExGadget.OutputsFlat but it needs 159 | // to have updated `Proj` fields. gate argument corresponds to the `Gate` object of the 160 | // gadget call. extra argument keeps track of the `Size` and `Index` elements of the nested 161 | // Proj. These need to be replaced because the output of a gadget is a combination 162 | // of Proj. 163 | func replaceArg(gOutputs interface{}, gate Operand, extra ...int) interface{} { 164 | // extra[0] -> i 165 | // extra[1] -> len 166 | switch v := (gOutputs).(type) { 167 | case Input, Gate: 168 | if len(extra) == 2 { 169 | return Proj{gate, extra[0], extra[1]} 170 | } 171 | return gate 172 | case Proj: 173 | if len(extra) >= 2 { 174 | return updateProj(gate, extra...) 175 | } 176 | return gate 177 | case []frontend.Variable: 178 | res := make([]frontend.Variable, len(v)) 179 | for i, o := range v { 180 | res[i] = replaceArg(o, gate, append(extra, []int{i, len(v)}...)...) 181 | } 182 | return res 183 | case [][]frontend.Variable: 184 | res := make([][]frontend.Variable, len(v)) 185 | for i, o := range v { 186 | res[i] = replaceArg(o, gate, append(extra, []int{i, len(v)}...)...).([]frontend.Variable) 187 | } 188 | return res 189 | case [][][]frontend.Variable: 190 | res := make([][][]frontend.Variable, len(v)) 191 | for i, o := range v { 192 | res[i] = replaceArg(o, gate, append(extra, []int{i, len(v)}...)...).([][]frontend.Variable) 193 | } 194 | return res 195 | case nil: 196 | return []frontend.Variable{} 197 | default: 198 | fmt.Printf("replaceArg invalid argument of type %T %#v\n", gOutputs, gOutputs) 199 | panic("replaceArg invalid argument") 200 | } 201 | } 202 | 203 | // cloneGadget performs deep cloning of `gadget` 204 | func cloneGadget(gadget abstractor.GadgetDefinition) abstractor.GadgetDefinition { 205 | dup, err := copystructure.Copy(gadget) 206 | if err != nil { 207 | panic(err) 208 | } 209 | // The reason for the following lines is to generate a reflect.Ptr to the interface 210 | v := reflect.ValueOf(dup) 211 | tmp_gadget := reflect.New(v.Type()) 212 | tmp_gadget.Elem().Set(v) 213 | return tmp_gadget.Interface().(abstractor.GadgetDefinition) 214 | } 215 | 216 | // generateUniqueName is a function that generates the gadget function name in Lean 217 | // To distinguish between gadgets instantiated with different array 218 | // sizes, add a suffix to the name. The suffix of each instantiation 219 | // is made up of the concatenation of the length of all the array 220 | // fields in the gadget 221 | func generateUniqueName(element any, args []ExArg) string { 222 | suffix := "" 223 | for _, a := range args { 224 | if a.Kind == reflect.Array || a.Kind == reflect.Slice { 225 | suffix += "_" 226 | suffix += strings.Join(getSizeGadgetArgs(a.Type), "_") 227 | } 228 | } 229 | 230 | val := reflect.ValueOf(element).Elem() 231 | for i := 0; i < val.NumField(); i++ { 232 | switch val.Field(i).Kind() { 233 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 234 | suffix += fmt.Sprintf("_%d", val.Field(i).Int()) 235 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 236 | suffix += fmt.Sprintf("_%d", val.Field(i).Uint()) 237 | case reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: 238 | fmt.Printf("-- Gadget name doesn't differentiate yet between different values of type %+v.\n", val.Field(i).Kind()) 239 | fmt.Println("-- Proceed with caution") 240 | } 241 | } 242 | return fmt.Sprintf("%s%s", reflect.TypeOf(element).Elem().Name(), suffix) 243 | } 244 | 245 | // getGadgetByName checks if `name` matches the ExGadget.Name of one of 246 | // the elements in `gadgets` 247 | func getGadgetByName(gadgets []ExGadget, name string) abstractor.Gadget { 248 | for _, gadget := range gadgets { 249 | if gadget.Name == name { 250 | return &gadget 251 | } 252 | } 253 | return nil 254 | } 255 | 256 | // getSizeGadgetArgs generates the concatenation of dimensions of 257 | // a slice/array (i.e. [3][2]frontend.Variable --> ["3","2"]) 258 | // It is used to generate a unique gadget name 259 | func getSizeGadgetArgs(elem ExArgType) []string { 260 | if elem.Type == nil { 261 | return []string{fmt.Sprintf("%d", elem.Size)} 262 | } 263 | return append(getSizeGadgetArgs(*elem.Type), fmt.Sprintf("%d", elem.Size)) 264 | } 265 | -------------------------------------------------------------------------------- /extractor/test/another_circuit_test.go: -------------------------------------------------------------------------------- 1 | package extractor_test 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/consensys/gnark-crypto/ecc" 8 | "github.com/consensys/gnark/frontend" 9 | "github.com/reilabs/gnark-lean-extractor/v3/abstractor" 10 | "github.com/reilabs/gnark-lean-extractor/v3/extractor" 11 | ) 12 | 13 | // Example: Gadget with nested array of int 14 | type IntArrayGadget struct { 15 | In []frontend.Variable 16 | Matrix [2]int 17 | NestedMatrix [2][2]int 18 | } 19 | 20 | func (gadget IntArrayGadget) DefineGadget(api frontend.API) interface{} { 21 | r := api.FromBinary(gadget.In...) 22 | api.Mul(gadget.Matrix[0], gadget.Matrix[1]) 23 | return []frontend.Variable{r, r, r} 24 | } 25 | 26 | type AnotherCircuit struct { 27 | In []frontend.Variable 28 | Matrix [2][2]int 29 | } 30 | 31 | func (circuit *AnotherCircuit) Define(api frontend.API) error { 32 | r := abstractor.Call1(api, IntArrayGadget{ 33 | circuit.In, 34 | circuit.Matrix[0], 35 | circuit.Matrix, 36 | }) 37 | 38 | api.FromBinary(r[1:3]...) 39 | api.FromBinary(r[0:2]...) 40 | api.FromBinary(r...) 41 | return nil 42 | } 43 | 44 | func TestAnotherCircuit(t *testing.T) { 45 | m := [2][2]int{ 46 | {0, 36}, 47 | {1, 44}, 48 | } 49 | assignment := AnotherCircuit{ 50 | In: make([]frontend.Variable, 4), 51 | Matrix: m, 52 | } 53 | out, err := extractor.CircuitToLean(&assignment, ecc.BN254) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | checkOutput(t, out) 58 | } 59 | -------------------------------------------------------------------------------- /extractor/test/circuit_with_parameter_test.go: -------------------------------------------------------------------------------- 1 | package extractor_test 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/consensys/gnark-crypto/ecc" 8 | "github.com/consensys/gnark/frontend" 9 | "github.com/reilabs/gnark-lean-extractor/v3/abstractor" 10 | "github.com/reilabs/gnark-lean-extractor/v3/extractor" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | // Example: circuit with constant parameter 15 | type ReturnItself struct { 16 | In_1 []frontend.Variable 17 | Out []frontend.Variable 18 | } 19 | 20 | func (gadget ReturnItself) DefineGadget(api frontend.API) interface{} { 21 | for i := 0; i < len(gadget.In_1); i++ { 22 | gadget.Out[i] = api.Mul(gadget.In_1[i], gadget.In_1[i]) 23 | } 24 | 25 | return gadget.Out 26 | } 27 | 28 | type SliceGadget struct { 29 | In_1 []frontend.Variable 30 | In_2 []frontend.Variable 31 | } 32 | 33 | func (gadget SliceGadget) DefineGadget(api frontend.API) interface{} { 34 | for i := 0; i < len(gadget.In_1); i++ { 35 | api.Mul(gadget.In_1[i], gadget.In_2[i]) 36 | } 37 | 38 | r := api.FromBinary(gadget.In_1...) 39 | return r 40 | } 41 | 42 | type CircuitWithParameter struct { 43 | In frontend.Variable `gnark:",public"` 44 | Path []frontend.Variable `gnark:",public"` 45 | Tree []frontend.Variable `gnark:",public"` 46 | Param int 47 | } 48 | 49 | func (circuit *CircuitWithParameter) Define(api frontend.API) error { 50 | D := make([]frontend.Variable, 3) 51 | for i := 0; i < len(circuit.Path); i++ { 52 | D = abstractor.Call1(api, ReturnItself{ 53 | In_1: circuit.Path, 54 | Out: D, 55 | }) 56 | api.AssertIsEqual(D[1], D[2]) 57 | } 58 | 59 | api.FromBinary(circuit.Path...) 60 | api.FromBinary(D...) 61 | api.FromBinary(D[1], D[2], D[0]) 62 | api.FromBinary(D[1], 0, D[0]) 63 | api.FromBinary(D[1:3]...) 64 | bin := api.ToBinary(circuit.In) 65 | bin = api.ToBinary(circuit.Param) 66 | 67 | dec := api.FromBinary(bin...) 68 | api.AssertIsEqual(circuit.Param, dec) 69 | abstractor.Call(api, SliceGadget{circuit.Path, circuit.Path}) 70 | 71 | api.Mul(circuit.Path[0], circuit.Path[0]) 72 | abstractor.Call(api, SliceGadget{circuit.Tree, circuit.Tree}) 73 | api.AssertIsEqual(circuit.Param, circuit.In) 74 | 75 | return nil 76 | } 77 | 78 | func TestCircuitWithParameter(t *testing.T) { 79 | paramValue := 20 80 | assignment := CircuitWithParameter{Path: make([]frontend.Variable, 3), Tree: make([]frontend.Variable, 2)} 81 | assignment.Param = paramValue 82 | assert.Equal(t, assignment.Param, paramValue, "assignment.Param is a const and should be 20.") 83 | out, err := extractor.CircuitToLean(&assignment, ecc.BN254) 84 | if err != nil { 85 | log.Fatal(err) 86 | } 87 | checkOutput(t, out) 88 | } 89 | -------------------------------------------------------------------------------- /extractor/test/deletion_mbu_circuit_test.go: -------------------------------------------------------------------------------- 1 | package extractor_test 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/consensys/gnark-crypto/ecc" 8 | "github.com/consensys/gnark/frontend" 9 | "github.com/reilabs/gnark-lean-extractor/v3/abstractor" 10 | "github.com/reilabs/gnark-lean-extractor/v3/extractor" 11 | ) 12 | 13 | // Example: Mismatched arguments error 14 | type DeletionProof struct { 15 | DeletionIndices []frontend.Variable 16 | PreRoot frontend.Variable 17 | IdComms []frontend.Variable 18 | MerkleProofs [][]frontend.Variable 19 | 20 | BatchSize int 21 | Depth int 22 | } 23 | 24 | func (gadget DeletionProof) DefineGadget(api frontend.API) interface{} { 25 | return gadget.PreRoot 26 | } 27 | 28 | type DeletionMbuCircuit struct { 29 | // single public input 30 | InputHash frontend.Variable `gnark:",public"` 31 | 32 | // private inputs, but used as public inputs 33 | DeletionIndices []frontend.Variable `gnark:"input"` 34 | PreRoot frontend.Variable `gnark:"input"` 35 | PostRoot frontend.Variable `gnark:"input"` 36 | 37 | // private inputs 38 | IdComms []frontend.Variable `gnark:"input"` 39 | MerkleProofs [][]frontend.Variable `gnark:"input"` 40 | 41 | BatchSize int 42 | Depth int 43 | } 44 | 45 | func (circuit *DeletionMbuCircuit) Define(api frontend.API) error { 46 | root := abstractor.Call(api, DeletionProof{ 47 | DeletionIndices: circuit.DeletionIndices, 48 | PreRoot: circuit.PreRoot, 49 | IdComms: circuit.IdComms, 50 | MerkleProofs: circuit.MerkleProofs, 51 | BatchSize: circuit.BatchSize, 52 | Depth: circuit.Depth, 53 | }) 54 | 55 | // Final root needs to match. 56 | api.AssertIsEqual(root, circuit.PostRoot) 57 | 58 | return nil 59 | } 60 | 61 | func TestDeletionMbuCircuit(t *testing.T) { 62 | batchSize := 2 63 | treeDepth := 3 64 | proofs := make([][]frontend.Variable, batchSize) 65 | for i := 0; i < int(batchSize); i++ { 66 | proofs[i] = make([]frontend.Variable, treeDepth) 67 | } 68 | 69 | assignment := DeletionMbuCircuit{ 70 | DeletionIndices: make([]frontend.Variable, batchSize), 71 | IdComms: make([]frontend.Variable, batchSize), 72 | MerkleProofs: proofs, 73 | 74 | BatchSize: int(batchSize), 75 | Depth: int(treeDepth), 76 | } 77 | out, err := extractor.CircuitToLean(&assignment, ecc.BN254) 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | checkOutput(t, out) 82 | } 83 | -------------------------------------------------------------------------------- /extractor/test/merkle_recover_test.go: -------------------------------------------------------------------------------- 1 | package extractor_test 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/consensys/gnark-crypto/ecc" 8 | "github.com/consensys/gnark/frontend" 9 | "github.com/reilabs/gnark-lean-extractor/v3/abstractor" 10 | "github.com/reilabs/gnark-lean-extractor/v3/extractor" 11 | ) 12 | 13 | // Example: circuit with arrays and gadget 14 | type DummyHash struct { 15 | In_1 frontend.Variable 16 | In_2 frontend.Variable 17 | } 18 | 19 | func (gadget DummyHash) DefineGadget(api frontend.API) interface{} { 20 | r := api.Mul(gadget.In_1, gadget.In_2) 21 | return r 22 | } 23 | 24 | type MerkleRecover struct { 25 | Root frontend.Variable `gnark:",public"` 26 | Element frontend.Variable `gnark:",public"` 27 | Path [20]frontend.Variable `gnark:",secret"` 28 | Proof [20]frontend.Variable `gnark:",secret"` 29 | } 30 | 31 | func (circuit *MerkleRecover) Define(api frontend.API) error { 32 | current := circuit.Element 33 | for i := 0; i < len(circuit.Path); i++ { 34 | leftHash := abstractor.Call(api, DummyHash{current, circuit.Proof[i]}) 35 | rightHash := abstractor.Call(api, DummyHash{circuit.Proof[i], current}) 36 | current = api.Select(circuit.Path[i], rightHash, leftHash) 37 | } 38 | api.AssertIsEqual(current, circuit.Root) 39 | 40 | return nil 41 | } 42 | 43 | func TestMerkleRecover(t *testing.T) { 44 | assignment := MerkleRecover{} 45 | out, err := extractor.CircuitToLean(&assignment, ecc.BN254) 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | checkOutput(t, out) 50 | } 51 | -------------------------------------------------------------------------------- /extractor/test/my_circuit_test.go: -------------------------------------------------------------------------------- 1 | package extractor_test 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/consensys/gnark-crypto/ecc" 8 | "github.com/consensys/gnark/frontend" 9 | "github.com/reilabs/gnark-lean-extractor/v3/extractor" 10 | ) 11 | 12 | // Example: readme circuit 13 | type MyCircuit struct { 14 | In_1 frontend.Variable 15 | In_2 frontend.Variable 16 | Out frontend.Variable 17 | } 18 | 19 | func (circuit *MyCircuit) Define(api frontend.API) error { 20 | sum := api.Add(circuit.In_1, circuit.In_2) 21 | api.AssertIsEqual(sum, circuit.Out) 22 | return nil 23 | } 24 | 25 | func TestMyCircuit(t *testing.T) { 26 | circuit := MyCircuit{} 27 | out, err := extractor.CircuitToLean(&circuit, ecc.BN254) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | checkOutput(t, out) 32 | } 33 | -------------------------------------------------------------------------------- /extractor/test/slices_optimisation_test.go: -------------------------------------------------------------------------------- 1 | package extractor_test 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/consensys/gnark-crypto/ecc" 8 | "github.com/consensys/gnark/frontend" 9 | "github.com/reilabs/gnark-lean-extractor/v3/abstractor" 10 | "github.com/reilabs/gnark-lean-extractor/v3/extractor" 11 | ) 12 | 13 | // Example: checking slices optimisation 14 | type TwoSlices struct { 15 | TwoDim [][]frontend.Variable 16 | } 17 | 18 | func (gadget TwoSlices) DefineGadget(api frontend.API) interface{} { 19 | return gadget.TwoDim 20 | } 21 | 22 | type ThreeSlices struct { 23 | ThreeDim [][][]frontend.Variable 24 | } 25 | 26 | func (gadget ThreeSlices) DefineGadget(api frontend.API) interface{} { 27 | return gadget.ThreeDim 28 | } 29 | 30 | type SlicesGadget struct { 31 | TwoDim [][]frontend.Variable 32 | ThreeDim [][][]frontend.Variable 33 | } 34 | 35 | func (gadget SlicesGadget) DefineGadget(api frontend.API) interface{} { 36 | return append(gadget.ThreeDim[0][0], gadget.TwoDim[0]...) 37 | } 38 | 39 | type SlicesOptimisation struct { 40 | Test frontend.Variable 41 | Id []frontend.Variable 42 | TwoDim [][]frontend.Variable 43 | ThreeDim [][][]frontend.Variable 44 | } 45 | 46 | func (circuit *SlicesOptimisation) Define(api frontend.API) error { 47 | abstractor.Call1(api, SlicesGadget{ 48 | TwoDim: circuit.TwoDim, 49 | ThreeDim: circuit.ThreeDim, 50 | }) 51 | abstractor.Call1(api, SlicesGadget{ 52 | TwoDim: [][]frontend.Variable{circuit.TwoDim[1], circuit.TwoDim[0]}, 53 | ThreeDim: [][][]frontend.Variable{circuit.ThreeDim[1], circuit.ThreeDim[0]}, 54 | }) 55 | abstractor.Call1(api, SlicesGadget{ 56 | TwoDim: [][]frontend.Variable{{circuit.TwoDim[1][1]}, {circuit.TwoDim[1][0]}}, 57 | ThreeDim: [][][]frontend.Variable{circuit.ThreeDim[1], circuit.ThreeDim[0], circuit.ThreeDim[1]}, 58 | }) 59 | abstractor.Call1(api, SlicesGadget{ 60 | TwoDim: [][]frontend.Variable{circuit.TwoDim[1], {circuit.TwoDim[1][0], circuit.TwoDim[0][0], circuit.TwoDim[1][1]}}, 61 | ThreeDim: circuit.ThreeDim, 62 | }) 63 | abstractor.Call2(api, TwoSlices{ 64 | TwoDim: circuit.TwoDim, 65 | }) 66 | a := abstractor.Call3(api, ThreeSlices{ 67 | ThreeDim: circuit.ThreeDim, 68 | }) 69 | b := abstractor.Call3(api, ThreeSlices{ 70 | ThreeDim: a, 71 | }) 72 | abstractor.Call3(api, ThreeSlices{ 73 | ThreeDim: b, 74 | }) 75 | 76 | return nil 77 | } 78 | 79 | func TestSlicesOptimisation(t *testing.T) { 80 | depthOne := 2 81 | depthTwo := 3 82 | depthThree := 4 83 | twoSlice := make([][]frontend.Variable, depthOne) 84 | for i := 0; i < int(depthOne); i++ { 85 | twoSlice[i] = make([]frontend.Variable, depthTwo) 86 | } 87 | 88 | threeSlice := make([][][]frontend.Variable, depthOne) 89 | for x := 0; x < int(depthOne); x++ { 90 | threeSlice[x] = make([][]frontend.Variable, depthTwo) 91 | for y := 0; y < int(depthTwo); y++ { 92 | threeSlice[x][y] = make([]frontend.Variable, depthThree) 93 | } 94 | } 95 | 96 | assignment := SlicesOptimisation{ 97 | Id: make([]frontend.Variable, depthTwo), 98 | TwoDim: twoSlice, 99 | ThreeDim: threeSlice, 100 | } 101 | out, err := extractor.CircuitToLean(&assignment, ecc.BN254) 102 | if err != nil { 103 | log.Fatal(err) 104 | } 105 | checkOutput(t, out) 106 | } 107 | -------------------------------------------------------------------------------- /extractor/test/to_binary_circuit_test.go: -------------------------------------------------------------------------------- 1 | package extractor_test 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/consensys/gnark-crypto/ecc" 8 | "github.com/consensys/gnark/frontend" 9 | "github.com/reilabs/gnark-lean-extractor/v3/abstractor" 10 | "github.com/reilabs/gnark-lean-extractor/v3/extractor" 11 | ) 12 | 13 | // Example: Gadget that returns a vector 14 | type OptimisedVectorGadget struct { 15 | In frontend.Variable 16 | } 17 | 18 | func (gadget OptimisedVectorGadget) DefineGadget(api frontend.API) interface{} { 19 | return api.ToBinary(gadget.In, 3) 20 | } 21 | 22 | // Example: ToBinary behaviour and nested Slice 23 | type VectorGadget struct { 24 | In_1 []frontend.Variable 25 | In_2 []frontend.Variable 26 | Nested [][]frontend.Variable 27 | } 28 | 29 | func (gadget VectorGadget) DefineGadget(api frontend.API) interface{} { 30 | var sum frontend.Variable 31 | for i := 0; i < len(gadget.In_1); i++ { 32 | sum = api.Mul(gadget.In_1[i], gadget.In_2[i]) 33 | } 34 | return []frontend.Variable{sum, sum, sum} 35 | } 36 | 37 | type ToBinaryCircuit struct { 38 | In frontend.Variable `gnark:",public"` 39 | Out frontend.Variable `gnark:",public"` 40 | Double [][]frontend.Variable `gnark:",public"` 41 | } 42 | 43 | func (circuit *ToBinaryCircuit) Define(api frontend.API) error { 44 | bin := api.ToBinary(circuit.In, 3) 45 | bout := api.ToBinary(circuit.Out, 3) 46 | 47 | api.Add(circuit.Double[2][2], circuit.Double[1][1], circuit.Double[0][0]) 48 | api.Mul(bin[1], bout[1]) 49 | d := abstractor.Call1(api, VectorGadget{circuit.Double[2][:], circuit.Double[0][:], circuit.Double}) 50 | api.Mul(d[2], d[1]) 51 | 52 | return nil 53 | } 54 | 55 | func TestGadgetExtraction(t *testing.T) { 56 | dim_1 := 3 57 | dim_2 := 3 58 | doubleSlice := make([][]frontend.Variable, dim_1) 59 | for i := 0; i < int(dim_1); i++ { 60 | doubleSlice[i] = make([]frontend.Variable, dim_2) 61 | } 62 | assignment := VectorGadget{ 63 | In_1: make([]frontend.Variable, dim_2), 64 | In_2: make([]frontend.Variable, dim_2), 65 | Nested: doubleSlice, 66 | } 67 | out, err := extractor.GadgetToLean(&assignment, ecc.BN254) 68 | if err != nil { 69 | log.Fatal(err) 70 | } 71 | checkOutput(t, out) 72 | } 73 | 74 | func TestToBinaryCircuit(t *testing.T) { 75 | dim_1 := 3 76 | dim_2 := 3 77 | doubleSlice := make([][]frontend.Variable, dim_1) 78 | for i := 0; i < int(dim_1); i++ { 79 | doubleSlice[i] = make([]frontend.Variable, dim_2) 80 | } 81 | assignment := ToBinaryCircuit{Double: doubleSlice} 82 | out, err := extractor.CircuitToLean(&assignment, ecc.BN254) 83 | if err != nil { 84 | log.Fatal(err) 85 | } 86 | checkOutput(t, out) 87 | } 88 | -------------------------------------------------------------------------------- /extractor/test/two_gadgets_test.go: -------------------------------------------------------------------------------- 1 | package extractor_test 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/consensys/gnark-crypto/ecc" 8 | "github.com/consensys/gnark/frontend" 9 | "github.com/reilabs/gnark-lean-extractor/v3/abstractor" 10 | "github.com/reilabs/gnark-lean-extractor/v3/extractor" 11 | ) 12 | 13 | // Example: circuit with multiple gadgets 14 | type MyWidget struct { 15 | Test_1 frontend.Variable 16 | Test_2 frontend.Variable 17 | Num uint32 18 | } 19 | 20 | func (gadget MyWidget) DefineGadget(api frontend.API) interface{} { 21 | sum := api.Add(gadget.Test_1, gadget.Test_2) 22 | mul := api.Mul(gadget.Test_1, gadget.Test_2) 23 | r := api.Div(sum, mul) 24 | api.AssertIsBoolean(gadget.Num) 25 | return r 26 | } 27 | 28 | type MySecondWidget struct { 29 | Test_1 frontend.Variable 30 | Test_2 frontend.Variable 31 | Num int 32 | } 33 | 34 | func (gadget MySecondWidget) DefineGadget(api frontend.API) interface{} { 35 | mul := api.Mul(gadget.Test_1, gadget.Test_2) 36 | snd := abstractor.Call(api, MyWidget{gadget.Test_1, gadget.Test_2, uint32(gadget.Num)}) 37 | api.Mul(mul, snd) 38 | return nil 39 | } 40 | 41 | type TwoGadgets struct { 42 | In_1 frontend.Variable 43 | In_2 frontend.Variable 44 | Num int 45 | } 46 | 47 | func (circuit *TwoGadgets) Define(api frontend.API) error { 48 | sum := api.Add(circuit.In_1, circuit.In_2) 49 | prod := api.Mul(circuit.In_1, circuit.In_2) 50 | abstractor.CallVoid(api, MySecondWidget{sum, prod, circuit.Num}) 51 | return nil 52 | } 53 | 54 | func TestTwoGadgets(t *testing.T) { 55 | assignment := TwoGadgets{Num: 11} 56 | out, err := extractor.CircuitToLean(&assignment, ecc.BN254) 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | checkOutput(t, out) 61 | } 62 | 63 | func TestExtractGadgets(t *testing.T) { 64 | assignment_1 := DummyHash{} 65 | assignment_2 := MySecondWidget{Num: 11} 66 | assignment_3 := MySecondWidget{Num: 9} 67 | out, err := extractor.ExtractGadgets("MultipleGadgets", ecc.BN254, &assignment_1, &assignment_2, &assignment_3) 68 | if err != nil { 69 | log.Fatal(err) 70 | } 71 | checkOutput(t, out) 72 | } 73 | 74 | func TestExtractGadgetsVectors(t *testing.T) { 75 | dim_1 := 3 76 | dim_2 := 3 77 | doubleSlice := make([][]frontend.Variable, dim_1) 78 | for i := 0; i < int(dim_1); i++ { 79 | doubleSlice[i] = make([]frontend.Variable, dim_2) 80 | } 81 | assignment_1 := VectorGadget{ 82 | In_1: make([]frontend.Variable, dim_2), 83 | In_2: make([]frontend.Variable, dim_2), 84 | Nested: doubleSlice, 85 | } 86 | assignment_2 := ReturnItself{ 87 | In_1: make([]frontend.Variable, dim_1), 88 | Out: make([]frontend.Variable, dim_1), 89 | } 90 | assignment_3 := OptimisedVectorGadget{} 91 | out, err := extractor.ExtractGadgets("MultipleGadgetsVectors", ecc.BN254, &assignment_1, &assignment_2, &assignment_3) 92 | if err != nil { 93 | log.Fatal(err) 94 | } 95 | checkOutput(t, out) 96 | } 97 | 98 | func TestExtractCircuits(t *testing.T) { 99 | assignment_1 := TwoGadgets{Num: 11} 100 | assignment_2 := MerkleRecover{} 101 | 102 | dim_1 := 3 103 | dim_2 := 3 104 | doubleSlice := make([][]frontend.Variable, dim_1) 105 | for i := 0; i < int(dim_1); i++ { 106 | doubleSlice[i] = make([]frontend.Variable, dim_2) 107 | } 108 | assignment_3 := ToBinaryCircuit{Double: doubleSlice} 109 | assignment_4 := TwoGadgets{Num: 6} 110 | assignment_5 := TwoGadgets{Num: 6} 111 | 112 | out, err := extractor.ExtractCircuits("MultipleCircuits", ecc.BN254, &assignment_3, &assignment_2, &assignment_1, &assignment_4, &assignment_5) 113 | if err != nil { 114 | log.Fatal(err) 115 | } 116 | checkOutput(t, out) 117 | } 118 | -------------------------------------------------------------------------------- /extractor/test/utils_test.go: -------------------------------------------------------------------------------- 1 | package extractor_test 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "log" 10 | "os" 11 | "testing" 12 | ) 13 | 14 | // saveOutput can be called once when creating/changing a test to generate 15 | // the reference result 16 | func saveOutput(filename string, testOutput string) { 17 | f, err := os.Create(filename) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | defer f.Close() 22 | 23 | _, err = f.WriteString(testOutput) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | } 28 | 29 | // checkOutput performs a check of the circuit generated by the extractor. 30 | // If the hashes don't match, the circuit generated by the extractor is printed. 31 | func checkOutput(t *testing.T, testOutput string) { 32 | // I assume tests are executed from the extractor/test directory 33 | filename := fmt.Sprintf("../../test/%s.lean", t.Name()) 34 | 35 | // https://stackoverflow.com/a/66405130 36 | if _, err := os.Stat(filename); errors.Is(err, os.ErrNotExist) { 37 | saveOutput(filename, testOutput) 38 | } 39 | 40 | f, err := os.Open(filename) 41 | if err != nil { 42 | log.Fatalf("Error checking test output\n\n%s\n\n%s\n\n", err, testOutput) 43 | } 44 | defer f.Close() 45 | 46 | h := sha256.New() 47 | if _, err := io.Copy(h, f); err != nil { 48 | log.Fatal(err) 49 | } 50 | 51 | correctHash := h.Sum(nil) 52 | 53 | h.Reset() 54 | if _, err := h.Write([]byte(testOutput)); err != nil { 55 | log.Fatal(err) 56 | } 57 | testResultHash := h.Sum(nil) 58 | if !bytes.Equal(correctHash, testResultHash) { 59 | t.Logf("This circuit doesn't match the result in the test folder\n\n%s", testOutput) 60 | t.Fail() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/reilabs/gnark-lean-extractor/v3 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/consensys/gnark v0.9.2-0.20240322153533-3abde1199375 7 | github.com/consensys/gnark-crypto v0.12.2-0.20240215234832-d72fcb379d3e 8 | github.com/mitchellh/copystructure v1.2.0 9 | github.com/stretchr/testify v1.8.4 10 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 11 | ) 12 | 13 | require ( 14 | github.com/bits-and-blooms/bitset v1.8.0 // indirect 15 | github.com/blang/semver/v4 v4.0.0 // indirect 16 | github.com/consensys/bavard v0.1.13 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect 19 | github.com/kr/text v0.2.0 // indirect 20 | github.com/mattn/go-colorable v0.1.13 // indirect 21 | github.com/mattn/go-isatty v0.0.19 // indirect 22 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 23 | github.com/mmcloughlin/addchain v0.4.0 // indirect 24 | github.com/pmezard/go-difflib v1.0.0 // indirect 25 | github.com/rs/zerolog v1.30.0 // indirect 26 | golang.org/x/sys v0.15.0 // indirect 27 | gopkg.in/yaml.v3 v3.0.1 // indirect 28 | rsc.io/tmplfunc v0.0.3 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c= 2 | github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 3 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 4 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 5 | github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= 6 | github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= 7 | github.com/consensys/gnark v0.9.2-0.20240322153533-3abde1199375 h1:yp/JtBdxrG2vjbe6vntaP11m6uROj/QmI86jxW7Ih6k= 8 | github.com/consensys/gnark v0.9.2-0.20240322153533-3abde1199375/go.mod h1:0dnRvl8EDbPsSZsIg8xOP1Au8cf43xOlT7/BhwMV98g= 9 | github.com/consensys/gnark-crypto v0.12.2-0.20240215234832-d72fcb379d3e h1:MKdOuCiy2DAX1tMp2YsmtNDaqdigpY6B5cZQDJ9BvEo= 10 | github.com/consensys/gnark-crypto v0.12.2-0.20240215234832-d72fcb379d3e/go.mod h1:wKqwsieaKPThcFkHe0d0zMsbHEUWFmZcG7KBCse210o= 11 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 12 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= 16 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 17 | github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= 18 | github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= 19 | github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= 20 | github.com/ingonyama-zk/icicle v0.0.0-20230928131117-97f0079e5c71 h1:YxI1RTPzpFJ3MBmxPl3Bo0F7ume7CmQEC1M9jL6CT94= 21 | github.com/ingonyama-zk/iciclegnark v0.1.0 h1:88MkEghzjQBMjrYRJFxZ9oR9CTIpB8NG2zLeCJSvXKQ= 22 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 23 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 24 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 25 | github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= 26 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 27 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 28 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 29 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 30 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 31 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 32 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 33 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 34 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 35 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 36 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 37 | github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= 38 | github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= 39 | github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= 40 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 41 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 42 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 43 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 44 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 45 | github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= 46 | github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= 47 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 48 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 49 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 50 | golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= 51 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= 52 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 53 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 54 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 55 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 56 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 57 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 58 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 59 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 60 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 61 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 62 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 63 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 64 | rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= 65 | rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= 66 | -------------------------------------------------------------------------------- /test/TestAnotherCircuit.lean: -------------------------------------------------------------------------------- 1 | import ProvenZk.Gates 2 | import ProvenZk.Ext.Vector 3 | 4 | set_option linter.unusedVariables false 5 | 6 | namespace AnotherCircuit 7 | 8 | def Order : ℕ := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 9 | variable [Fact (Nat.Prime Order)] 10 | abbrev F := ZMod Order 11 | abbrev Gates := GatesGnark9 Order 12 | 13 | def IntArrayGadget_4 (In: Vector F 4) (k: Vector F 3 -> Prop): Prop := 14 | ∃gate_0, Gates.from_binary In gate_0 ∧ 15 | ∃_ignored_, _ignored_ = Gates.mul (0:F) (36:F) ∧ 16 | k vec![gate_0, gate_0, gate_0] 17 | 18 | def circuit (In: Vector F 4): Prop := 19 | IntArrayGadget_4 In fun gate_0 => 20 | ∃_ignored_, Gates.from_binary vec![gate_0[1], gate_0[2]] _ignored_ ∧ 21 | ∃_ignored_, Gates.from_binary vec![gate_0[0], gate_0[1]] _ignored_ ∧ 22 | ∃_ignored_, Gates.from_binary gate_0 _ignored_ ∧ 23 | True 24 | 25 | end AnotherCircuit -------------------------------------------------------------------------------- /test/TestCircuitWithParameter.lean: -------------------------------------------------------------------------------- 1 | import ProvenZk.Gates 2 | import ProvenZk.Ext.Vector 3 | 4 | set_option linter.unusedVariables false 5 | 6 | namespace CircuitWithParameter 7 | 8 | def Order : ℕ := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 9 | variable [Fact (Nat.Prime Order)] 10 | abbrev F := ZMod Order 11 | abbrev Gates := GatesGnark9 Order 12 | 13 | def ReturnItself_3_3 (In_1: Vector F 3) (Out: Vector F 3) (k: Vector F 3 -> Prop): Prop := 14 | ∃gate_0, gate_0 = Gates.mul In_1[0] In_1[0] ∧ 15 | ∃gate_1, gate_1 = Gates.mul In_1[1] In_1[1] ∧ 16 | ∃gate_2, gate_2 = Gates.mul In_1[2] In_1[2] ∧ 17 | k vec![gate_0, gate_1, gate_2] 18 | 19 | def SliceGadget_3_3 (In_1: Vector F 3) (In_2: Vector F 3) (k: F -> Prop): Prop := 20 | ∃_ignored_, _ignored_ = Gates.mul In_1[0] In_2[0] ∧ 21 | ∃_ignored_, _ignored_ = Gates.mul In_1[1] In_2[1] ∧ 22 | ∃_ignored_, _ignored_ = Gates.mul In_1[2] In_2[2] ∧ 23 | ∃gate_3, Gates.from_binary In_1 gate_3 ∧ 24 | k gate_3 25 | 26 | def SliceGadget_2_2 (In_1: Vector F 2) (In_2: Vector F 2) (k: F -> Prop): Prop := 27 | ∃_ignored_, _ignored_ = Gates.mul In_1[0] In_2[0] ∧ 28 | ∃_ignored_, _ignored_ = Gates.mul In_1[1] In_2[1] ∧ 29 | ∃gate_2, Gates.from_binary In_1 gate_2 ∧ 30 | k gate_2 31 | 32 | def circuit (In: F) (Path: Vector F 3) (Tree: Vector F 2): Prop := 33 | ReturnItself_3_3 Path vec![(0:F), (0:F), (0:F)] fun gate_0 => 34 | Gates.eq gate_0[1] gate_0[2] ∧ 35 | ReturnItself_3_3 Path gate_0 fun gate_2 => 36 | Gates.eq gate_2[1] gate_2[2] ∧ 37 | ReturnItself_3_3 Path gate_2 fun gate_4 => 38 | Gates.eq gate_4[1] gate_4[2] ∧ 39 | ∃_ignored_, Gates.from_binary Path _ignored_ ∧ 40 | ∃_ignored_, Gates.from_binary gate_4 _ignored_ ∧ 41 | ∃_ignored_, Gates.from_binary vec![gate_4[1], gate_4[2], gate_4[0]] _ignored_ ∧ 42 | ∃_ignored_, Gates.from_binary vec![gate_4[1], (0:F), gate_4[0]] _ignored_ ∧ 43 | ∃_ignored_, Gates.from_binary vec![gate_4[1], gate_4[2]] _ignored_ ∧ 44 | ∃_ignored_, Gates.to_binary In 254 _ignored_ ∧ 45 | ∃gate_12, Gates.to_binary (20:F) 254 gate_12 ∧ 46 | ∃gate_13, Gates.from_binary gate_12 gate_13 ∧ 47 | Gates.eq (20:F) gate_13 ∧ 48 | SliceGadget_3_3 Path Path fun _ => 49 | ∃_ignored_, _ignored_ = Gates.mul Path[0] Path[0] ∧ 50 | SliceGadget_2_2 Tree Tree fun _ => 51 | Gates.eq (20:F) In ∧ 52 | True 53 | 54 | end CircuitWithParameter -------------------------------------------------------------------------------- /test/TestDeletionMbuCircuit.lean: -------------------------------------------------------------------------------- 1 | import ProvenZk.Gates 2 | import ProvenZk.Ext.Vector 3 | 4 | set_option linter.unusedVariables false 5 | 6 | namespace DeletionMbuCircuit 7 | 8 | def Order : ℕ := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 9 | variable [Fact (Nat.Prime Order)] 10 | abbrev F := ZMod Order 11 | abbrev Gates := GatesGnark9 Order 12 | 13 | def DeletionProof_2_2_3_2_2_3 (DeletionIndices: Vector F 2) (PreRoot: F) (IdComms: Vector F 2) (MerkleProofs: Vector (Vector F 3) 2) (k: F -> Prop): Prop := 14 | k PreRoot 15 | 16 | def circuit (InputHash: F) (DeletionIndices: Vector F 2) (PreRoot: F) (PostRoot: F) (IdComms: Vector F 2) (MerkleProofs: Vector (Vector F 3) 2): Prop := 17 | DeletionProof_2_2_3_2_2_3 DeletionIndices PreRoot IdComms MerkleProofs fun gate_0 => 18 | Gates.eq gate_0 PostRoot ∧ 19 | True 20 | 21 | end DeletionMbuCircuit -------------------------------------------------------------------------------- /test/TestExtractCircuits.lean: -------------------------------------------------------------------------------- 1 | import ProvenZk.Gates 2 | import ProvenZk.Ext.Vector 3 | 4 | set_option linter.unusedVariables false 5 | 6 | namespace MultipleCircuits 7 | 8 | def Order : ℕ := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 9 | variable [Fact (Nat.Prime Order)] 10 | abbrev F := ZMod Order 11 | abbrev Gates := GatesGnark9 Order 12 | 13 | def VectorGadget_3_3_3_3 (In_1: Vector F 3) (In_2: Vector F 3) (Nested: Vector (Vector F 3) 3) (k: Vector F 3 -> Prop): Prop := 14 | ∃_ignored_, _ignored_ = Gates.mul In_1[0] In_2[0] ∧ 15 | ∃_ignored_, _ignored_ = Gates.mul In_1[1] In_2[1] ∧ 16 | ∃gate_2, gate_2 = Gates.mul In_1[2] In_2[2] ∧ 17 | k vec![gate_2, gate_2, gate_2] 18 | 19 | def DummyHash (In_1: F) (In_2: F) (k: F -> Prop): Prop := 20 | ∃gate_0, gate_0 = Gates.mul In_1 In_2 ∧ 21 | k gate_0 22 | 23 | def MyWidget_11 (Test_1: F) (Test_2: F) (k: F -> Prop): Prop := 24 | ∃gate_0, gate_0 = Gates.add Test_1 Test_2 ∧ 25 | ∃gate_1, gate_1 = Gates.mul Test_1 Test_2 ∧ 26 | ∃gate_2, Gates.div gate_0 gate_1 gate_2 ∧ 27 | Gates.is_bool (11:F) ∧ 28 | k gate_2 29 | 30 | def MySecondWidget_11 (Test_1: F) (Test_2: F) : Prop := 31 | ∃gate_0, gate_0 = Gates.mul Test_1 Test_2 ∧ 32 | MyWidget_11 Test_1 Test_2 fun gate_1 => 33 | ∃_ignored_, _ignored_ = Gates.mul gate_0 gate_1 ∧ 34 | True 35 | 36 | def MyWidget_6 (Test_1: F) (Test_2: F) (k: F -> Prop): Prop := 37 | ∃gate_0, gate_0 = Gates.add Test_1 Test_2 ∧ 38 | ∃gate_1, gate_1 = Gates.mul Test_1 Test_2 ∧ 39 | ∃gate_2, Gates.div gate_0 gate_1 gate_2 ∧ 40 | Gates.is_bool (6:F) ∧ 41 | k gate_2 42 | 43 | def MySecondWidget_6 (Test_1: F) (Test_2: F) : Prop := 44 | ∃gate_0, gate_0 = Gates.mul Test_1 Test_2 ∧ 45 | MyWidget_6 Test_1 Test_2 fun gate_1 => 46 | ∃_ignored_, _ignored_ = Gates.mul gate_0 gate_1 ∧ 47 | True 48 | 49 | def ToBinaryCircuit_3_3 (In: F) (Out: F) (Double: Vector (Vector F 3) 3): Prop := 50 | ∃gate_0, Gates.to_binary In 3 gate_0 ∧ 51 | ∃gate_1, Gates.to_binary Out 3 gate_1 ∧ 52 | ∃_ignored_, _ignored_ = Gates.add Double[2][2] Double[1][1] ∧ 53 | ∃_ignored_, _ignored_ = Gates.add _ignored_ Double[0][0] ∧ 54 | ∃_ignored_, _ignored_ = Gates.mul gate_0[1] gate_1[1] ∧ 55 | VectorGadget_3_3_3_3 Double[2] Double[0] Double fun gate_4 => 56 | ∃_ignored_, _ignored_ = Gates.mul gate_4[2] gate_4[1] ∧ 57 | True 58 | 59 | def MerkleRecover_20_20 (Root: F) (Element: F) (Path: Vector F 20) (Proof: Vector F 20): Prop := 60 | DummyHash Element Proof[0] fun gate_0 => 61 | DummyHash Proof[0] Element fun gate_1 => 62 | ∃gate_2, Gates.select Path[0] gate_1 gate_0 gate_2 ∧ 63 | DummyHash gate_2 Proof[1] fun gate_3 => 64 | DummyHash Proof[1] gate_2 fun gate_4 => 65 | ∃gate_5, Gates.select Path[1] gate_4 gate_3 gate_5 ∧ 66 | DummyHash gate_5 Proof[2] fun gate_6 => 67 | DummyHash Proof[2] gate_5 fun gate_7 => 68 | ∃gate_8, Gates.select Path[2] gate_7 gate_6 gate_8 ∧ 69 | DummyHash gate_8 Proof[3] fun gate_9 => 70 | DummyHash Proof[3] gate_8 fun gate_10 => 71 | ∃gate_11, Gates.select Path[3] gate_10 gate_9 gate_11 ∧ 72 | DummyHash gate_11 Proof[4] fun gate_12 => 73 | DummyHash Proof[4] gate_11 fun gate_13 => 74 | ∃gate_14, Gates.select Path[4] gate_13 gate_12 gate_14 ∧ 75 | DummyHash gate_14 Proof[5] fun gate_15 => 76 | DummyHash Proof[5] gate_14 fun gate_16 => 77 | ∃gate_17, Gates.select Path[5] gate_16 gate_15 gate_17 ∧ 78 | DummyHash gate_17 Proof[6] fun gate_18 => 79 | DummyHash Proof[6] gate_17 fun gate_19 => 80 | ∃gate_20, Gates.select Path[6] gate_19 gate_18 gate_20 ∧ 81 | DummyHash gate_20 Proof[7] fun gate_21 => 82 | DummyHash Proof[7] gate_20 fun gate_22 => 83 | ∃gate_23, Gates.select Path[7] gate_22 gate_21 gate_23 ∧ 84 | DummyHash gate_23 Proof[8] fun gate_24 => 85 | DummyHash Proof[8] gate_23 fun gate_25 => 86 | ∃gate_26, Gates.select Path[8] gate_25 gate_24 gate_26 ∧ 87 | DummyHash gate_26 Proof[9] fun gate_27 => 88 | DummyHash Proof[9] gate_26 fun gate_28 => 89 | ∃gate_29, Gates.select Path[9] gate_28 gate_27 gate_29 ∧ 90 | DummyHash gate_29 Proof[10] fun gate_30 => 91 | DummyHash Proof[10] gate_29 fun gate_31 => 92 | ∃gate_32, Gates.select Path[10] gate_31 gate_30 gate_32 ∧ 93 | DummyHash gate_32 Proof[11] fun gate_33 => 94 | DummyHash Proof[11] gate_32 fun gate_34 => 95 | ∃gate_35, Gates.select Path[11] gate_34 gate_33 gate_35 ∧ 96 | DummyHash gate_35 Proof[12] fun gate_36 => 97 | DummyHash Proof[12] gate_35 fun gate_37 => 98 | ∃gate_38, Gates.select Path[12] gate_37 gate_36 gate_38 ∧ 99 | DummyHash gate_38 Proof[13] fun gate_39 => 100 | DummyHash Proof[13] gate_38 fun gate_40 => 101 | ∃gate_41, Gates.select Path[13] gate_40 gate_39 gate_41 ∧ 102 | DummyHash gate_41 Proof[14] fun gate_42 => 103 | DummyHash Proof[14] gate_41 fun gate_43 => 104 | ∃gate_44, Gates.select Path[14] gate_43 gate_42 gate_44 ∧ 105 | DummyHash gate_44 Proof[15] fun gate_45 => 106 | DummyHash Proof[15] gate_44 fun gate_46 => 107 | ∃gate_47, Gates.select Path[15] gate_46 gate_45 gate_47 ∧ 108 | DummyHash gate_47 Proof[16] fun gate_48 => 109 | DummyHash Proof[16] gate_47 fun gate_49 => 110 | ∃gate_50, Gates.select Path[16] gate_49 gate_48 gate_50 ∧ 111 | DummyHash gate_50 Proof[17] fun gate_51 => 112 | DummyHash Proof[17] gate_50 fun gate_52 => 113 | ∃gate_53, Gates.select Path[17] gate_52 gate_51 gate_53 ∧ 114 | DummyHash gate_53 Proof[18] fun gate_54 => 115 | DummyHash Proof[18] gate_53 fun gate_55 => 116 | ∃gate_56, Gates.select Path[18] gate_55 gate_54 gate_56 ∧ 117 | DummyHash gate_56 Proof[19] fun gate_57 => 118 | DummyHash Proof[19] gate_56 fun gate_58 => 119 | ∃gate_59, Gates.select Path[19] gate_58 gate_57 gate_59 ∧ 120 | Gates.eq gate_59 Root ∧ 121 | True 122 | 123 | def TwoGadgets_11 (In_1: F) (In_2: F): Prop := 124 | ∃gate_0, gate_0 = Gates.add In_1 In_2 ∧ 125 | ∃gate_1, gate_1 = Gates.mul In_1 In_2 ∧ 126 | MySecondWidget_11 gate_0 gate_1 ∧ 127 | True 128 | 129 | def TwoGadgets_6 (In_1: F) (In_2: F): Prop := 130 | ∃gate_0, gate_0 = Gates.add In_1 In_2 ∧ 131 | ∃gate_1, gate_1 = Gates.mul In_1 In_2 ∧ 132 | MySecondWidget_6 gate_0 gate_1 ∧ 133 | True 134 | 135 | end MultipleCircuits -------------------------------------------------------------------------------- /test/TestExtractGadgets.lean: -------------------------------------------------------------------------------- 1 | import ProvenZk.Gates 2 | import ProvenZk.Ext.Vector 3 | 4 | set_option linter.unusedVariables false 5 | 6 | namespace MultipleGadgets 7 | 8 | def Order : ℕ := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 9 | variable [Fact (Nat.Prime Order)] 10 | abbrev F := ZMod Order 11 | abbrev Gates := GatesGnark9 Order 12 | 13 | def DummyHash (In_1: F) (In_2: F) (k: F -> Prop): Prop := 14 | ∃gate_0, gate_0 = Gates.mul In_1 In_2 ∧ 15 | k gate_0 16 | 17 | def MyWidget_11 (Test_1: F) (Test_2: F) (k: F -> Prop): Prop := 18 | ∃gate_0, gate_0 = Gates.add Test_1 Test_2 ∧ 19 | ∃gate_1, gate_1 = Gates.mul Test_1 Test_2 ∧ 20 | ∃gate_2, Gates.div gate_0 gate_1 gate_2 ∧ 21 | Gates.is_bool (11:F) ∧ 22 | k gate_2 23 | 24 | def MySecondWidget_11 (Test_1: F) (Test_2: F) : Prop := 25 | ∃gate_0, gate_0 = Gates.mul Test_1 Test_2 ∧ 26 | MyWidget_11 Test_1 Test_2 fun gate_1 => 27 | ∃_ignored_, _ignored_ = Gates.mul gate_0 gate_1 ∧ 28 | True 29 | 30 | def MyWidget_9 (Test_1: F) (Test_2: F) (k: F -> Prop): Prop := 31 | ∃gate_0, gate_0 = Gates.add Test_1 Test_2 ∧ 32 | ∃gate_1, gate_1 = Gates.mul Test_1 Test_2 ∧ 33 | ∃gate_2, Gates.div gate_0 gate_1 gate_2 ∧ 34 | Gates.is_bool (9:F) ∧ 35 | k gate_2 36 | 37 | def MySecondWidget_9 (Test_1: F) (Test_2: F) : Prop := 38 | ∃gate_0, gate_0 = Gates.mul Test_1 Test_2 ∧ 39 | MyWidget_9 Test_1 Test_2 fun gate_1 => 40 | ∃_ignored_, _ignored_ = Gates.mul gate_0 gate_1 ∧ 41 | True 42 | 43 | end MultipleGadgets -------------------------------------------------------------------------------- /test/TestExtractGadgetsVectors.lean: -------------------------------------------------------------------------------- 1 | import ProvenZk.Gates 2 | import ProvenZk.Ext.Vector 3 | 4 | set_option linter.unusedVariables false 5 | 6 | namespace MultipleGadgetsVectors 7 | 8 | def Order : ℕ := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 9 | variable [Fact (Nat.Prime Order)] 10 | abbrev F := ZMod Order 11 | abbrev Gates := GatesGnark9 Order 12 | 13 | def VectorGadget_3_3_3_3 (In_1: Vector F 3) (In_2: Vector F 3) (Nested: Vector (Vector F 3) 3) (k: Vector F 3 -> Prop): Prop := 14 | ∃_ignored_, _ignored_ = Gates.mul In_1[0] In_2[0] ∧ 15 | ∃_ignored_, _ignored_ = Gates.mul In_1[1] In_2[1] ∧ 16 | ∃gate_2, gate_2 = Gates.mul In_1[2] In_2[2] ∧ 17 | k vec![gate_2, gate_2, gate_2] 18 | 19 | def ReturnItself_3_3 (In_1: Vector F 3) (Out: Vector F 3) (k: Vector F 3 -> Prop): Prop := 20 | ∃gate_0, gate_0 = Gates.mul In_1[0] In_1[0] ∧ 21 | ∃gate_1, gate_1 = Gates.mul In_1[1] In_1[1] ∧ 22 | ∃gate_2, gate_2 = Gates.mul In_1[2] In_1[2] ∧ 23 | k vec![gate_0, gate_1, gate_2] 24 | 25 | def OptimisedVectorGadget (In: F) (k: Vector F 3 -> Prop): Prop := 26 | ∃gate_0, Gates.to_binary In 3 gate_0 ∧ 27 | k gate_0 28 | 29 | end MultipleGadgetsVectors -------------------------------------------------------------------------------- /test/TestGadgetExtraction.lean: -------------------------------------------------------------------------------- 1 | import ProvenZk.Gates 2 | import ProvenZk.Ext.Vector 3 | 4 | set_option linter.unusedVariables false 5 | 6 | namespace VectorGadget 7 | 8 | def Order : ℕ := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 9 | variable [Fact (Nat.Prime Order)] 10 | abbrev F := ZMod Order 11 | abbrev Gates := GatesGnark9 Order 12 | 13 | def VectorGadget_3_3_3_3 (In_1: Vector F 3) (In_2: Vector F 3) (Nested: Vector (Vector F 3) 3) (k: Vector F 3 -> Prop): Prop := 14 | ∃_ignored_, _ignored_ = Gates.mul In_1[0] In_2[0] ∧ 15 | ∃_ignored_, _ignored_ = Gates.mul In_1[1] In_2[1] ∧ 16 | ∃gate_2, gate_2 = Gates.mul In_1[2] In_2[2] ∧ 17 | k vec![gate_2, gate_2, gate_2] 18 | 19 | end VectorGadget -------------------------------------------------------------------------------- /test/TestMerkleRecover.lean: -------------------------------------------------------------------------------- 1 | import ProvenZk.Gates 2 | import ProvenZk.Ext.Vector 3 | 4 | set_option linter.unusedVariables false 5 | 6 | namespace MerkleRecover 7 | 8 | def Order : ℕ := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 9 | variable [Fact (Nat.Prime Order)] 10 | abbrev F := ZMod Order 11 | abbrev Gates := GatesGnark9 Order 12 | 13 | def DummyHash (In_1: F) (In_2: F) (k: F -> Prop): Prop := 14 | ∃gate_0, gate_0 = Gates.mul In_1 In_2 ∧ 15 | k gate_0 16 | 17 | def circuit (Root: F) (Element: F) (Path: Vector F 20) (Proof: Vector F 20): Prop := 18 | DummyHash Element Proof[0] fun gate_0 => 19 | DummyHash Proof[0] Element fun gate_1 => 20 | ∃gate_2, Gates.select Path[0] gate_1 gate_0 gate_2 ∧ 21 | DummyHash gate_2 Proof[1] fun gate_3 => 22 | DummyHash Proof[1] gate_2 fun gate_4 => 23 | ∃gate_5, Gates.select Path[1] gate_4 gate_3 gate_5 ∧ 24 | DummyHash gate_5 Proof[2] fun gate_6 => 25 | DummyHash Proof[2] gate_5 fun gate_7 => 26 | ∃gate_8, Gates.select Path[2] gate_7 gate_6 gate_8 ∧ 27 | DummyHash gate_8 Proof[3] fun gate_9 => 28 | DummyHash Proof[3] gate_8 fun gate_10 => 29 | ∃gate_11, Gates.select Path[3] gate_10 gate_9 gate_11 ∧ 30 | DummyHash gate_11 Proof[4] fun gate_12 => 31 | DummyHash Proof[4] gate_11 fun gate_13 => 32 | ∃gate_14, Gates.select Path[4] gate_13 gate_12 gate_14 ∧ 33 | DummyHash gate_14 Proof[5] fun gate_15 => 34 | DummyHash Proof[5] gate_14 fun gate_16 => 35 | ∃gate_17, Gates.select Path[5] gate_16 gate_15 gate_17 ∧ 36 | DummyHash gate_17 Proof[6] fun gate_18 => 37 | DummyHash Proof[6] gate_17 fun gate_19 => 38 | ∃gate_20, Gates.select Path[6] gate_19 gate_18 gate_20 ∧ 39 | DummyHash gate_20 Proof[7] fun gate_21 => 40 | DummyHash Proof[7] gate_20 fun gate_22 => 41 | ∃gate_23, Gates.select Path[7] gate_22 gate_21 gate_23 ∧ 42 | DummyHash gate_23 Proof[8] fun gate_24 => 43 | DummyHash Proof[8] gate_23 fun gate_25 => 44 | ∃gate_26, Gates.select Path[8] gate_25 gate_24 gate_26 ∧ 45 | DummyHash gate_26 Proof[9] fun gate_27 => 46 | DummyHash Proof[9] gate_26 fun gate_28 => 47 | ∃gate_29, Gates.select Path[9] gate_28 gate_27 gate_29 ∧ 48 | DummyHash gate_29 Proof[10] fun gate_30 => 49 | DummyHash Proof[10] gate_29 fun gate_31 => 50 | ∃gate_32, Gates.select Path[10] gate_31 gate_30 gate_32 ∧ 51 | DummyHash gate_32 Proof[11] fun gate_33 => 52 | DummyHash Proof[11] gate_32 fun gate_34 => 53 | ∃gate_35, Gates.select Path[11] gate_34 gate_33 gate_35 ∧ 54 | DummyHash gate_35 Proof[12] fun gate_36 => 55 | DummyHash Proof[12] gate_35 fun gate_37 => 56 | ∃gate_38, Gates.select Path[12] gate_37 gate_36 gate_38 ∧ 57 | DummyHash gate_38 Proof[13] fun gate_39 => 58 | DummyHash Proof[13] gate_38 fun gate_40 => 59 | ∃gate_41, Gates.select Path[13] gate_40 gate_39 gate_41 ∧ 60 | DummyHash gate_41 Proof[14] fun gate_42 => 61 | DummyHash Proof[14] gate_41 fun gate_43 => 62 | ∃gate_44, Gates.select Path[14] gate_43 gate_42 gate_44 ∧ 63 | DummyHash gate_44 Proof[15] fun gate_45 => 64 | DummyHash Proof[15] gate_44 fun gate_46 => 65 | ∃gate_47, Gates.select Path[15] gate_46 gate_45 gate_47 ∧ 66 | DummyHash gate_47 Proof[16] fun gate_48 => 67 | DummyHash Proof[16] gate_47 fun gate_49 => 68 | ∃gate_50, Gates.select Path[16] gate_49 gate_48 gate_50 ∧ 69 | DummyHash gate_50 Proof[17] fun gate_51 => 70 | DummyHash Proof[17] gate_50 fun gate_52 => 71 | ∃gate_53, Gates.select Path[17] gate_52 gate_51 gate_53 ∧ 72 | DummyHash gate_53 Proof[18] fun gate_54 => 73 | DummyHash Proof[18] gate_53 fun gate_55 => 74 | ∃gate_56, Gates.select Path[18] gate_55 gate_54 gate_56 ∧ 75 | DummyHash gate_56 Proof[19] fun gate_57 => 76 | DummyHash Proof[19] gate_56 fun gate_58 => 77 | ∃gate_59, Gates.select Path[19] gate_58 gate_57 gate_59 ∧ 78 | Gates.eq gate_59 Root ∧ 79 | True 80 | 81 | end MerkleRecover -------------------------------------------------------------------------------- /test/TestMyCircuit.lean: -------------------------------------------------------------------------------- 1 | import ProvenZk.Gates 2 | import ProvenZk.Ext.Vector 3 | 4 | set_option linter.unusedVariables false 5 | 6 | namespace MyCircuit 7 | 8 | def Order : ℕ := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 9 | variable [Fact (Nat.Prime Order)] 10 | abbrev F := ZMod Order 11 | abbrev Gates := GatesGnark9 Order 12 | 13 | 14 | 15 | def circuit (In_1: F) (In_2: F) (Out: F): Prop := 16 | ∃gate_0, gate_0 = Gates.add In_1 In_2 ∧ 17 | Gates.eq gate_0 Out ∧ 18 | True 19 | 20 | end MyCircuit -------------------------------------------------------------------------------- /test/TestSlicesOptimisation.lean: -------------------------------------------------------------------------------- 1 | import ProvenZk.Gates 2 | import ProvenZk.Ext.Vector 3 | 4 | set_option linter.unusedVariables false 5 | 6 | namespace SlicesOptimisation 7 | 8 | def Order : ℕ := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 9 | variable [Fact (Nat.Prime Order)] 10 | abbrev F := ZMod Order 11 | abbrev Gates := GatesGnark9 Order 12 | 13 | def SlicesGadget_3_2_4_3_2 (TwoDim: Vector (Vector F 3) 2) (ThreeDim: Vector (Vector (Vector F 4) 3) 2) (k: Vector F 7 -> Prop): Prop := 14 | k vec![ThreeDim[0][0][0], ThreeDim[0][0][1], ThreeDim[0][0][2], ThreeDim[0][0][3], TwoDim[0][0], TwoDim[0][1], TwoDim[0][2]] 15 | 16 | def SlicesGadget_1_2_4_3_3 (TwoDim: Vector (Vector F 1) 2) (ThreeDim: Vector (Vector (Vector F 4) 3) 3) (k: Vector F 5 -> Prop): Prop := 17 | k vec![ThreeDim[0][0][0], ThreeDim[0][0][1], ThreeDim[0][0][2], ThreeDim[0][0][3], TwoDim[0][0]] 18 | 19 | def TwoSlices_3_2 (TwoDim: Vector (Vector F 3) 2) (k: Vector (Vector F 3) 2 -> Prop): Prop := 20 | k TwoDim 21 | 22 | def ThreeSlices_4_3_2 (ThreeDim: Vector (Vector (Vector F 4) 3) 2) (k: Vector (Vector (Vector F 4) 3) 2 -> Prop): Prop := 23 | k ThreeDim 24 | 25 | def circuit (Test: F) (Id: Vector F 3) (TwoDim: Vector (Vector F 3) 2) (ThreeDim: Vector (Vector (Vector F 4) 3) 2): Prop := 26 | SlicesGadget_3_2_4_3_2 TwoDim ThreeDim fun _ => 27 | SlicesGadget_3_2_4_3_2 vec![TwoDim[1], TwoDim[0]] vec![vec![ThreeDim[1][0], ThreeDim[1][1], ThreeDim[1][2]], vec![ThreeDim[0][0], ThreeDim[0][1], ThreeDim[0][2]]] fun _ => 28 | SlicesGadget_1_2_4_3_3 vec![vec![TwoDim[1][1]], vec![TwoDim[1][0]]] vec![vec![ThreeDim[1][0], ThreeDim[1][1], ThreeDim[1][2]], vec![ThreeDim[0][0], ThreeDim[0][1], ThreeDim[0][2]], vec![ThreeDim[1][0], ThreeDim[1][1], ThreeDim[1][2]]] fun _ => 29 | SlicesGadget_3_2_4_3_2 vec![TwoDim[1], vec![TwoDim[1][0], TwoDim[0][0], TwoDim[1][1]]] ThreeDim fun _ => 30 | TwoSlices_3_2 TwoDim fun _ => 31 | ThreeSlices_4_3_2 ThreeDim fun gate_5 => 32 | ThreeSlices_4_3_2 gate_5 fun gate_6 => 33 | ThreeSlices_4_3_2 gate_6 fun _ => 34 | True 35 | 36 | end SlicesOptimisation -------------------------------------------------------------------------------- /test/TestToBinaryCircuit.lean: -------------------------------------------------------------------------------- 1 | import ProvenZk.Gates 2 | import ProvenZk.Ext.Vector 3 | 4 | set_option linter.unusedVariables false 5 | 6 | namespace ToBinaryCircuit 7 | 8 | def Order : ℕ := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 9 | variable [Fact (Nat.Prime Order)] 10 | abbrev F := ZMod Order 11 | abbrev Gates := GatesGnark9 Order 12 | 13 | def VectorGadget_3_3_3_3 (In_1: Vector F 3) (In_2: Vector F 3) (Nested: Vector (Vector F 3) 3) (k: Vector F 3 -> Prop): Prop := 14 | ∃_ignored_, _ignored_ = Gates.mul In_1[0] In_2[0] ∧ 15 | ∃_ignored_, _ignored_ = Gates.mul In_1[1] In_2[1] ∧ 16 | ∃gate_2, gate_2 = Gates.mul In_1[2] In_2[2] ∧ 17 | k vec![gate_2, gate_2, gate_2] 18 | 19 | def circuit (In: F) (Out: F) (Double: Vector (Vector F 3) 3): Prop := 20 | ∃gate_0, Gates.to_binary In 3 gate_0 ∧ 21 | ∃gate_1, Gates.to_binary Out 3 gate_1 ∧ 22 | ∃_ignored_, _ignored_ = Gates.add Double[2][2] Double[1][1] ∧ 23 | ∃_ignored_, _ignored_ = Gates.add _ignored_ Double[0][0] ∧ 24 | ∃_ignored_, _ignored_ = Gates.mul gate_0[1] gate_1[1] ∧ 25 | VectorGadget_3_3_3_3 Double[2] Double[0] Double fun gate_4 => 26 | ∃_ignored_, _ignored_ = Gates.mul gate_4[2] gate_4[1] ∧ 27 | True 28 | 29 | end ToBinaryCircuit -------------------------------------------------------------------------------- /test/TestTwoGadgets.lean: -------------------------------------------------------------------------------- 1 | import ProvenZk.Gates 2 | import ProvenZk.Ext.Vector 3 | 4 | set_option linter.unusedVariables false 5 | 6 | namespace TwoGadgets 7 | 8 | def Order : ℕ := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 9 | variable [Fact (Nat.Prime Order)] 10 | abbrev F := ZMod Order 11 | abbrev Gates := GatesGnark9 Order 12 | 13 | def MyWidget_11 (Test_1: F) (Test_2: F) (k: F -> Prop): Prop := 14 | ∃gate_0, gate_0 = Gates.add Test_1 Test_2 ∧ 15 | ∃gate_1, gate_1 = Gates.mul Test_1 Test_2 ∧ 16 | ∃gate_2, Gates.div gate_0 gate_1 gate_2 ∧ 17 | Gates.is_bool (11:F) ∧ 18 | k gate_2 19 | 20 | def MySecondWidget_11 (Test_1: F) (Test_2: F) : Prop := 21 | ∃gate_0, gate_0 = Gates.mul Test_1 Test_2 ∧ 22 | MyWidget_11 Test_1 Test_2 fun gate_1 => 23 | ∃_ignored_, _ignored_ = Gates.mul gate_0 gate_1 ∧ 24 | True 25 | 26 | def circuit (In_1: F) (In_2: F): Prop := 27 | ∃gate_0, gate_0 = Gates.add In_1 In_2 ∧ 28 | ∃gate_1, gate_1 = Gates.mul In_1 In_2 ∧ 29 | MySecondWidget_11 gate_0 gate_1 ∧ 30 | True 31 | 32 | end TwoGadgets --------------------------------------------------------------------------------