├── .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
--------------------------------------------------------------------------------