├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yml │ └── linkify_changelog.yml ├── .gitignore ├── AUTHORS ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── bench.rs ├── diagram ├── .gitignore ├── diagram.pdf └── diagram.tex ├── scripts └── linkify_changelog.py └── src ├── ahp ├── constraint_systems.rs ├── indexer.rs ├── mod.rs ├── prover.rs └── verifier.rs ├── data_structures.rs ├── error.rs ├── lib.rs ├── rng.rs └── test.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us squash bugs! 4 | 5 | --- 6 | 7 | ∂ 12 | 13 | ## Summary of Bug 14 | 15 | 16 | 17 | ## Version 18 | 19 | 20 | 21 | ## Steps to Reproduce 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Create a proposal to request a feature 4 | 5 | --- 6 | 7 | 13 | 14 | ## Summary 15 | 16 | 17 | 18 | ## Problem Definition 19 | 20 | 23 | 24 | ## Proposal 25 | 26 | 27 | 28 | ____ 29 | 30 | #### For Admin Use 31 | 32 | - [ ] Not duplicate issue 33 | - [ ] Appropriate labels applied 34 | - [ ] Appropriate contributors tagged 35 | - [ ] Contributor assigned/self-assigned 36 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ## Description 8 | 9 | 12 | 13 | closes: #XXXX 14 | 15 | --- 16 | 17 | Before we can merge this PR, please make sure that all the following items have been 18 | checked off. If any of the checklist items are not applicable, please leave them but 19 | write a little note why. 20 | 21 | - [ ] Targeted PR against correct branch (master) 22 | - [ ] Linked to Github issue with discussion and accepted design OR have an explanation in the PR that describes this work. 23 | - [ ] Wrote unit tests 24 | - [ ] Updated relevant documentation in the code 25 | - [ ] Added a relevant changelog entry to the `Pending` section in `CHANGELOG.md` 26 | - [ ] Re-reviewed `Files changed` in the Github PR explorer 27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | env: 8 | RUST_BACKTRACE: 1 9 | 10 | jobs: 11 | style: 12 | name: Check Style 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Checkout 17 | uses: actions/checkout@v1 18 | - name: Install Rust 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: stable 23 | override: true 24 | components: rustfmt 25 | 26 | - name: cargo fmt --check 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: fmt 30 | args: --all -- --check 31 | 32 | test: 33 | name: Test 34 | runs-on: ubuntu-latest 35 | env: 36 | RUSTFLAGS: -Dwarnings 37 | strategy: 38 | matrix: 39 | rust: 40 | - stable 41 | - nightly 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@v2 45 | 46 | - name: Install Rust (${{ matrix.rust }}) 47 | uses: actions-rs/toolchain@v1 48 | with: 49 | profile: minimal 50 | toolchain: ${{ matrix.rust }} 51 | override: true 52 | 53 | - name: Check examples 54 | uses: actions-rs/cargo@v1 55 | with: 56 | command: check 57 | args: --examples --all 58 | 59 | - name: Check examples with all features on stable 60 | uses: actions-rs/cargo@v1 61 | with: 62 | command: check 63 | args: --examples --all-features --all 64 | if: matrix.rust == 'stable' 65 | 66 | - name: Check benchmarks on nightly 67 | uses: actions-rs/cargo@v1 68 | with: 69 | command: check 70 | args: --all-features --examples --all --benches 71 | if: matrix.rust == 'nightly' 72 | 73 | - name: Test 74 | uses: actions-rs/cargo@v1 75 | with: 76 | command: test 77 | args: --release 78 | 79 | check_no_std: 80 | name: Check no_std 81 | runs-on: ubuntu-latest 82 | steps: 83 | - name: Checkout 84 | uses: actions/checkout@v2 85 | 86 | - name: Install Rust (${{ matrix.rust }}) 87 | uses: actions-rs/toolchain@v1 88 | with: 89 | toolchain: stable 90 | target: aarch64-unknown-none 91 | override: true 92 | 93 | - name: Build 94 | uses: actions-rs/cargo@v1 95 | with: 96 | use-cross: true 97 | command: build 98 | args: --no-default-features --target aarch64-unknown-none 99 | 100 | - name: Check 101 | uses: actions-rs/cargo@v1 102 | with: 103 | use-cross: true 104 | command: check 105 | args: --examples --no-default-features --target aarch64-unknown-none 106 | -------------------------------------------------------------------------------- /.github/workflows/linkify_changelog.yml: -------------------------------------------------------------------------------- 1 | name: Linkify Changelog 2 | 3 | on: 4 | workflow_dispatch 5 | 6 | jobs: 7 | linkify: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | - name: Add links 13 | run: python3 scripts/linkify_changelog.py CHANGELOG.md 14 | - name: Commit 15 | run: | 16 | git config user.name github-actions 17 | git config user.email github-actions@github.com 18 | git add . 19 | git commit -m "Linkify Changelog" 20 | git push -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .DS_Store 4 | .idea 5 | *.iml 6 | *.ipynb_checkpoints 7 | *.pyc 8 | *.sage.py 9 | params 10 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Alessandro Chiesa 2 | Yuncong Hu 3 | Mary Maller 4 | Pratyush Mishra 5 | Psi Vesely 6 | Nicholas Ward 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## Pending 4 | 5 | ### Breaking changes 6 | 7 | - [\#86](https://github.com/arkworks-rs/marlin/pull/86) Unify the interface for Fiat-Shamir transform. 8 | 9 | ### Features 10 | 11 | ### Improvements 12 | 13 | - [\#71](https://github.com/arkworks-rs/marlin/pull/71) Remove an unnecessary clone of the full witness. 14 | 15 | ### Bug fixes 16 | 17 | ## v0.3.0 18 | 19 | - Change dependency to version `0.3.0` of other arkworks-rs crates. 20 | 21 | ## v0.2.0 22 | 23 | ### Features 24 | 25 | - [\#47](https://github.com/arkworks-rs/marlin/pull/47) Automatically pad input to be of length 2^k, so constraint writers can have a public input of any size 26 | - [\#51](https://github.com/arkworks-rs/marlin/pull/51) Implement CanonicalSerialize for Marlin's proofs. 27 | - [\#54](https://github.com/arkworks-rs/marlin/pull/54) Implement CanonicalSerialize for Marlin's Index and Index Verification Key. 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ark-marlin" 3 | version = "0.3.0" 4 | authors = [ 5 | "Alessandro Chiesa ", 6 | "Mary Maller ", 7 | "Yuncong Hu ", 8 | "Pratyush Mishra ", 9 | "Psi Vesely ", 10 | "Nicholas Ward ", 11 | "arkworks contributors" 12 | ] 13 | description = "A library for the Marlin preprocessing zkSNARK" 14 | repository = "https://github.com/arkworks-rs/marlin" 15 | documentation = "https://docs.rs/ark-marlin/" 16 | keywords = ["cryptography", "commitments", "zkSNARK"] 17 | categories = ["cryptography"] 18 | include = ["Cargo.toml", "src", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] 19 | license = "MIT/Apache-2.0" 20 | edition = "2018" 21 | 22 | [dependencies] 23 | ark-serialize = { version = "^0.3.0", default-features = false, features = [ "derive" ] } 24 | ark-ff = { version = "^0.3.0", default-features = false } 25 | ark-std = { version = "^0.3.0", default-features = false } 26 | ark-poly = { version = "^0.3.0", default-features = false } 27 | ark-relations = { version = "^0.3.0", default-features = false } 28 | ark-poly-commit = { version = "^0.3.0", default-features = false } 29 | 30 | rayon = { version = "1", optional = true } 31 | digest = { version = "0.9" } 32 | derivative = { version = "2", features = ["use_core"] } 33 | 34 | [dev-dependencies] 35 | rand_chacha = { version = "0.3.0", default-features = false } 36 | blake2 = { version = "0.9", default-features = false } 37 | ark-bls12-381 = { version = "^0.3.0", default-features = false, features = [ "curve" ] } 38 | ark-mnt4-298 = { version = "^0.3.0", default-features = false, features = ["r1cs", "curve"] } 39 | ark-mnt6-298 = { version = "^0.3.0", default-features = false, features = ["r1cs"] } 40 | ark-mnt4-753 = { version = "^0.3.0", default-features = false, features = ["r1cs", "curve"] } 41 | ark-mnt6-753 = { version = "^0.3.0", default-features = false, features = ["r1cs"] } 42 | 43 | [profile.release] 44 | opt-level = 3 45 | lto = "thin" 46 | incremental = true 47 | debug = true 48 | panic = 'abort' 49 | 50 | [profile.test] 51 | opt-level = 3 52 | debug-assertions = true 53 | incremental = true 54 | debug = true 55 | 56 | [profile.dev] 57 | opt-level = 0 58 | panic = 'abort' 59 | 60 | [features] 61 | default = ["std", "parallel"] 62 | std = [ "ark-ff/std", "ark-poly/std", "ark-relations/std", "ark-std/std", "ark-serialize/std", "ark-poly-commit/std" ] 63 | print-trace = [ "ark-std/print-trace" ] 64 | parallel = [ "std", "ark-ff/parallel", "ark-poly/parallel", "ark-std/parallel", "ark-poly-commit/parallel", "rayon" ] 65 | 66 | [[bench]] 67 | name = "marlin-benches" 68 | path = "benches/bench.rs" 69 | harness = false 70 | required-features = ["std"] 71 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Marlin

2 | 3 |

4 | 5 | 6 |

7 | 8 | 9 | `marlin` is a Rust library that implements a 10 |

11 | preprocessing zkSNARK for R1CS
12 | with
13 | universal and updatable SRS 14 |

15 | 16 | This library was initially developed as part of the [Marlin paper][marlin], and is released under the MIT License and the Apache v2 License (see [License](#license)). 17 | 18 | **WARNING:** This is an academic prototype, and in particular has not received careful code review. This implementation is NOT ready for production use. 19 | 20 | ## Overview 21 | 22 | A zkSNARK with **preprocessing** achieves succinct verification for arbitrary computations, as opposed to only for structured computations. Informally, in an offline phase, one can preprocess the desired computation to produce a short summary of it; subsequently, in an online phase, this summary can be used to check any number of arguments relative to this computation. 23 | 24 | The preprocessing zkSNARKs in this library rely on a structured reference string (SRS), which contains system parameters required by the argument system to produce/validate arguments. The SRS in this library is **universal**, which means that it supports (deterministically) preprocessing any computation up to a given size bound. The SRS is also **updatable**, which means that anyone can contribute a fresh share of randomness to it, which facilitates deployments in the real world. 25 | 26 | The construction in this library follows the methodology introduced in the [Marlin paper][marlin], which obtains preprocessing zkSNARKs with universal and updatable SRS by combining two ingredients: 27 | 28 | * an **algebraic holographic proof** 29 | * a **polynomial commitment scheme** 30 | 31 | The first ingredient is provided as part of this library, and is an efficient algebraic holographic proof for R1CS (a generalization of arithmetic circuit satisfiability supported by many argument systems). The second ingredient is imported from [`poly-commit`](https://github.com/arkworks-rs/poly-commit). See below for evaluation details. 32 | 33 | ## Build guide 34 | 35 | The library compiles on the `stable` toolchain of the Rust compiler. To install the latest version of Rust, first install `rustup` by following the instructions [here](https://rustup.rs/), or via your platform's package manager. Once `rustup` is installed, install the Rust toolchain by invoking: 36 | ```bash 37 | rustup install stable 38 | ``` 39 | 40 | After that, use `cargo` (the standard Rust build tool) to build the library: 41 | ```bash 42 | git clone https://github.com/arkworks-rs/marlin.git 43 | cd marlin 44 | cargo build --release 45 | ``` 46 | 47 | This library comes with some unit and integration tests. Run these tests with: 48 | ```bash 49 | cargo test 50 | ``` 51 | 52 | Lastly, this library is instrumented with profiling infrastructure that prints detailed traces of execution time. To enable this, compile with `cargo build --features print-trace`. 53 | 54 | 55 | ## Benchmarks 56 | 57 | All benchmarks below are performed over the BLS12-381 curve implemented in the [`ark-bls12-381`](https://github.com/arkworks-rs/curves/) library, with the `asm` feature activated. Benchmarks were run on a machine with an Intel Xeon 6136 CPU running at 3.0 GHz. 58 | 59 | 60 | ### Running time compared to Groth16 61 | 62 | The graphs below compare the running time, in single-thread execution, of Marlin's indexer, prover, and verifier algorithms with the corresponding algorithms of [Groth16][groth16] (the state of the art in preprocessing zkSNARKs for R1CS with circuit-specific SRS) as implemented in [`groth16`](https://github.com/arkworks-rs/groth16). We evaluate Marlin's algorithms when instantiated with the PC scheme from [[CHMMVW20]][marlin] (denoted "M-AHP w/ PC of [[CHMMVW20]][marlin]"), and the PC scheme from [[MBKM19]][sonic] (denoted "M-AHP w/ PC of [[MBKM19]][sonic]"). 63 | 64 |

65 | Indexer 66 | Prover 67 |

68 |

69 | Verifier 70 |

71 | 72 | ### Multi-threaded performance 73 | 74 | The following graphs compare the running time of Marlin's prover when instantiated with the PC scheme from [[CHMMVW20]][marlin] (left) and the PC scheme from [[MBKM19]][sonic] (right) when executed with a different number of threads. 75 | 76 |

77 | Multi-threaded scaling of Marlin AHP with the PC scheme from [CHMMVW20] 78 | Multi-threaded scaling of Marlin AHP with the PC scheme from [MBKM19] 79 |

80 | 81 | ### Proof size 82 | 83 | We compare the proof size of Marlin with that of [Groth16][groth16]. We instantiate the Marlin SNARK with the PC scheme from [[CHMMVW20]][marlin], and the PC scheme from [[MBKM19]][sonic]. 84 | 85 | | Scheme | Proof size in bytes | 86 | |:------------------------------------------:|:---------------------:| 87 | | Marlin AHP with PC of [[CHMMVW20]][marlin] | 880 | 88 | | Marlin AHP with PC of [[MBKM19]][sonic] | 784 | 89 | | [\[Groth16\]][groth16] | 192 | 90 | 91 | 92 | ## License 93 | 94 | This library is licensed under either of the following licenses, at your discretion. 95 | 96 | * [Apache License Version 2.0](LICENSE-APACHE) 97 | * [MIT License](LICENSE-MIT) 98 | 99 | Unless you explicitly state otherwise, any contribution that you submit to this library shall be dual licensed as above (as defined in the Apache v2 License), without any additional terms or conditions. 100 | 101 | [marlin]: https://ia.cr/2019/1047 102 | [sonic]: https://ia.cr/2019/099 103 | [groth16]: https://ia.cr/2016/260 104 | 105 | ## Reference paper 106 | 107 | [Marlin: Preprocessing zkSNARKs with Universal and Updatable SRS][marlin] 108 | Alessandro Chiesa, Yuncong Hu, Mary Maller, [Pratyush Mishra](https://www.github.com/pratyush), [Psi Vesely](https://github.com/psivesely), [Nicholas Ward](https://www.github.com/npwardberkeley) 109 | EUROCRYPT 2020 110 | 111 | ## Acknowledgements 112 | 113 | This work was supported by: an Engineering and Physical Sciences Research Council grant; a Google Faculty Award; the RISELab at UC Berkeley; and donations from the Ethereum Foundation and the Interchain Foundation. 114 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | // For benchmark, run: 2 | // RAYON_NUM_THREADS=N cargo bench --no-default-features --features "std parallel" -- --nocapture 3 | // where N is the number of threads you want to use (N = 1 for single-thread). 4 | 5 | use ark_bls12_381::{Bls12_381, Fr as BlsFr}; 6 | use ark_ff::PrimeField; 7 | use ark_marlin::{Marlin, SimpleHashFiatShamirRng}; 8 | use ark_mnt4_298::{Fr as MNT4Fr, MNT4_298}; 9 | use ark_mnt4_753::{Fr as MNT4BigFr, MNT4_753}; 10 | use ark_mnt6_298::{Fr as MNT6Fr, MNT6_298}; 11 | use ark_mnt6_753::{Fr as MNT6BigFr, MNT6_753}; 12 | use ark_poly::univariate::DensePolynomial; 13 | use ark_poly_commit::sonic_pc::SonicKZG10; 14 | use ark_relations::{ 15 | lc, 16 | r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}, 17 | }; 18 | use ark_std::{ops::Mul, UniformRand}; 19 | use blake2::Blake2s; 20 | use rand_chacha::ChaChaRng; 21 | 22 | const NUM_PROVE_REPEATITIONS: usize = 10; 23 | const NUM_VERIFY_REPEATITIONS: usize = 50; 24 | 25 | #[derive(Copy)] 26 | struct DummyCircuit { 27 | pub a: Option, 28 | pub b: Option, 29 | pub num_variables: usize, 30 | pub num_constraints: usize, 31 | } 32 | 33 | impl Clone for DummyCircuit { 34 | fn clone(&self) -> Self { 35 | DummyCircuit { 36 | a: self.a.clone(), 37 | b: self.b.clone(), 38 | num_variables: self.num_variables.clone(), 39 | num_constraints: self.num_constraints.clone(), 40 | } 41 | } 42 | } 43 | 44 | impl ConstraintSynthesizer for DummyCircuit { 45 | fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { 46 | let a = cs.new_witness_variable(|| self.a.ok_or(SynthesisError::AssignmentMissing))?; 47 | let b = cs.new_witness_variable(|| self.b.ok_or(SynthesisError::AssignmentMissing))?; 48 | let c = cs.new_input_variable(|| { 49 | let a = self.a.ok_or(SynthesisError::AssignmentMissing)?; 50 | let b = self.b.ok_or(SynthesisError::AssignmentMissing)?; 51 | 52 | Ok(a * b) 53 | })?; 54 | 55 | for _ in 0..(self.num_variables - 3) { 56 | let _ = cs.new_witness_variable(|| self.a.ok_or(SynthesisError::AssignmentMissing))?; 57 | } 58 | 59 | for _ in 0..self.num_constraints - 1 { 60 | cs.enforce_constraint(lc!() + a, lc!() + b, lc!() + c)?; 61 | } 62 | 63 | cs.enforce_constraint(lc!(), lc!(), lc!())?; 64 | 65 | Ok(()) 66 | } 67 | } 68 | 69 | macro_rules! marlin_prove_bench { 70 | ($bench_name:ident, $bench_field:ty, $bench_pairing_engine:ty) => { 71 | let rng = &mut ark_std::test_rng(); 72 | let c = DummyCircuit::<$bench_field> { 73 | a: Some(<$bench_field>::rand(rng)), 74 | b: Some(<$bench_field>::rand(rng)), 75 | num_variables: 10, 76 | num_constraints: 65536, 77 | }; 78 | 79 | let srs = Marlin::< 80 | $bench_field, 81 | SonicKZG10<$bench_pairing_engine, DensePolynomial<$bench_field>>, 82 | SimpleHashFiatShamirRng, 83 | >::universal_setup(65536, 65536, 3 * 65536, rng) 84 | .unwrap(); 85 | let (pk, _) = Marlin::< 86 | $bench_field, 87 | SonicKZG10<$bench_pairing_engine, DensePolynomial<$bench_field>>, 88 | SimpleHashFiatShamirRng, 89 | >::index(&srs, c) 90 | .unwrap(); 91 | 92 | let start = ark_std::time::Instant::now(); 93 | 94 | for _ in 0..NUM_PROVE_REPEATITIONS { 95 | let _ = Marlin::< 96 | $bench_field, 97 | SonicKZG10<$bench_pairing_engine, DensePolynomial<$bench_field>>, 98 | SimpleHashFiatShamirRng, 99 | >::prove(&pk, c.clone(), rng) 100 | .unwrap(); 101 | } 102 | 103 | println!( 104 | "per-constraint proving time for {}: {} ns/constraint", 105 | stringify!($bench_pairing_engine), 106 | start.elapsed().as_nanos() / NUM_PROVE_REPEATITIONS as u128 / 65536u128 107 | ); 108 | }; 109 | } 110 | 111 | macro_rules! marlin_verify_bench { 112 | ($bench_name:ident, $bench_field:ty, $bench_pairing_engine:ty) => { 113 | let rng = &mut ark_std::test_rng(); 114 | let c = DummyCircuit::<$bench_field> { 115 | a: Some(<$bench_field>::rand(rng)), 116 | b: Some(<$bench_field>::rand(rng)), 117 | num_variables: 10, 118 | num_constraints: 65536, 119 | }; 120 | 121 | let srs = Marlin::< 122 | $bench_field, 123 | SonicKZG10<$bench_pairing_engine, DensePolynomial<$bench_field>>, 124 | SimpleHashFiatShamirRng, 125 | >::universal_setup(65536, 65536, 3 * 65536, rng) 126 | .unwrap(); 127 | let (pk, vk) = Marlin::< 128 | $bench_field, 129 | SonicKZG10<$bench_pairing_engine, DensePolynomial<$bench_field>>, 130 | SimpleHashFiatShamirRng, 131 | >::index(&srs, c) 132 | .unwrap(); 133 | let proof = Marlin::< 134 | $bench_field, 135 | SonicKZG10<$bench_pairing_engine, DensePolynomial<$bench_field>>, 136 | SimpleHashFiatShamirRng, 137 | >::prove(&pk, c.clone(), rng) 138 | .unwrap(); 139 | 140 | let v = c.a.unwrap().mul(c.b.unwrap()); 141 | 142 | let start = ark_std::time::Instant::now(); 143 | 144 | for _ in 0..NUM_VERIFY_REPEATITIONS { 145 | let _ = Marlin::< 146 | $bench_field, 147 | SonicKZG10<$bench_pairing_engine, DensePolynomial<$bench_field>>, 148 | SimpleHashFiatShamirRng, 149 | >::verify(&vk, &vec![v], &proof, rng) 150 | .unwrap(); 151 | } 152 | 153 | println!( 154 | "verifying time for {}: {} ns", 155 | stringify!($bench_pairing_engine), 156 | start.elapsed().as_nanos() / NUM_VERIFY_REPEATITIONS as u128 157 | ); 158 | }; 159 | } 160 | 161 | fn bench_prove() { 162 | marlin_prove_bench!(bls, BlsFr, Bls12_381); 163 | marlin_prove_bench!(mnt4, MNT4Fr, MNT4_298); 164 | marlin_prove_bench!(mnt6, MNT6Fr, MNT6_298); 165 | marlin_prove_bench!(mnt4big, MNT4BigFr, MNT4_753); 166 | marlin_prove_bench!(mnt6big, MNT6BigFr, MNT6_753); 167 | } 168 | 169 | fn bench_verify() { 170 | marlin_verify_bench!(bls, BlsFr, Bls12_381); 171 | marlin_verify_bench!(mnt4, MNT4Fr, MNT4_298); 172 | marlin_verify_bench!(mnt6, MNT6Fr, MNT6_298); 173 | marlin_verify_bench!(mnt4big, MNT4BigFr, MNT4_753); 174 | marlin_verify_bench!(mnt6big, MNT6BigFr, MNT6_753); 175 | } 176 | 177 | fn main() { 178 | bench_prove(); 179 | bench_verify(); 180 | } 181 | -------------------------------------------------------------------------------- /diagram/.gitignore: -------------------------------------------------------------------------------- 1 | latex.out 2 | out 3 | -------------------------------------------------------------------------------- /diagram/diagram.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkworks-rs/marlin/026b73c20638f4f86cbae0946045934c865d5a30/diagram/diagram.pdf -------------------------------------------------------------------------------- /diagram/diagram.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage[utf8]{inputenc} 3 | \usepackage{amsmath} 4 | \usepackage{amsfonts} 5 | 6 | \title{Marlin Diagram} 7 | \date{July 2020} 8 | 9 | \usepackage[x11names]{xcolor} 10 | \usepackage[b4paper,margin=1.2in]{geometry} 11 | \usepackage{tikz} 12 | \usepackage{afterpage} 13 | 14 | \newenvironment{rcases} 15 | {\left.\begin{aligned}} 16 | {\end{aligned}\right\rbrace} 17 | 18 | \begin{document} 19 | 20 | \newcommand{\cm}[1]{\ensuremath{\mathsf{cm}_{#1}}} 21 | \newcommand{\vcm}[1]{\ensuremath{\mathsf{vcm}_{#1}}} 22 | \newcommand{\s}{\ensuremath{\hat{s}}} 23 | \newcommand{\w}{\ensuremath{\hat{w}}} 24 | \newcommand{\x}{\ensuremath{\hat{x}}} 25 | \newcommand{\z}{\ensuremath{\hat{z}}} 26 | \newcommand{\za}{\ensuremath{\hat{z}_A}} 27 | \newcommand{\zb}{\ensuremath{\hat{z}_B}} 28 | \newcommand{\zc}{\ensuremath{\hat{z}_C 29 | }} 30 | \newcommand{\zm}{\ensuremath{\hat{z}_M}} 31 | 32 | \newcommand{\val}{\ensuremath{\mathsf{val}}} 33 | \newcommand{\row}{\ensuremath{\mathsf{row}}} 34 | \newcommand{\col}{\ensuremath{\mathsf{col}}} 35 | \newcommand{\rowcol}{\ensuremath{\mathsf{rowcol}}} 36 | 37 | \newcommand{\hval}{\ensuremath{\widehat{\val}}} 38 | \newcommand{\hrow}{\ensuremath{\widehat{\row}}} 39 | \newcommand{\hcol}{\ensuremath{\widehat{\col}}} 40 | \newcommand{\hrowcol}{\ensuremath{\widehat{\rowcol}}} 41 | 42 | \newcommand{\bb}{\ensuremath{\mathsf{b}}} 43 | \newcommand{\denom}{\ensuremath{\mathsf{denom}}} 44 | 45 | \newcommand{\sumcheckinner}{\mathsf{inner}} 46 | \newcommand{\sumcheckouter}{\mathsf{outer}} 47 | 48 | \newcommand{\Prover}{\mathcal{P}} 49 | \newcommand{\Verifier}{\mathcal{V}} 50 | 51 | \newcommand{\F}{\mathbb{F}} 52 | 53 | \newcommand{\DomainA}{H} 54 | \newcommand{\DomainB}{K} 55 | 56 | \newcommand{\vPoly}[1]{\ensuremath{v_{#1}}} 57 | 58 | 59 | This diagram (on the following page) shows the interaction of the Marlin prover and verifier. It is similar to the diagrams in the paper (Figure 5 in Section 5 and Figure 7 in Appendix E, in the latest ePrint version), but with two changes: it shows not just the AHP but also the use of the polynomial commitments (the cryptography layer); and it aims to be fully up-to-date with the recent optimizations to the codebase. This diagram, together with the diagrams in the paper, can act as a ``bridge" between the codebase and the theory that the paper describes. 60 | 61 | \section{Glossary of notation} 62 | \begin{table*}[htbp] 63 | \centering 64 | \begin{tabular}{c|c} 65 | $\F$ & the finite field over which the R1CS instance is defined \\ 66 | \hline 67 | $x$ & public input \\ 68 | \hline 69 | $w$ & secret witness \\ 70 | \hline 71 | $\DomainA$ & variable domain \\ 72 | \hline 73 | $\DomainB$ & matrix domain \\ 74 | \hline 75 | $X$ & domain sized for input (not including witness) \\ 76 | \hline 77 | $v_D(X)$ & vanishing polynomial over domain $D$ \\ 78 | \hline 79 | $u_D(X, Y)$ & bivariate derivative of vanishing polynomials over domain $D$\\ 80 | \hline 81 | $A, B, C$ & R1CS instance matrices \\ 82 | \hline 83 | $A^*, B^*, C^*$ & 84 | \begin{tabular}{@{}c@{}}shifted transpose of $A,B,C$ matries given by $M^*_{a,b} := M_{b,a} \cdot u_\DomainA(b,b) \; \forall a,b \in \DomainA$ \\ (optimization from Fractal, explained in Claim 6.7 of that paper) \end{tabular} \\ 85 | \hline 86 | $\hrow, \hcol$ & 87 | \begin{tabular}{@{}c@{}} LDEs of (respectively) row positions and column positions of non-zero elements of any \\ linear combination of $A^*$, $B^*$, and $C^*$ (the choice of combination is irrelevant).\end{tabular} \\ 88 | \hline 89 | ${\hrowcol}$ & 90 | \begin{tabular}{@{}c@{}} LDE of the element-wise product of $\row$ and $\col$, given separately for efficiency \\ (namely to allow this product to be part of a \textit{linear} combination) \end{tabular} \\ 91 | \hline 92 | $\hval_{\{A^*, B^*, C^*\}}$ & 93 | \begin{tabular}{@{}c@{}} preprocessed polynomials containing LDEs of \\ the values of non-zero elements of any linear combination of $A^*$, $B^*$, and $C^*$. \\ That is, if $\kappa$ is the $k$-th element of $\DomainB$, then $(\sum_M \eta_M \hval_{M^*})(\kappa)$ is the \\$k$-th non-zero entry of $\sum_M \eta_M M^*$, for arbitrary $\eta_{\{A, B, C\}} \in \F$.\end{tabular} \\ 94 | \hline 95 | $\Prover$ & prover \\ 96 | \hline 97 | $\Verifier$ & verifier \\ 98 | \hline 99 | $\Verifier^{p}$ & 100 | \begin{tabular}{@{}c@{}} $\Verifier$ with ``oracle" access to polynomial $p$ (via commitments provided \\ by the indexer, later opened as necessary by $\Prover$) \end{tabular}\\ 101 | \hline 102 | $\bb$ & bound on the number of queries \\ 103 | \hline 104 | $r_M(X, Y)$ & an intermediate polynomial defined by $r_M(X, Y) = M^*(Y,X)$\\ 105 | \hline 106 | \end{tabular} 107 | \end{table*} 108 | 109 | \afterpage{% 110 | \newgeometry{margin=0.2in} 111 | 112 | \section{Diagram} 113 | 114 | \centering 115 | \begin{tikzpicture}[scale=0.95, every node/.style={scale=0.95}] 116 | 117 | \tikzstyle{lalign} = [minimum width=3cm,align=left,anchor=west] 118 | \tikzstyle{ralign} = [minimum width=3cm,align=right,anchor=east] 119 | 120 | \node[lalign] (prover) at (-3,27.3) {% 121 | $\Prover(\F, \DomainA, \DomainB, A, B, C, x, w)$ 122 | }; 123 | 124 | \node[ralign] (verifier) at (16.2,27.3) {% 125 | $\Verifier^{\hrow, \hcol, \hrowcol, \hval_{A^*}, \hval_{B^*}, \hval_{C^*}}(\F, \DomainA, \DomainB, x)$ 126 | }; 127 | 128 | \draw [line width=1.0pt] (-3,27.0) -- (16,27.0); 129 | 130 | \node[lalign] (prover1) at (-3,26.1) {% 131 | $z := (x, w), z_A := Az, z_B := Bz$ \\ 132 | sample $\w(X) \in \F^{<|w|+\bb}[X]$ and $\za(X), \zb(X) \in \F^{<|\DomainA|+\bb}[X]$ \\ 133 | sample mask poly $\s(X) \in \F^{<3|\DomainA|+2\bb-2}[X]$ such that $\sum_{\kappa \in \DomainA}\s(\kappa) = 0$ 134 | }; 135 | 136 | \draw [->] (-2,24.8) -- node[midway,fill=white] {commitments $\cm{\w}, \cm{\za}, \cm{\zb}, \cm{\s}$} (15,24.8); 137 | 138 | \node[ralign] (verifier1) at (16,24.0) {% 139 | $\eta_A, \eta_B, \eta_C \gets \F$ \\ 140 | $\alpha \gets \F \setminus \DomainA$ 141 | }; 142 | 143 | \draw [->] (15,23.3) -- node[midway,fill=white] {$\eta_A, \eta_B, \eta_C, \alpha \in \F$} (-2,23.3); 144 | 145 | \node[lalign] (prover2) at (-3,22.5) {% 146 | compute $t(X) := \sum_M \eta_M r_M(\alpha, X)$ 147 | }; 148 | 149 | \draw (-2.9,22.0) rectangle (15.9,3.8); 150 | 151 | \node (sc1label) at (6.5,21.7) {% 152 | \textbf{sumcheck for} $\s(X) + u_H(\alpha, X) \left(\sum_M \eta_M \zm(X)\right) - t(X)\z(X)$ \textbf{ over } $\DomainA$ 153 | }; 154 | 155 | \node[lalign] (prover3) at (-2,20.7) {% 156 | let $\zc(X) := \za(X) \cdot \zb(X)$ \\ 157 | find $g_1(X) \in \F^{|\DomainA|-1}[X]$ and $h_1(X)$ such that \\ 158 | $s(X)+u_H(\alpha, X)(\sum_M \eta_M \zm(X)) - t(X)\z(X) = h_1(X)\vPoly{\DomainA}(X) + Xg_1(X)$ \hspace{0.3cm} $(*)$ 159 | }; 160 | 161 | \draw [->] (-1,19.5) -- node[midway,fill=white] {commitments $\cm{t}, \cm{g_1}, \cm{h_1}$} (14,19.5); 162 | 163 | \node[ralign] (verifier2) at (15.4,19.1) {% 164 | $\beta \gets \F \setminus \DomainA$ 165 | }; 166 | 167 | \draw [->] (14,18.7) -- node[midway,fill=white] {$\beta \in \F$} (-1,18.7); 168 | 169 | \draw (-0.85,18.2) rectangle (13.85,7.6); 170 | 171 | \node (sc2label) at (6.5,17.6) {% 172 | \textbf{sumcheck for } $\sum\limits_{M \in \{A, B, C\}} \eta_M \frac{\vPoly{\DomainA}(\beta) \vPoly{\DomainA}(\alpha)\hval_{M^*}(X)}{\color{purple}(\beta-\hrow(X))(\alpha-\hcol(X))} $ \textbf{ over } $\DomainB$ 173 | }; 174 | 175 | \node[align=center] (mid1) at (6.5, 15) {% 176 | $\begin{aligned} 177 | \text{let } {\color{purple} \denom(X)} &{}:= (\beta - \hrow(X)) (\alpha - \hcol(X)) \\ 178 | &{}= {\color{gray}\alpha\beta} - {\color{gray}\alpha}\hrow(X) - {\color{gray}\beta}\hcol(X) + \hrowcol(X) \text{ (over $\DomainB$)}\\\\ 179 | \text{ let } {\color{orange} a(X)} &{}:= {\color{gray} \vPoly{\DomainA}(\beta) \vPoly{\DomainA}(\alpha)} \sum\limits_{M \in \{A, B, C\}} \eta_M \hval_{M^*}(X) 180 | \\\\ 181 | \text{ let } {\color{Green4} b(X)} &{}:= {\color{purple} \denom(X)}\\\\ 182 | \end{aligned}$ 183 | }; 184 | 185 | \node[lalign] (prover4) at (-0.75,12.2) {% 186 | find $g_2(X) \in \F^{|\DomainB|-1}[X]$ and $h_2(X)$ s.t. \\ 187 | $h_2(X)\vPoly{\DomainB}(X) = {\color{orange} a(X)} - {\color{Green4} b(X)} (Xg_2(X)+t(\beta)/|\DomainB|)$ \hspace{0.3cm} $(**)$ 188 | }; 189 | 190 | \draw [->] (0,11.2) -- node[midway,fill=white] {commitments $\cm{g_2}, \cm{h_2}$} (13,11.2); 191 | 192 | \draw [->] (13,10.5) -- node[midway,fill=white] {$\gamma \in \F$} (0,10.5); 193 | 194 | \node[ralign] (verifier3) at (14.5, 10.9) {% 195 | $\gamma \gets \F$ 196 | }; 197 | 198 | \draw[dashed] (1.5,10.0) rectangle (11.5,7.8); 199 | 200 | \node[align=center] (mid4) at (6.5, 8.9) {% 201 | To verify $(**)$, $\Verifier$ will need to check the following: \\[10pt] 202 | $ \underbrace{{\color{orange} a({\color{black} \gamma})} - {\color{Green4} b({\color{black} \gamma})} {\color{gray} (\gamma g_2(\gamma) + t(\beta) / |\DomainB|) - \vPoly{\DomainB}(\gamma)} h_2(\gamma)}_{\sumcheckinner(\gamma)} \stackrel{?}{=} 0 $ 203 | }; 204 | 205 | \node[ralign] (verifier3) at (15.4, 6.9) {% 206 | Compute $\x(X) \in \F^{<|x|}[X]$ from input $x$ 207 | }; 208 | 209 | \draw[dashed] (-2.7,7.4) rectangle (15.7,4.2); 210 | 211 | \node[align=center] (mid5) at (6.5, 5.3) {% 212 | To verify $(*)$, $\Verifier$ will need to check the following: \\[10pt] 213 | $ \underbrace{s(\beta) + {\color{gray} v_H(\alpha, \beta)} ({\color{gray} \eta_A} \za(\beta) + {\color{gray} \eta_C\zb(\beta)} \za(\beta) + {\color{gray} \eta_B\zb(\beta)}) - {\color{gray} t(\beta) \vPoly{X}(\beta)} \w(\beta) - {\color{gray} t(\beta) \x(\beta)} - {\color{gray} \vPoly{\DomainA}(\beta)} h_1(\beta) - {\color{gray} \beta g_1(\beta)}}_{\sumcheckouter(\beta)} \stackrel{?}{=} 0 $ 214 | }; 215 | 216 | \node[lalign] (prover5) at (-3,2.9) {% 217 | $v_{g_2} := g_2(\gamma)$ \\[3pt] 218 | $v_{g_1} := g_1(\beta), v_{\zb} := \zb(\beta), v_{t} := t(\beta)$ 219 | }; 220 | 221 | \draw [->] (-2,1.9) -- node[midway,fill=white] {$v_{g_2}, v_{g_1}, v_{\zb}, v_{t}$} (15,1.9); 222 | 223 | \node[align=center] (mid7) at (6.5,0.8) {% 224 | use index commitments $\hrow, \hcol, \hrowcol, \hval_{\{A^*, B^*, C^*\}}$, commitment $\cm{h_2}$, {\color{gray} and evaluations $g_2(\gamma),t(\beta)$} \\ 225 | to construct virtual commitment $\vcm{\sumcheckinner}$ 226 | }; 227 | 228 | \node[align=center] (mid8) at (6.5,-0.5) {% 229 | use commitments $\cm{\s}, \cm{\za}, \cm{\w}, \cm{h_1}$ {\color{gray} and evaluations $\zb(\beta), t(\beta), g_1(\beta)$} \\ 230 | to construct virtual commitment $\vcm{\sumcheckouter}$ 231 | }; 232 | 233 | \node[ralign] (verifier4) at (16,-1.4) {% 234 | $\xi_1, \dots, \xi_5 \gets F$ 235 | }; 236 | 237 | \draw [->] (15,-2.1) -- node[midway,fill=white] {$\xi_1, \dots, \xi_5$} (-2,-2.1); 238 | 239 | \node[lalign] (prover6) at (-3,-3.6) {% 240 | use $\mathsf{PC}.\mathsf{Prove}$ with randomness $\xi_1, \dots, \xi_5$ to \\ 241 | construct a batch opening proof $\pi$ of the following: \\ 242 | $(\cm{g_2}, {\color{red} \vcm{\sumcheckinner}})$ at $\gamma$ evaluate to $(v_{g_2}, {\color{red} 0})$ \hspace{0.3cm} ${\color{red} (**)}$ \\ 243 | $(\cm{g_1}, \cm{\zb}, \cm{t}, {\color{red} \vcm{\sumcheckouter}})$ at $\beta$ evaluate to $(v_{g_1}, v_{\zb}, v_{t}, {\color{red} 0})$ \hspace{0.3cm} ${\color{red} (*)}$ \\ 244 | }; 245 | 246 | \draw [->] (-2,-4.7) -- node[midway,fill=white] {$\pi$} (15,-4.7); 247 | 248 | \node[ralign] (verifier5) at (16,-6.0) {% 249 | verify $\pi$ with $\mathsf{PC}.\mathsf{Verify}$, using randomness $\xi_1, \dots, \xi_5$, \\ 250 | evaluations $v_{g_2}, v_{g_1}, v_{\zb}, v_{t}$, and \\ 251 | commitments $\cm{g_2},\vcm{\sumcheckinner}, \cm{g_1}, \cm{\zb}, \cm{t}, \vcm{\sumcheckinner}$ 252 | }; 253 | 254 | \end{tikzpicture} 255 | 256 | \clearpage 257 | \restoregeometry 258 | } 259 | 260 | 261 | \end{document} 262 | -------------------------------------------------------------------------------- /scripts/linkify_changelog.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import fileinput 4 | import os 5 | 6 | # Set this to the name of the repo, if you don't want it to be read from the filesystem. 7 | # It assumes the changelog file is in the root of the repo. 8 | repo_name = "" 9 | 10 | # This script goes through the provided file, and replaces any " \#", 11 | # with the valid mark down formatted link to it. e.g. 12 | # " [\#number](https://github.com/arkworks-rs/template/pull/) 13 | # Note that if the number is for a an issue, github will auto-redirect you when you click the link. 14 | # It is safe to run the script multiple times in succession. 15 | # 16 | # Example usage $ python3 linkify_changelog.py ../CHANGELOG.md 17 | if len(sys.argv) < 2: 18 | print("Must include path to changelog as the first argument to the script") 19 | print("Example Usage: python3 linkify_changelog.py ../CHANGELOG.md") 20 | exit() 21 | 22 | changelog_path = sys.argv[1] 23 | if repo_name == "": 24 | path = os.path.abspath(changelog_path) 25 | components = path.split(os.path.sep) 26 | repo_name = components[-2] 27 | 28 | for line in fileinput.input(inplace=True): 29 | line = re.sub(r"\- #([0-9]*)", r"- [\\#\1](https://github.com/arkworks-rs/" + repo_name + r"/pull/\1)", line.rstrip()) 30 | # edits the current file 31 | print(line) -------------------------------------------------------------------------------- /src/ahp/constraint_systems.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use crate::ahp::indexer::Matrix; 4 | use crate::ahp::*; 5 | use crate::BTreeMap; 6 | use ark_ff::{Field, PrimeField}; 7 | use ark_poly::{EvaluationDomain, Evaluations as EvaluationsOnDomain, GeneralEvaluationDomain}; 8 | use ark_relations::{lc, r1cs::ConstraintSystemRef}; 9 | use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError}; 10 | use ark_std::{ 11 | cfg_iter_mut, 12 | io::{Read, Write}, 13 | }; 14 | use derivative::Derivative; 15 | 16 | /* ************************************************************************* */ 17 | /* ************************************************************************* */ 18 | /* ************************************************************************* */ 19 | 20 | pub(crate) fn num_non_zero(joint_matrix: &Vec>) -> usize { 21 | joint_matrix.iter().map(|row| row.len()).sum() 22 | } 23 | 24 | pub(crate) fn make_matrices_square_for_indexer(cs: ConstraintSystemRef) { 25 | let num_variables = cs.num_instance_variables() + cs.num_witness_variables(); 26 | let matrix_dim = padded_matrix_dim(num_variables, cs.num_constraints()); 27 | make_matrices_square(cs.clone(), num_variables); 28 | assert_eq!( 29 | cs.num_instance_variables() + cs.num_witness_variables(), 30 | cs.num_constraints(), 31 | "padding failed!" 32 | ); 33 | assert_eq!( 34 | cs.num_instance_variables() + cs.num_witness_variables(), 35 | matrix_dim, 36 | "padding does not result in expected matrix size!" 37 | ); 38 | } 39 | 40 | /// This must *always* be in sync with `make_matrices_square`. 41 | pub(crate) fn padded_matrix_dim(num_formatted_variables: usize, num_constraints: usize) -> usize { 42 | core::cmp::max(num_formatted_variables, num_constraints) 43 | } 44 | 45 | pub(crate) fn pad_input_for_indexer_and_prover(cs: ConstraintSystemRef) { 46 | let formatted_input_size = cs.num_instance_variables(); 47 | 48 | let domain_x = GeneralEvaluationDomain::::new(formatted_input_size); 49 | assert!(domain_x.is_some()); 50 | 51 | let padded_size = domain_x.unwrap().size(); 52 | 53 | if padded_size > formatted_input_size { 54 | for _ in 0..(padded_size - formatted_input_size) { 55 | cs.new_input_variable(|| Ok(F::zero())).unwrap(); 56 | } 57 | } 58 | } 59 | 60 | pub(crate) fn make_matrices_square( 61 | cs: ConstraintSystemRef, 62 | num_formatted_variables: usize, 63 | ) { 64 | let num_constraints = cs.num_constraints(); 65 | let matrix_padding = ((num_formatted_variables as isize) - (num_constraints as isize)).abs(); 66 | 67 | if num_formatted_variables > num_constraints { 68 | // Add dummy constraints of the form 0 * 0 == 0 69 | for _ in 0..matrix_padding { 70 | cs.enforce_constraint(lc!(), lc!(), lc!()) 71 | .expect("enforce 0 * 0 == 0 failed"); 72 | } 73 | } else { 74 | // Add dummy unconstrained variables 75 | for _ in 0..matrix_padding { 76 | let _ = cs 77 | .new_witness_variable(|| Ok(F::one())) 78 | .expect("alloc failed"); 79 | } 80 | } 81 | } 82 | 83 | /// Evaluations of various polynomials related to the constraint matrices, 84 | /// over the same domain. 85 | #[derive(Derivative, CanonicalSerialize, CanonicalDeserialize)] 86 | #[derivative(Debug(bound = "F: PrimeField"), Clone(bound = "F: PrimeField"))] 87 | pub struct MatrixEvals { 88 | /// Evaluations of the `row` polynomial. 89 | pub row: EvaluationsOnDomain, 90 | /// Evaluations of the `col` polynomial. 91 | pub col: EvaluationsOnDomain, 92 | /// Evaluations of the `row_col` polynomial. 93 | pub row_col: EvaluationsOnDomain, 94 | /// Evaluations of the `val_a` polynomial. 95 | pub val_a: EvaluationsOnDomain, 96 | /// Evaluations of the `val_b` polynomial. 97 | pub val_b: EvaluationsOnDomain, 98 | /// Evaluations of the `val_c` polynomial. 99 | pub val_c: EvaluationsOnDomain, 100 | } 101 | 102 | /// Contains information about the arithmetization of the matrix M^*. 103 | /// Here `M^*(i, j) := M(j, i) * u_H(j, j)`. For more details, see [\[COS20\]](https://eprint.iacr.org/2019/1076). 104 | #[derive(Derivative, CanonicalSerialize, CanonicalDeserialize)] 105 | #[derivative(Debug(bound = "F: PrimeField"), Clone(bound = "F: PrimeField"))] 106 | pub struct MatrixArithmetization { 107 | /// LDE of the row indices of M^*. 108 | pub row: LabeledPolynomial, 109 | /// LDE of the column indices of M^*. 110 | pub col: LabeledPolynomial, 111 | /// LDE of the non-zero entries of A^*. 112 | pub val_a: LabeledPolynomial, 113 | /// LDE of the non-zero entries of B^*. 114 | pub val_b: LabeledPolynomial, 115 | /// LDE of the non-zero entries of C^*. 116 | pub val_c: LabeledPolynomial, 117 | /// LDE of the vector containing entry-wise products of `row` and `col`, 118 | /// where `row` and `col` are as above. 119 | pub row_col: LabeledPolynomial, 120 | 121 | /// Evaluation of `self.row`, `self.col`, and `self.val` on the domain `K`. 122 | pub evals_on_K: MatrixEvals, 123 | } 124 | 125 | pub(crate) fn arithmetize_matrix( 126 | joint_matrix: &Vec>, 127 | a: &Matrix, 128 | b: &Matrix, 129 | c: &Matrix, 130 | interpolation_domain: GeneralEvaluationDomain, 131 | output_domain: GeneralEvaluationDomain, 132 | input_domain: GeneralEvaluationDomain, 133 | ) -> MatrixArithmetization { 134 | let matrix_time = start_timer!(|| "Computing row, col, and val LDEs"); 135 | 136 | let elems: Vec<_> = output_domain.elements().collect(); 137 | 138 | let lde_evals_time = start_timer!(|| "Computing row, col and val evals"); 139 | // Recall that we are computing the arithmetization of M^*, 140 | // where `M^*(i, j) := M(j, i) * u_H(j, j)`. 141 | let a = a 142 | .iter() 143 | .enumerate() 144 | .map(|(r, row)| row.iter().map(move |(f, i)| ((r, *i), *f))) 145 | .flatten() 146 | .collect::>(); 147 | 148 | let b = b 149 | .iter() 150 | .enumerate() 151 | .map(|(r, row)| row.iter().map(move |(f, i)| ((r, *i), *f))) 152 | .flatten() 153 | .collect::>(); 154 | 155 | let c = c 156 | .iter() 157 | .enumerate() 158 | .map(|(r, row)| row.iter().map(move |(f, i)| ((r, *i), *f))) 159 | .flatten() 160 | .collect::>(); 161 | 162 | let eq_poly_vals_time = start_timer!(|| "Precomputing eq_poly_vals"); 163 | let eq_poly_vals: BTreeMap = output_domain 164 | .elements() 165 | .zip(output_domain.batch_eval_unnormalized_bivariate_lagrange_poly_with_same_inputs()) 166 | .collect(); 167 | end_timer!(eq_poly_vals_time); 168 | 169 | let mut row_vec = Vec::with_capacity(interpolation_domain.size()); 170 | let mut col_vec = Vec::with_capacity(interpolation_domain.size()); 171 | let mut val_a_vec = Vec::with_capacity(interpolation_domain.size()); 172 | let mut val_b_vec = Vec::with_capacity(interpolation_domain.size()); 173 | let mut val_c_vec = Vec::with_capacity(interpolation_domain.size()); 174 | let mut inverses = Vec::with_capacity(interpolation_domain.size()); 175 | let mut count = 0; 176 | 177 | for (r, row) in joint_matrix.into_iter().enumerate() { 178 | for i in row { 179 | let row_val = elems[r]; 180 | let col_val = elems[output_domain.reindex_by_subdomain(input_domain, *i)]; 181 | 182 | // We are dealing with the transpose of M 183 | row_vec.push(col_val); 184 | col_vec.push(row_val); 185 | // We insert zeros if a matrix doesn't contain an entry at the given (row, col) location. 186 | val_a_vec.push(a.get(&(r, *i)).copied().unwrap_or(F::zero())); 187 | val_b_vec.push(b.get(&(r, *i)).copied().unwrap_or(F::zero())); 188 | val_c_vec.push(c.get(&(r, *i)).copied().unwrap_or(F::zero())); 189 | inverses.push(eq_poly_vals[&col_val]); 190 | 191 | count += 1; 192 | } 193 | } 194 | ark_ff::batch_inversion::(&mut inverses); 195 | drop(eq_poly_vals); 196 | 197 | cfg_iter_mut!(val_a_vec) 198 | .zip(&mut val_b_vec) 199 | .zip(&mut val_c_vec) 200 | .zip(inverses) 201 | .for_each(|(((v_a, v_b), v_c), inv)| { 202 | *v_a *= &inv; 203 | *v_b *= &inv; 204 | *v_c *= &inv; 205 | }); 206 | end_timer!(lde_evals_time); 207 | 208 | for _ in count..interpolation_domain.size() { 209 | col_vec.push(elems[0]); 210 | row_vec.push(elems[0]); 211 | val_a_vec.push(F::zero()); 212 | val_b_vec.push(F::zero()); 213 | val_c_vec.push(F::zero()); 214 | } 215 | 216 | let row_col_vec: Vec<_> = row_vec 217 | .iter() 218 | .zip(&col_vec) 219 | .map(|(row, col)| *row * col) 220 | .collect(); 221 | 222 | let interpolate_time = start_timer!(|| "Interpolating on K"); 223 | let row_evals_on_K = EvaluationsOnDomain::from_vec_and_domain(row_vec, interpolation_domain); 224 | let col_evals_on_K = EvaluationsOnDomain::from_vec_and_domain(col_vec, interpolation_domain); 225 | let val_a_evals_on_K = 226 | EvaluationsOnDomain::from_vec_and_domain(val_a_vec, interpolation_domain); 227 | let val_b_evals_on_K = 228 | EvaluationsOnDomain::from_vec_and_domain(val_b_vec, interpolation_domain); 229 | let val_c_evals_on_K = 230 | EvaluationsOnDomain::from_vec_and_domain(val_c_vec, interpolation_domain); 231 | let row_col_evals_on_K = 232 | EvaluationsOnDomain::from_vec_and_domain(row_col_vec, interpolation_domain); 233 | 234 | let row = row_evals_on_K.clone().interpolate(); 235 | let col = col_evals_on_K.clone().interpolate(); 236 | let val_a = val_a_evals_on_K.clone().interpolate(); 237 | let val_b = val_b_evals_on_K.clone().interpolate(); 238 | let val_c = val_c_evals_on_K.clone().interpolate(); 239 | let row_col = row_col_evals_on_K.clone().interpolate(); 240 | 241 | end_timer!(interpolate_time); 242 | 243 | end_timer!(matrix_time); 244 | let evals_on_K = MatrixEvals { 245 | row: row_evals_on_K, 246 | col: col_evals_on_K, 247 | row_col: row_col_evals_on_K, 248 | val_a: val_a_evals_on_K, 249 | val_b: val_b_evals_on_K, 250 | val_c: val_c_evals_on_K, 251 | }; 252 | 253 | MatrixArithmetization { 254 | row: LabeledPolynomial::new("row".into(), row, None, None), 255 | col: LabeledPolynomial::new("col".into(), col, None, None), 256 | val_a: LabeledPolynomial::new("a_val".into(), val_a, None, None), 257 | val_b: LabeledPolynomial::new("b_val".into(), val_b, None, None), 258 | val_c: LabeledPolynomial::new("c_val".into(), val_c, None, None), 259 | row_col: LabeledPolynomial::new("row_col".into(), row_col, None, None), 260 | evals_on_K, 261 | } 262 | } 263 | 264 | /* ************************************************************************* */ 265 | /* ************************************************************************* */ 266 | /* ************************************************************************* */ 267 | 268 | /// Formats the public input according to the requirements of the constraint 269 | /// system 270 | pub(crate) fn format_public_input(public_input: &[F]) -> Vec { 271 | let mut input = vec![F::one()]; 272 | input.extend_from_slice(public_input); 273 | input 274 | } 275 | 276 | /// Takes in a previously formatted public input and removes the formatting 277 | /// imposed by the constraint system. 278 | pub(crate) fn unformat_public_input(input: &[F]) -> Vec { 279 | input[1..].to_vec() 280 | } 281 | 282 | pub(crate) fn make_matrices_square_for_prover(cs: ConstraintSystemRef) { 283 | let num_variables = cs.num_instance_variables() + cs.num_witness_variables(); 284 | make_matrices_square(cs.clone(), num_variables); 285 | assert_eq!( 286 | cs.num_instance_variables() + cs.num_witness_variables(), 287 | cs.num_constraints(), 288 | "padding failed!" 289 | ); 290 | } 291 | 292 | #[cfg(test)] 293 | mod tests { 294 | use super::*; 295 | use ark_relations::r1cs::Matrix; 296 | use ark_std::{collections::BTreeMap, UniformRand}; 297 | 298 | use ark_bls12_381::Fr as F; 299 | use ark_ff::{One, Zero}; 300 | use ark_poly::EvaluationDomain; 301 | 302 | fn entry(matrix: &Matrix, row: usize, col: usize) -> F { 303 | matrix[row] 304 | .iter() 305 | .find_map(|(f, i)| (i == &col).then(|| *f)) 306 | .unwrap_or(F::zero()) 307 | } 308 | 309 | #[test] 310 | fn check_arithmetization() { 311 | let a = vec![ 312 | vec![(F::one(), 1), (F::one(), 2)], 313 | vec![(F::one(), 3)], 314 | vec![(F::one(), 3)], 315 | vec![(F::one(), 0), (F::one(), 1), (F::one(), 5)], 316 | vec![(F::one(), 1), (F::one(), 2), (F::one(), 6)], 317 | vec![(F::one(), 2), (F::one(), 5), (F::one(), 7)], 318 | vec![(F::one(), 3), (F::one(), 4), (F::one(), 6)], 319 | vec![(F::one(), 0), (F::one(), 6), (F::one(), 7)], 320 | ]; 321 | 322 | let b = vec![ 323 | vec![], 324 | vec![(F::one(), 1)], 325 | vec![(F::one(), 0)], 326 | vec![(F::one(), 2)], 327 | vec![(F::one(), 3)], 328 | vec![(F::one(), 4)], 329 | vec![(F::one(), 5)], 330 | vec![(F::one(), 6)], 331 | ]; 332 | 333 | let c = vec![ 334 | vec![], 335 | vec![(F::one(), 7)], 336 | vec![], 337 | vec![], 338 | vec![], 339 | vec![(F::one(), 3)], 340 | vec![], 341 | vec![], 342 | ]; 343 | let joint_matrix = crate::ahp::indexer::sum_matrices(&a, &b, &c); 344 | let num_non_zero = dbg!(num_non_zero(&joint_matrix)); 345 | let interpolation_domain = EvaluationDomain::new(num_non_zero).unwrap(); 346 | let output_domain = EvaluationDomain::new(2 + 6).unwrap(); 347 | let input_domain = EvaluationDomain::new(2).unwrap(); 348 | let joint_arith = arithmetize_matrix( 349 | &joint_matrix, 350 | &a, 351 | &b, 352 | &c, 353 | interpolation_domain, 354 | output_domain, 355 | input_domain, 356 | ); 357 | let inverse_map = output_domain 358 | .elements() 359 | .enumerate() 360 | .map(|(i, e)| (e, i)) 361 | .collect::>(); 362 | let elements = output_domain.elements().collect::>(); 363 | let reindexed_inverse_map = (0..output_domain.size()) 364 | .map(|i| { 365 | let reindexed_i = output_domain.reindex_by_subdomain(input_domain, i); 366 | (elements[reindexed_i], i) 367 | }) 368 | .collect::>(); 369 | 370 | let eq_poly_vals: BTreeMap = output_domain 371 | .elements() 372 | .zip(output_domain.batch_eval_unnormalized_bivariate_lagrange_poly_with_same_inputs()) 373 | .collect(); 374 | 375 | let mut rng = ark_std::test_rng(); 376 | let eta_a = F::rand(&mut rng); 377 | let eta_b = F::rand(&mut rng); 378 | let eta_c = F::rand(&mut rng); 379 | for (k_index, k) in interpolation_domain.elements().enumerate() { 380 | let row_val = joint_arith.row.evaluate(&k); 381 | let col_val = joint_arith.col.evaluate(&k); 382 | 383 | let inverse = (eq_poly_vals[&row_val]).inverse().unwrap(); 384 | // we're in transpose land. 385 | 386 | let val_a = joint_arith.val_a.evaluate(&k); 387 | let val_b = joint_arith.val_b.evaluate(&k); 388 | let val_c = joint_arith.val_c.evaluate(&k); 389 | assert_eq!(joint_arith.evals_on_K.row[k_index], row_val); 390 | assert_eq!(joint_arith.evals_on_K.col[k_index], col_val); 391 | assert_eq!(joint_arith.evals_on_K.val_a[k_index], val_a); 392 | assert_eq!(joint_arith.evals_on_K.val_b[k_index], val_b); 393 | assert_eq!(joint_arith.evals_on_K.val_c[k_index], val_c); 394 | if k_index < num_non_zero { 395 | let col = *dbg!(reindexed_inverse_map.get(&row_val).unwrap()); 396 | let row = *dbg!(inverse_map.get(&col_val).unwrap()); 397 | assert!(joint_matrix[row].binary_search(&col).is_ok()); 398 | assert_eq!( 399 | eta_a * val_a + eta_b * val_b + eta_c * val_c, 400 | inverse 401 | * (eta_a * entry(&a, row, col) 402 | + eta_b * entry(&b, row, col) 403 | + eta_c * entry(&c, row, col)), 404 | ); 405 | } 406 | } 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /src/ahp/indexer.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use ark_std::collections::BTreeSet; 4 | 5 | use crate::ahp::{ 6 | constraint_systems::{arithmetize_matrix, MatrixArithmetization}, 7 | AHPForR1CS, Error, LabeledPolynomial, 8 | }; 9 | use crate::Vec; 10 | use ark_ff::PrimeField; 11 | use ark_poly::{EvaluationDomain, GeneralEvaluationDomain}; 12 | use ark_relations::r1cs::{ 13 | ConstraintSynthesizer, ConstraintSystem, OptimizationGoal, SynthesisError, SynthesisMode, 14 | }; 15 | use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError}; 16 | use ark_std::{ 17 | io::{Read, Write}, 18 | marker::PhantomData, 19 | }; 20 | use derivative::Derivative; 21 | 22 | use crate::ahp::constraint_systems::{ 23 | make_matrices_square_for_indexer, num_non_zero, pad_input_for_indexer_and_prover, 24 | }; 25 | 26 | /// Information about the index, including the field of definition, the number of 27 | /// variables, the number of constraints, and the maximum number of non-zero 28 | /// entries in any of the constraint matrices. 29 | #[derive(Derivative, CanonicalSerialize, CanonicalDeserialize)] 30 | #[derivative(Clone(bound = ""), Copy(bound = ""))] 31 | pub struct IndexInfo { 32 | /// The total number of variables in the constraint system. 33 | pub num_variables: usize, 34 | /// The number of constraints. 35 | pub num_constraints: usize, 36 | /// The total number of non-zero entries in the sum of all constraint matrices. 37 | pub num_non_zero: usize, 38 | /// The number of input elements. 39 | pub num_instance_variables: usize, 40 | 41 | #[doc(hidden)] 42 | f: PhantomData, 43 | } 44 | 45 | impl IndexInfo { 46 | /// Construct a new index info 47 | pub fn new( 48 | num_variables: usize, 49 | num_constraints: usize, 50 | num_non_zero: usize, 51 | num_instance_variables: usize, 52 | ) -> Self { 53 | Self { 54 | num_variables, 55 | num_constraints, 56 | num_non_zero, 57 | num_instance_variables, 58 | f: PhantomData, 59 | } 60 | } 61 | } 62 | 63 | impl ark_ff::ToBytes for IndexInfo { 64 | fn write(&self, mut w: W) -> ark_std::io::Result<()> { 65 | (self.num_variables as u64).write(&mut w)?; 66 | (self.num_constraints as u64).write(&mut w)?; 67 | (self.num_non_zero as u64).write(&mut w) 68 | } 69 | } 70 | 71 | impl IndexInfo { 72 | /// The maximum degree of polynomial required to represent this index in the 73 | /// the AHP. 74 | pub fn max_degree(&self) -> usize { 75 | AHPForR1CS::::max_degree(self.num_constraints, self.num_variables, self.num_non_zero) 76 | .unwrap() 77 | } 78 | } 79 | 80 | /// Represents a matrix. 81 | pub type Matrix = Vec>; 82 | 83 | pub(crate) fn sum_matrices( 84 | a: &Matrix, 85 | b: &Matrix, 86 | c: &Matrix, 87 | ) -> Vec> { 88 | a.iter() 89 | .zip(b) 90 | .zip(c) 91 | .map(|((row_a, row_b), row_c)| { 92 | row_a 93 | .iter() 94 | .map(|(_, i)| *i) 95 | .chain(row_b.iter().map(|(_, i)| *i)) 96 | .chain(row_c.iter().map(|(_, i)| *i)) 97 | .collect::>() 98 | .into_iter() 99 | .collect() 100 | }) 101 | .collect() 102 | } 103 | 104 | #[derive(Derivative)] 105 | #[derivative(Clone(bound = "F: PrimeField"))] 106 | /// The indexed version of the constraint system. 107 | /// This struct contains three kinds of objects: 108 | /// 1) `index_info` is information about the index, such as the size of the 109 | /// public input 110 | /// 2) `{a,b,c}` are the matrices defining the R1CS instance 111 | /// 3) `{a,b,c}_star_arith` are structs containing information about A^*, B^*, and C^*, 112 | /// which are matrices defined as `M^*(i, j) = M(j, i) * u_H(j, j)`. 113 | #[derive(CanonicalSerialize, CanonicalDeserialize)] 114 | pub struct Index { 115 | /// Information about the index. 116 | pub index_info: IndexInfo, 117 | 118 | /// The A matrix for the R1CS instance 119 | pub a: Matrix, 120 | /// The B matrix for the R1CS instance 121 | pub b: Matrix, 122 | /// The C matrix for the R1CS instance 123 | pub c: Matrix, 124 | 125 | /// Joint arithmetization of the A*, B*, and C* matrices. 126 | pub joint_arith: MatrixArithmetization, 127 | } 128 | 129 | impl Index { 130 | /// The maximum degree required to represent polynomials of this index. 131 | pub fn max_degree(&self) -> usize { 132 | self.index_info.max_degree() 133 | } 134 | 135 | /// Iterate over the indexed polynomials. 136 | pub fn iter(&self) -> impl Iterator> { 137 | ark_std::vec![ 138 | &self.joint_arith.row, 139 | &self.joint_arith.col, 140 | &self.joint_arith.val_a, 141 | &self.joint_arith.val_b, 142 | &self.joint_arith.val_c, 143 | &self.joint_arith.row_col, 144 | ] 145 | .into_iter() 146 | } 147 | } 148 | 149 | impl AHPForR1CS { 150 | /// Generate the index for this constraint system. 151 | pub fn index>(c: C) -> Result, Error> { 152 | let index_time = start_timer!(|| "AHP::Index"); 153 | 154 | let constraint_time = start_timer!(|| "Generating constraints"); 155 | let ics = ConstraintSystem::new_ref(); 156 | ics.set_optimization_goal(OptimizationGoal::Weight); 157 | ics.set_mode(SynthesisMode::Setup); 158 | c.generate_constraints(ics.clone())?; 159 | end_timer!(constraint_time); 160 | 161 | let padding_time = start_timer!(|| "Padding matrices to make them square"); 162 | pad_input_for_indexer_and_prover(ics.clone()); 163 | end_timer!(padding_time); 164 | let matrix_processing_time = start_timer!(|| "Processing matrices"); 165 | ics.finalize(); 166 | make_matrices_square_for_indexer(ics.clone()); 167 | let matrices = ics.to_matrices().expect("should not be `None`"); 168 | let joint_matrix = sum_matrices(&matrices.a, &matrices.b, &matrices.c); 169 | let num_non_zero_val = num_non_zero(&joint_matrix); 170 | let (mut a, mut b, mut c) = (matrices.a, matrices.b, matrices.c); 171 | end_timer!(matrix_processing_time); 172 | 173 | let (num_formatted_input_variables, num_witness_variables, num_constraints, num_non_zero) = ( 174 | ics.num_instance_variables(), 175 | ics.num_witness_variables(), 176 | ics.num_constraints(), 177 | num_non_zero_val, 178 | ); 179 | let num_variables = num_formatted_input_variables + num_witness_variables; 180 | 181 | if num_constraints != num_formatted_input_variables + num_witness_variables { 182 | eprintln!( 183 | "number of (formatted) input_variables: {}", 184 | num_formatted_input_variables 185 | ); 186 | eprintln!("number of witness_variables: {}", num_witness_variables); 187 | eprintln!("number of num_constraints: {}", num_constraints); 188 | eprintln!("number of num_non_zero: {}", num_non_zero); 189 | return Err(Error::NonSquareMatrix); 190 | } 191 | 192 | if !Self::num_formatted_public_inputs_is_admissible(num_formatted_input_variables) { 193 | return Err(Error::InvalidPublicInputLength); 194 | } 195 | 196 | let index_info = IndexInfo { 197 | num_variables, 198 | num_constraints, 199 | num_non_zero, 200 | num_instance_variables: num_formatted_input_variables, 201 | 202 | f: PhantomData, 203 | }; 204 | 205 | let domain_h = GeneralEvaluationDomain::new(num_constraints) 206 | .ok_or(SynthesisError::PolynomialDegreeTooLarge)?; 207 | let domain_k = GeneralEvaluationDomain::new(num_non_zero) 208 | .ok_or(SynthesisError::PolynomialDegreeTooLarge)?; 209 | let x_domain = GeneralEvaluationDomain::new(num_formatted_input_variables) 210 | .ok_or(SynthesisError::PolynomialDegreeTooLarge)?; 211 | 212 | let joint_arithmetization_time = start_timer!(|| "Arithmetizing all matrices"); 213 | let joint_arith = arithmetize_matrix( 214 | &joint_matrix, 215 | &mut a, 216 | &mut b, 217 | &mut c, 218 | domain_k, 219 | domain_h, 220 | x_domain, 221 | ); 222 | end_timer!(joint_arithmetization_time); 223 | 224 | end_timer!(index_time); 225 | Ok(Index { 226 | index_info, 227 | 228 | a, 229 | b, 230 | c, 231 | 232 | joint_arith, 233 | }) 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/ahp/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{String, ToString, Vec}; 2 | use ark_ff::{Field, PrimeField}; 3 | use ark_poly::univariate::DensePolynomial; 4 | use ark_poly::{EvaluationDomain, GeneralEvaluationDomain}; 5 | use ark_poly_commit::{LCTerm, LinearCombination}; 6 | use ark_relations::r1cs::SynthesisError; 7 | use ark_std::{borrow::Borrow, cfg_iter_mut, format, marker::PhantomData, vec}; 8 | 9 | #[cfg(feature = "parallel")] 10 | use rayon::prelude::*; 11 | 12 | pub(crate) mod constraint_systems; 13 | /// Describes data structures and the algorithms used by the AHP indexer. 14 | pub mod indexer; 15 | /// Describes data structures and the algorithms used by the AHP prover. 16 | pub mod prover; 17 | /// Describes data structures and the algorithms used by the AHP verifier. 18 | pub mod verifier; 19 | 20 | /// A labeled DensePolynomial with coefficients over `F` 21 | pub type LabeledPolynomial = ark_poly_commit::LabeledPolynomial>; 22 | 23 | /// The algebraic holographic proof defined in [CHMMVW19](https://eprint.iacr.org/2019/1047). 24 | /// Currently, this AHP only supports inputs of size one 25 | /// less than a power of 2 (i.e., of the form 2^n - 1). 26 | pub struct AHPForR1CS { 27 | field: PhantomData, 28 | } 29 | 30 | impl AHPForR1CS { 31 | /// The labels for the polynomials output by the AHP indexer. 32 | #[rustfmt::skip] 33 | pub const INDEXER_POLYNOMIALS: [&'static str; 6] = [ 34 | // Polynomials for M 35 | "row", "col", "a_val", "b_val", "c_val", "row_col", 36 | ]; 37 | 38 | /// The labels for the polynomials output by the AHP prover. 39 | #[rustfmt::skip] 40 | pub const PROVER_POLYNOMIALS: [&'static str; 9] = [ 41 | // First sumcheck 42 | "w", "z_a", "z_b", "mask_poly", "t", "g_1", "h_1", 43 | // Second sumcheck 44 | "g_2", "h_2", 45 | ]; 46 | 47 | /// THe linear combinations that are statically known to evaluate to zero. 48 | pub const LC_WITH_ZERO_EVAL: [&'static str; 2] = ["inner_sumcheck", "outer_sumcheck"]; 49 | 50 | pub(crate) fn polynomial_labels() -> impl Iterator { 51 | Self::INDEXER_POLYNOMIALS 52 | .iter() 53 | .chain(&Self::PROVER_POLYNOMIALS) 54 | .map(|s| s.to_string()) 55 | } 56 | 57 | /// Check that the (formatted) public input is of the form 2^n for some integer n. 58 | pub fn num_formatted_public_inputs_is_admissible(num_inputs: usize) -> bool { 59 | num_inputs.count_ones() == 1 60 | } 61 | 62 | /// Check that the (formatted) public input is of the form 2^n for some integer n. 63 | pub fn formatted_public_input_is_admissible(input: &[F]) -> bool { 64 | Self::num_formatted_public_inputs_is_admissible(input.len()) 65 | } 66 | 67 | /// The maximum degree of polynomials produced by the indexer and prover 68 | /// of this protocol. 69 | /// The number of the variables must include the "one" variable. That is, it 70 | /// must be with respect to the number of formatted public inputs. 71 | pub fn max_degree( 72 | num_constraints: usize, 73 | num_variables: usize, 74 | num_non_zero: usize, 75 | ) -> Result { 76 | let padded_matrix_dim = 77 | constraint_systems::padded_matrix_dim(num_variables, num_constraints); 78 | let zk_bound = 1; 79 | let domain_h_size = GeneralEvaluationDomain::::compute_size_of_domain(padded_matrix_dim) 80 | .ok_or(SynthesisError::PolynomialDegreeTooLarge)?; 81 | let domain_k_size = GeneralEvaluationDomain::::compute_size_of_domain(num_non_zero) 82 | .ok_or(SynthesisError::PolynomialDegreeTooLarge)?; 83 | Ok(*[ 84 | 2 * domain_h_size + zk_bound - 2, 85 | 3 * domain_h_size + 2 * zk_bound - 3, // mask_poly 86 | domain_h_size, 87 | domain_h_size, 88 | domain_k_size - 1, 89 | ] 90 | .iter() 91 | .max() 92 | .unwrap()) 93 | } 94 | 95 | /// Get all the strict degree bounds enforced in the AHP. 96 | pub fn get_degree_bounds(info: &indexer::IndexInfo) -> [usize; 2] { 97 | let mut degree_bounds = [0usize; 2]; 98 | let num_constraints = info.num_constraints; 99 | let num_non_zero = info.num_non_zero; 100 | let h_size = GeneralEvaluationDomain::::compute_size_of_domain(num_constraints).unwrap(); 101 | let k_size = GeneralEvaluationDomain::::compute_size_of_domain(num_non_zero).unwrap(); 102 | 103 | degree_bounds[0] = h_size - 2; 104 | degree_bounds[1] = k_size - 2; 105 | degree_bounds 106 | } 107 | 108 | /// Construct the linear combinations that are checked by the AHP. 109 | #[allow(non_snake_case)] 110 | pub fn construct_linear_combinations( 111 | public_input: &[F], 112 | evals: &E, 113 | state: &verifier::VerifierState, 114 | ) -> Result>, Error> 115 | where 116 | E: EvaluationsProvider, 117 | { 118 | let domain_h = state.domain_h; 119 | let domain_k = state.domain_k; 120 | let k_size = domain_k.size_as_field_element(); 121 | 122 | let public_input = constraint_systems::format_public_input(public_input); 123 | if !Self::formatted_public_input_is_admissible(&public_input) { 124 | return Err(Error::InvalidPublicInputLength); 125 | } 126 | let x_domain = GeneralEvaluationDomain::new(public_input.len()) 127 | .ok_or(SynthesisError::PolynomialDegreeTooLarge)?; 128 | 129 | let first_round_msg = state.first_round_msg.unwrap(); 130 | let alpha = first_round_msg.alpha; 131 | let eta_a = first_round_msg.eta_a; 132 | let eta_b = first_round_msg.eta_b; 133 | let eta_c = first_round_msg.eta_c; 134 | 135 | let beta = state.second_round_msg.unwrap().beta; 136 | let gamma = state.gamma.unwrap(); 137 | 138 | let mut linear_combinations = Vec::new(); 139 | 140 | // Outer sumcheck: 141 | let z_b = LinearCombination::new("z_b", vec![(F::one(), "z_b")]); 142 | let g_1 = LinearCombination::new("g_1", vec![(F::one(), "g_1")]); 143 | let t = LinearCombination::new("t", vec![(F::one(), "t")]); 144 | 145 | let r_alpha_at_beta = domain_h.eval_unnormalized_bivariate_lagrange_poly(alpha, beta); 146 | let v_H_at_alpha = domain_h.evaluate_vanishing_polynomial(alpha); 147 | let v_H_at_beta = domain_h.evaluate_vanishing_polynomial(beta); 148 | let v_X_at_beta = x_domain.evaluate_vanishing_polynomial(beta); 149 | 150 | let z_b_at_beta = evals.get_lc_eval(&z_b, beta)?; 151 | let t_at_beta = evals.get_lc_eval(&t, beta)?; 152 | let g_1_at_beta = evals.get_lc_eval(&g_1, beta)?; 153 | 154 | let x_at_beta = x_domain 155 | .evaluate_all_lagrange_coefficients(beta) 156 | .into_iter() 157 | .zip(public_input) 158 | .map(|(l, x)| l * &x) 159 | .fold(F::zero(), |x, y| x + &y); 160 | 161 | #[rustfmt::skip] 162 | let outer_sumcheck = LinearCombination::new( 163 | "outer_sumcheck", 164 | vec![ 165 | (F::one(), "mask_poly".into()), 166 | 167 | (r_alpha_at_beta * (eta_a + eta_c * z_b_at_beta), "z_a".into()), 168 | (r_alpha_at_beta * eta_b * z_b_at_beta, LCTerm::One), 169 | 170 | (-t_at_beta * v_X_at_beta, "w".into()), 171 | (-t_at_beta * x_at_beta, LCTerm::One), 172 | 173 | (-v_H_at_beta, "h_1".into()), 174 | (-beta * g_1_at_beta, LCTerm::One), 175 | ], 176 | ); 177 | debug_assert!(evals.get_lc_eval(&outer_sumcheck, beta)?.is_zero()); 178 | 179 | linear_combinations.push(z_b); 180 | linear_combinations.push(g_1); 181 | linear_combinations.push(t); 182 | linear_combinations.push(outer_sumcheck); 183 | 184 | // Inner sumcheck: 185 | let beta_alpha = beta * alpha; 186 | let g_2 = LinearCombination::new("g_2", vec![(F::one(), "g_2")]); 187 | 188 | let g_2_at_gamma = evals.get_lc_eval(&g_2, gamma)?; 189 | 190 | let v_K_at_gamma = domain_k.evaluate_vanishing_polynomial(gamma); 191 | 192 | let mut a = LinearCombination::new( 193 | "a_poly", 194 | vec![(eta_a, "a_val"), (eta_b, "b_val"), (eta_c, "c_val")], 195 | ); 196 | a *= v_H_at_alpha * v_H_at_beta; 197 | 198 | let mut b = LinearCombination::new( 199 | "denom", 200 | vec![ 201 | (beta_alpha, LCTerm::One), 202 | (-alpha, "row".into()), 203 | (-beta, "col".into()), 204 | (F::one(), "row_col".into()), 205 | ], 206 | ); 207 | b *= gamma * g_2_at_gamma + &(t_at_beta / k_size); 208 | 209 | let mut inner_sumcheck = a; 210 | inner_sumcheck -= &b; 211 | inner_sumcheck -= &LinearCombination::new("h_2", vec![(v_K_at_gamma, "h_2")]); 212 | 213 | inner_sumcheck.label = "inner_sumcheck".into(); 214 | debug_assert!(evals.get_lc_eval(&inner_sumcheck, gamma)?.is_zero()); 215 | 216 | linear_combinations.push(g_2); 217 | linear_combinations.push(inner_sumcheck); 218 | 219 | linear_combinations.sort_by(|a, b| a.label.cmp(&b.label)); 220 | Ok(linear_combinations) 221 | } 222 | } 223 | 224 | /// Abstraction that provides evaluations of (linear combinations of) polynomials 225 | /// 226 | /// Intended to provide a common interface for both the prover and the verifier 227 | /// when constructing linear combinations via `AHPForR1CS::construct_linear_combinations`. 228 | pub trait EvaluationsProvider { 229 | /// Get the evaluation of linear combination `lc` at `point`. 230 | fn get_lc_eval(&self, lc: &LinearCombination, point: F) -> Result; 231 | } 232 | 233 | impl<'a, F: Field> EvaluationsProvider for ark_poly_commit::Evaluations { 234 | fn get_lc_eval(&self, lc: &LinearCombination, point: F) -> Result { 235 | let key = (lc.label.clone(), point); 236 | self.get(&key) 237 | .map(|v| *v) 238 | .ok_or(Error::MissingEval(lc.label.clone())) 239 | } 240 | } 241 | 242 | impl>> EvaluationsProvider for Vec { 243 | fn get_lc_eval(&self, lc: &LinearCombination, point: F) -> Result { 244 | let mut eval = F::zero(); 245 | for (coeff, term) in lc.iter() { 246 | let value = if let LCTerm::PolyLabel(label) = term { 247 | self.iter() 248 | .find(|p| { 249 | let p: &LabeledPolynomial = (*p).borrow(); 250 | p.label() == label 251 | }) 252 | .ok_or(Error::MissingEval(format!( 253 | "Missing {} for {}", 254 | label, lc.label 255 | )))? 256 | .borrow() 257 | .evaluate(&point) 258 | } else { 259 | assert!(term.is_one()); 260 | F::one() 261 | }; 262 | eval += *coeff * value 263 | } 264 | Ok(eval) 265 | } 266 | } 267 | 268 | /// Describes the failure modes of the AHP scheme. 269 | #[derive(Debug)] 270 | pub enum Error { 271 | /// During verification, a required evaluation is missing 272 | MissingEval(String), 273 | /// The number of public inputs is incorrect. 274 | InvalidPublicInputLength, 275 | /// The instance generated during proving does not match that in the index. 276 | InstanceDoesNotMatchIndex, 277 | /// Currently we only support square constraint matrices. 278 | NonSquareMatrix, 279 | /// An error occurred during constraint generation. 280 | ConstraintSystemError(SynthesisError), 281 | } 282 | 283 | impl From for Error { 284 | fn from(other: SynthesisError) -> Self { 285 | Error::ConstraintSystemError(other) 286 | } 287 | } 288 | 289 | /// The derivative of the vanishing polynomial 290 | pub trait UnnormalizedBivariateLagrangePoly { 291 | /// Evaluate the polynomial 292 | fn eval_unnormalized_bivariate_lagrange_poly(&self, x: F, y: F) -> F; 293 | 294 | /// Evaluate over a batch of inputs 295 | fn batch_eval_unnormalized_bivariate_lagrange_poly_with_diff_inputs(&self, x: F) -> Vec; 296 | 297 | /// Evaluate the magic polynomial over `self` 298 | fn batch_eval_unnormalized_bivariate_lagrange_poly_with_same_inputs(&self) -> Vec; 299 | } 300 | 301 | impl UnnormalizedBivariateLagrangePoly for GeneralEvaluationDomain { 302 | fn eval_unnormalized_bivariate_lagrange_poly(&self, x: F, y: F) -> F { 303 | if x != y { 304 | (self.evaluate_vanishing_polynomial(x) - self.evaluate_vanishing_polynomial(y)) 305 | / (x - y) 306 | } else { 307 | self.size_as_field_element() * x.pow(&[(self.size() - 1) as u64]) 308 | } 309 | } 310 | 311 | fn batch_eval_unnormalized_bivariate_lagrange_poly_with_diff_inputs(&self, x: F) -> Vec { 312 | let vanish_x = self.evaluate_vanishing_polynomial(x); 313 | let mut inverses: Vec = self.elements().map(|y| x - y).collect(); 314 | ark_ff::batch_inversion(&mut inverses); 315 | 316 | cfg_iter_mut!(inverses).for_each(|denominator| *denominator *= vanish_x); 317 | inverses 318 | } 319 | 320 | fn batch_eval_unnormalized_bivariate_lagrange_poly_with_same_inputs(&self) -> Vec { 321 | let mut elems: Vec = self 322 | .elements() 323 | .map(|e| e * self.size_as_field_element()) 324 | .collect(); 325 | elems[1..].reverse(); 326 | elems 327 | } 328 | } 329 | 330 | #[cfg(test)] 331 | mod tests { 332 | use super::*; 333 | use ark_bls12_381::Fr; 334 | use ark_ff::{One, UniformRand, Zero}; 335 | use ark_poly::{ 336 | univariate::{DenseOrSparsePolynomial, DensePolynomial}, 337 | Polynomial, UVPolynomial, 338 | }; 339 | 340 | #[test] 341 | fn domain_unnormalized_bivariate_lagrange_poly() { 342 | for domain_size in 1..10 { 343 | let domain = GeneralEvaluationDomain::::new(1 << domain_size).unwrap(); 344 | let manual: Vec<_> = domain 345 | .elements() 346 | .map(|elem| domain.eval_unnormalized_bivariate_lagrange_poly(elem, elem)) 347 | .collect(); 348 | let fast = domain.batch_eval_unnormalized_bivariate_lagrange_poly_with_same_inputs(); 349 | assert_eq!(fast, manual); 350 | } 351 | } 352 | 353 | #[test] 354 | fn domain_unnormalized_bivariate_lagrange_poly_diff_inputs() { 355 | let rng = &mut ark_std::test_rng(); 356 | for domain_size in 1..10 { 357 | let domain = GeneralEvaluationDomain::::new(1 << domain_size).unwrap(); 358 | let x = Fr::rand(rng); 359 | let manual: Vec<_> = domain 360 | .elements() 361 | .map(|y| domain.eval_unnormalized_bivariate_lagrange_poly(x, y)) 362 | .collect(); 363 | let fast = domain.batch_eval_unnormalized_bivariate_lagrange_poly_with_diff_inputs(x); 364 | assert_eq!(fast, manual); 365 | } 366 | } 367 | 368 | #[test] 369 | fn test_summation() { 370 | let rng = &mut ark_std::test_rng(); 371 | let size = 1 << 4; 372 | let domain = GeneralEvaluationDomain::::new(1 << 4).unwrap(); 373 | let size_as_fe = domain.size_as_field_element(); 374 | let poly = DensePolynomial::rand(size, rng); 375 | 376 | let mut sum: Fr = Fr::zero(); 377 | for eval in domain.elements().map(|e| poly.evaluate(&e)) { 378 | sum += eval; 379 | } 380 | let first = poly.coeffs[0] * size_as_fe; 381 | let last = *poly.coeffs.last().unwrap() * size_as_fe; 382 | println!("sum: {:?}", sum); 383 | println!("a_0: {:?}", first); 384 | println!("a_n: {:?}", last); 385 | println!("first + last: {:?}\n", first + last); 386 | assert_eq!(sum, first + last); 387 | } 388 | 389 | #[test] 390 | fn test_alternator_polynomial() { 391 | use ark_poly::Evaluations; 392 | let domain_k = GeneralEvaluationDomain::::new(1 << 4).unwrap(); 393 | let domain_h = GeneralEvaluationDomain::::new(1 << 3).unwrap(); 394 | let domain_h_elems = domain_h 395 | .elements() 396 | .collect::>(); 397 | let alternator_poly_evals = domain_k 398 | .elements() 399 | .map(|e| { 400 | if domain_h_elems.contains(&e) { 401 | Fr::one() 402 | } else { 403 | Fr::zero() 404 | } 405 | }) 406 | .collect(); 407 | let v_k: DenseOrSparsePolynomial<_> = domain_k.vanishing_polynomial().into(); 408 | let v_h: DenseOrSparsePolynomial<_> = domain_h.vanishing_polynomial().into(); 409 | let (divisor, remainder) = v_k.divide_with_q_and_r(&v_h).unwrap(); 410 | assert!(remainder.is_zero()); 411 | println!("Divisor: {:?}", divisor); 412 | println!( 413 | "{:#?}", 414 | divisor 415 | .coeffs 416 | .iter() 417 | .filter_map(|f| if !f.is_zero() { 418 | Some(f.into_repr()) 419 | } else { 420 | None 421 | }) 422 | .collect::>() 423 | ); 424 | 425 | for e in domain_h.elements() { 426 | println!("{:?}", divisor.evaluate(&e)); 427 | } 428 | // Let p = v_K / v_H; 429 | // The alternator polynomial is p * t, where t is defined as 430 | // the LDE of p(h)^{-1} for all h in H. 431 | // 432 | // Because for each h in H, p(h) equals a constant c, we have that t 433 | // is the constant polynomial c^{-1}. 434 | // 435 | // Q: what is the constant c? Why is p(h) constant? What is the easiest 436 | // way to calculate c? 437 | let alternator_poly = 438 | Evaluations::from_vec_and_domain(alternator_poly_evals, domain_k).interpolate(); 439 | let (quotient, remainder) = DenseOrSparsePolynomial::from(alternator_poly.clone()) 440 | .divide_with_q_and_r(&DenseOrSparsePolynomial::from(divisor)) 441 | .unwrap(); 442 | assert!(remainder.is_zero()); 443 | println!("quotient: {:?}", quotient); 444 | println!( 445 | "{:#?}", 446 | quotient 447 | .coeffs 448 | .iter() 449 | .filter_map(|f| if !f.is_zero() { 450 | Some(f.into_repr()) 451 | } else { 452 | None 453 | }) 454 | .collect::>() 455 | ); 456 | 457 | println!("{:?}", alternator_poly); 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /src/ahp/prover.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use crate::ahp::indexer::*; 4 | use crate::ahp::verifier::*; 5 | use crate::ahp::*; 6 | 7 | use crate::ahp::constraint_systems::{ 8 | make_matrices_square_for_prover, pad_input_for_indexer_and_prover, unformat_public_input, 9 | }; 10 | use crate::{ToString, Vec}; 11 | use ark_ff::{Field, PrimeField, Zero}; 12 | use ark_poly::{ 13 | univariate::DensePolynomial, EvaluationDomain, Evaluations as EvaluationsOnDomain, 14 | GeneralEvaluationDomain, Polynomial, UVPolynomial, 15 | }; 16 | use ark_relations::r1cs::{ 17 | ConstraintSynthesizer, ConstraintSystem, OptimizationGoal, SynthesisError, 18 | }; 19 | use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError}; 20 | use ark_std::rand::RngCore; 21 | use ark_std::{ 22 | cfg_into_iter, cfg_iter, cfg_iter_mut, 23 | io::{Read, Write}, 24 | }; 25 | 26 | /// State for the AHP prover. 27 | pub struct ProverState<'a, F: PrimeField> { 28 | formatted_input_assignment: Vec, 29 | witness_assignment: Vec, 30 | /// Az 31 | z_a: Option>, 32 | /// Bz 33 | z_b: Option>, 34 | /// query bound b 35 | zk_bound: usize, 36 | 37 | w_poly: Option>, 38 | mz_polys: Option<(LabeledPolynomial, LabeledPolynomial)>, 39 | 40 | index: &'a Index, 41 | 42 | /// the random values sent by the verifier in the first round 43 | verifier_first_msg: Option>, 44 | 45 | /// the blinding polynomial for the first round 46 | mask_poly: Option>, 47 | 48 | /// domain X, sized for the public input 49 | domain_x: GeneralEvaluationDomain, 50 | 51 | /// domain H, sized for constraints 52 | domain_h: GeneralEvaluationDomain, 53 | 54 | /// domain K, sized for matrix nonzero elements 55 | domain_k: GeneralEvaluationDomain, 56 | } 57 | 58 | impl<'a, F: PrimeField> ProverState<'a, F> { 59 | /// Get the public input. 60 | pub fn public_input(&self) -> Vec { 61 | unformat_public_input(&self.formatted_input_assignment) 62 | } 63 | } 64 | 65 | /// Each prover message that is not a list of oracles is a list of field elements. 66 | #[derive(Clone)] 67 | pub enum ProverMsg { 68 | /// Some rounds, the prover sends only oracles. (This is actually the case for all 69 | /// rounds in Marlin.) 70 | EmptyMessage, 71 | /// Otherwise, it's one or more field elements. 72 | FieldElements(Vec), 73 | } 74 | 75 | impl ark_ff::ToBytes for ProverMsg { 76 | fn write(&self, w: W) -> ark_std::io::Result<()> { 77 | match self { 78 | ProverMsg::EmptyMessage => Ok(()), 79 | ProverMsg::FieldElements(field_elems) => field_elems.write(w), 80 | } 81 | } 82 | } 83 | 84 | impl CanonicalSerialize for ProverMsg { 85 | fn serialize(&self, mut writer: W) -> Result<(), SerializationError> { 86 | let res: Option> = match self { 87 | ProverMsg::EmptyMessage => None, 88 | ProverMsg::FieldElements(v) => Some(v.clone()), 89 | }; 90 | res.serialize(&mut writer) 91 | } 92 | 93 | fn serialized_size(&self) -> usize { 94 | let res: Option> = match self { 95 | ProverMsg::EmptyMessage => None, 96 | ProverMsg::FieldElements(v) => Some(v.clone()), 97 | }; 98 | res.serialized_size() 99 | } 100 | 101 | fn serialize_unchecked(&self, mut writer: W) -> Result<(), SerializationError> { 102 | let res: Option> = match self { 103 | ProverMsg::EmptyMessage => None, 104 | ProverMsg::FieldElements(v) => Some(v.clone()), 105 | }; 106 | res.serialize_unchecked(&mut writer) 107 | } 108 | 109 | fn serialize_uncompressed(&self, mut writer: W) -> Result<(), SerializationError> { 110 | let res: Option> = match self { 111 | ProverMsg::EmptyMessage => None, 112 | ProverMsg::FieldElements(v) => Some(v.clone()), 113 | }; 114 | res.serialize_uncompressed(&mut writer) 115 | } 116 | 117 | fn uncompressed_size(&self) -> usize { 118 | let res: Option> = match self { 119 | ProverMsg::EmptyMessage => None, 120 | ProverMsg::FieldElements(v) => Some(v.clone()), 121 | }; 122 | res.uncompressed_size() 123 | } 124 | } 125 | 126 | impl CanonicalDeserialize for ProverMsg { 127 | fn deserialize(mut reader: R) -> Result { 128 | let res = Option::>::deserialize(&mut reader)?; 129 | 130 | if let Some(res) = res { 131 | Ok(ProverMsg::FieldElements(res)) 132 | } else { 133 | Ok(ProverMsg::EmptyMessage) 134 | } 135 | } 136 | 137 | fn deserialize_unchecked(mut reader: R) -> Result { 138 | let res = Option::>::deserialize_unchecked(&mut reader)?; 139 | 140 | if let Some(res) = res { 141 | Ok(ProverMsg::FieldElements(res)) 142 | } else { 143 | Ok(ProverMsg::EmptyMessage) 144 | } 145 | } 146 | 147 | fn deserialize_uncompressed(mut reader: R) -> Result { 148 | let res = Option::>::deserialize_uncompressed(&mut reader)?; 149 | 150 | if let Some(res) = res { 151 | Ok(ProverMsg::FieldElements(res)) 152 | } else { 153 | Ok(ProverMsg::EmptyMessage) 154 | } 155 | } 156 | } 157 | 158 | /// The first set of prover oracles. 159 | pub struct ProverFirstOracles { 160 | /// The LDE of `w`. 161 | pub w: LabeledPolynomial, 162 | /// The LDE of `Az`. 163 | pub z_a: LabeledPolynomial, 164 | /// The LDE of `Bz`. 165 | pub z_b: LabeledPolynomial, 166 | /// The sum-check hiding polynomial. 167 | pub mask_poly: LabeledPolynomial, 168 | } 169 | 170 | impl ProverFirstOracles { 171 | /// Iterate over the polynomials output by the prover in the first round. 172 | pub fn iter(&self) -> impl Iterator> { 173 | vec![&self.w, &self.z_a, &self.z_b, &self.mask_poly].into_iter() 174 | } 175 | } 176 | 177 | /// The second set of prover oracles. 178 | pub struct ProverSecondOracles { 179 | /// The polynomial `t` that is produced in the first round. 180 | pub t: LabeledPolynomial, 181 | /// The polynomial `g` resulting from the first sumcheck. 182 | pub g_1: LabeledPolynomial, 183 | /// The polynomial `h` resulting from the first sumcheck. 184 | pub h_1: LabeledPolynomial, 185 | } 186 | 187 | impl ProverSecondOracles { 188 | /// Iterate over the polynomials output by the prover in the second round. 189 | pub fn iter(&self) -> impl Iterator> { 190 | vec![&self.t, &self.g_1, &self.h_1].into_iter() 191 | } 192 | } 193 | 194 | /// The third set of prover oracles. 195 | pub struct ProverThirdOracles { 196 | /// The polynomial `g` resulting from the second sumcheck. 197 | pub g_2: LabeledPolynomial, 198 | /// The polynomial `h` resulting from the second sumcheck. 199 | pub h_2: LabeledPolynomial, 200 | } 201 | 202 | impl ProverThirdOracles { 203 | /// Iterate over the polynomials output by the prover in the third round. 204 | pub fn iter(&self) -> impl Iterator> { 205 | vec![&self.g_2, &self.h_2].into_iter() 206 | } 207 | } 208 | 209 | impl AHPForR1CS { 210 | /// Initialize the AHP prover. 211 | pub fn prover_init<'a, C: ConstraintSynthesizer>( 212 | index: &'a Index, 213 | c: C, 214 | ) -> Result, Error> { 215 | let init_time = start_timer!(|| "AHP::Prover::Init"); 216 | 217 | let constraint_time = start_timer!(|| "Generating constraints and witnesses"); 218 | let pcs = ConstraintSystem::new_ref(); 219 | pcs.set_optimization_goal(OptimizationGoal::Weight); 220 | pcs.set_mode(ark_relations::r1cs::SynthesisMode::Prove { 221 | construct_matrices: true, 222 | }); 223 | c.generate_constraints(pcs.clone())?; 224 | end_timer!(constraint_time); 225 | 226 | let padding_time = start_timer!(|| "Padding matrices to make them square"); 227 | pad_input_for_indexer_and_prover(pcs.clone()); 228 | pcs.finalize(); 229 | make_matrices_square_for_prover(pcs.clone()); 230 | end_timer!(padding_time); 231 | 232 | let num_non_zero = index.index_info.num_non_zero; 233 | 234 | let (formatted_input_assignment, witness_assignment, num_constraints) = { 235 | let pcs = pcs.into_inner().unwrap(); 236 | ( 237 | pcs.instance_assignment, 238 | pcs.witness_assignment, 239 | pcs.num_constraints, 240 | ) 241 | }; 242 | 243 | let num_input_variables = formatted_input_assignment.len(); 244 | let num_witness_variables = witness_assignment.len(); 245 | if index.index_info.num_constraints != num_constraints 246 | || num_input_variables + num_witness_variables != index.index_info.num_variables 247 | { 248 | return Err(Error::InstanceDoesNotMatchIndex); 249 | } 250 | 251 | if !Self::formatted_public_input_is_admissible(&formatted_input_assignment) { 252 | return Err(Error::InvalidPublicInputLength); 253 | } 254 | 255 | // Perform matrix multiplications 256 | let inner_prod_fn = |row: &[(F, usize)]| { 257 | let mut acc = F::zero(); 258 | for &(ref coeff, i) in row { 259 | let tmp = if i < num_input_variables { 260 | formatted_input_assignment[i] 261 | } else { 262 | witness_assignment[i - num_input_variables] 263 | }; 264 | 265 | acc += &(if coeff.is_one() { tmp } else { tmp * coeff }); 266 | } 267 | acc 268 | }; 269 | 270 | let eval_z_a_time = start_timer!(|| "Evaluating z_A"); 271 | let z_a = index.a.iter().map(|row| inner_prod_fn(row)).collect(); 272 | end_timer!(eval_z_a_time); 273 | 274 | let eval_z_b_time = start_timer!(|| "Evaluating z_B"); 275 | let z_b = index.b.iter().map(|row| inner_prod_fn(row)).collect(); 276 | end_timer!(eval_z_b_time); 277 | 278 | let zk_bound = 1; // One query is sufficient for our desired soundness 279 | 280 | let domain_h = GeneralEvaluationDomain::new(num_constraints) 281 | .ok_or(SynthesisError::PolynomialDegreeTooLarge)?; 282 | 283 | let domain_k = GeneralEvaluationDomain::new(num_non_zero) 284 | .ok_or(SynthesisError::PolynomialDegreeTooLarge)?; 285 | 286 | let domain_x = GeneralEvaluationDomain::new(num_input_variables) 287 | .ok_or(SynthesisError::PolynomialDegreeTooLarge)?; 288 | 289 | end_timer!(init_time); 290 | 291 | Ok(ProverState { 292 | formatted_input_assignment, 293 | witness_assignment, 294 | z_a: Some(z_a), 295 | z_b: Some(z_b), 296 | w_poly: None, 297 | mz_polys: None, 298 | zk_bound, 299 | index, 300 | verifier_first_msg: None, 301 | mask_poly: None, 302 | domain_h, 303 | domain_k, 304 | domain_x, 305 | }) 306 | } 307 | 308 | /// Output the first round message and the next state. 309 | pub fn prover_first_round<'a, R: RngCore>( 310 | mut state: ProverState<'a, F>, 311 | rng: &mut R, 312 | ) -> Result<(ProverMsg, ProverFirstOracles, ProverState<'a, F>), Error> { 313 | let round_time = start_timer!(|| "AHP::Prover::FirstRound"); 314 | let domain_h = state.domain_h; 315 | let zk_bound = state.zk_bound; 316 | 317 | let v_H = domain_h.vanishing_polynomial().into(); 318 | 319 | let x_time = start_timer!(|| "Computing x polynomial and evals"); 320 | let domain_x = state.domain_x; 321 | let x_poly = EvaluationsOnDomain::from_vec_and_domain( 322 | state.formatted_input_assignment.clone(), 323 | domain_x, 324 | ) 325 | .interpolate(); 326 | let x_evals = domain_h.fft(&x_poly); 327 | end_timer!(x_time); 328 | 329 | let ratio = domain_h.size() / domain_x.size(); 330 | 331 | let mut w_extended = state.witness_assignment.clone(); 332 | w_extended.extend(vec![ 333 | F::zero(); 334 | domain_h.size() 335 | - domain_x.size() 336 | - state.witness_assignment.len() 337 | ]); 338 | 339 | let w_poly_time = start_timer!(|| "Computing w polynomial"); 340 | let w_poly_evals = cfg_into_iter!(0..domain_h.size()) 341 | .map(|k| { 342 | if k % ratio == 0 { 343 | F::zero() 344 | } else { 345 | w_extended[k - (k / ratio) - 1] - &x_evals[k] 346 | } 347 | }) 348 | .collect(); 349 | 350 | let w_poly = &EvaluationsOnDomain::from_vec_and_domain(w_poly_evals, domain_h) 351 | .interpolate() 352 | + &(&DensePolynomial::from_coefficients_slice(&[F::rand(rng)]) * &v_H); 353 | let (w_poly, remainder) = w_poly.divide_by_vanishing_poly(domain_x).unwrap(); 354 | assert!(remainder.is_zero()); 355 | end_timer!(w_poly_time); 356 | 357 | let z_a_poly_time = start_timer!(|| "Computing z_A polynomial"); 358 | let z_a = state.z_a.clone().unwrap(); 359 | let z_a_poly = &EvaluationsOnDomain::from_vec_and_domain(z_a, domain_h).interpolate() 360 | + &(&DensePolynomial::from_coefficients_slice(&[F::rand(rng)]) * &v_H); 361 | end_timer!(z_a_poly_time); 362 | 363 | let z_b_poly_time = start_timer!(|| "Computing z_B polynomial"); 364 | let z_b = state.z_b.clone().unwrap(); 365 | let z_b_poly = &EvaluationsOnDomain::from_vec_and_domain(z_b, domain_h).interpolate() 366 | + &(&DensePolynomial::from_coefficients_slice(&[F::rand(rng)]) * &v_H); 367 | end_timer!(z_b_poly_time); 368 | 369 | let mask_poly_time = start_timer!(|| "Computing mask polynomial"); 370 | let mask_poly_degree = 3 * domain_h.size() + 2 * zk_bound - 3; 371 | let mut mask_poly = DensePolynomial::rand(mask_poly_degree, rng); 372 | 373 | let nh = domain_h.size(); 374 | let upper_bound = mask_poly_degree / nh; 375 | let mut r_0 = F::zero(); 376 | for i in 0..upper_bound + 1 { 377 | r_0 += mask_poly[nh * i]; 378 | } 379 | 380 | mask_poly[0] -= &r_0; 381 | end_timer!(mask_poly_time); 382 | 383 | let msg = ProverMsg::EmptyMessage; 384 | 385 | assert!(w_poly.degree() < domain_h.size() - domain_x.size() + zk_bound); 386 | assert!(z_a_poly.degree() < domain_h.size() + zk_bound); 387 | assert!(z_b_poly.degree() < domain_h.size() + zk_bound); 388 | assert!(mask_poly.degree() <= 3 * domain_h.size() + 2 * zk_bound - 3); 389 | 390 | let w = LabeledPolynomial::new("w".to_string(), w_poly, None, Some(1)); 391 | let z_a = LabeledPolynomial::new("z_a".to_string(), z_a_poly, None, Some(1)); 392 | let z_b = LabeledPolynomial::new("z_b".to_string(), z_b_poly, None, Some(1)); 393 | let mask_poly = 394 | LabeledPolynomial::new("mask_poly".to_string(), mask_poly.clone(), None, None); 395 | 396 | let oracles = ProverFirstOracles { 397 | w: w.clone(), 398 | z_a: z_a.clone(), 399 | z_b: z_b.clone(), 400 | mask_poly: mask_poly.clone(), 401 | }; 402 | 403 | state.w_poly = Some(w); 404 | state.mz_polys = Some((z_a, z_b)); 405 | state.mask_poly = Some(mask_poly); 406 | end_timer!(round_time); 407 | 408 | Ok((msg, oracles, state)) 409 | } 410 | 411 | fn calculate_t<'a>( 412 | matrices: impl Iterator>, 413 | matrix_randomizers: &[F], 414 | input_domain: GeneralEvaluationDomain, 415 | domain_h: GeneralEvaluationDomain, 416 | r_alpha_x_on_h: Vec, 417 | ) -> DensePolynomial { 418 | let mut t_evals_on_h = vec![F::zero(); domain_h.size()]; 419 | for (matrix, eta) in matrices.zip(matrix_randomizers) { 420 | for (r, row) in matrix.iter().enumerate() { 421 | for (coeff, c) in row.iter() { 422 | let index = domain_h.reindex_by_subdomain(input_domain, *c); 423 | t_evals_on_h[index] += *eta * coeff * r_alpha_x_on_h[r]; 424 | } 425 | } 426 | } 427 | EvaluationsOnDomain::from_vec_and_domain(t_evals_on_h, domain_h).interpolate() 428 | } 429 | 430 | /// Output the number of oracles sent by the prover in the first round. 431 | pub fn prover_num_first_round_oracles() -> usize { 432 | 4 433 | } 434 | 435 | /// Output the degree bounds of oracles in the first round. 436 | pub fn prover_first_round_degree_bounds( 437 | _info: &IndexInfo, 438 | ) -> impl Iterator> { 439 | vec![None; 4].into_iter() 440 | } 441 | 442 | /// Output the second round message and the next state. 443 | pub fn prover_second_round<'a, R: RngCore>( 444 | ver_message: &VerifierFirstMsg, 445 | mut state: ProverState<'a, F>, 446 | _r: &mut R, 447 | ) -> (ProverMsg, ProverSecondOracles, ProverState<'a, F>) { 448 | let round_time = start_timer!(|| "AHP::Prover::SecondRound"); 449 | 450 | let domain_h = state.domain_h; 451 | let zk_bound = state.zk_bound; 452 | 453 | let mask_poly = state 454 | .mask_poly 455 | .as_ref() 456 | .expect("ProverState should include mask_poly when prover_second_round is called"); 457 | 458 | let VerifierFirstMsg { 459 | alpha, 460 | eta_a, 461 | eta_b, 462 | eta_c, 463 | } = *ver_message; 464 | 465 | let summed_z_m_poly_time = start_timer!(|| "Compute z_m poly"); 466 | let (z_a_poly, z_b_poly) = state.mz_polys.as_ref().unwrap(); 467 | let z_c_poly = z_a_poly.polynomial() * z_b_poly.polynomial(); 468 | 469 | let mut summed_z_m_coeffs = z_c_poly.coeffs; 470 | // Note: Can't combine these two loops, because z_c_poly has 2x the degree 471 | // of z_a_poly and z_b_poly, so the second loop gets truncated due to 472 | // the `zip`s. 473 | cfg_iter_mut!(summed_z_m_coeffs).for_each(|c| *c *= &eta_c); 474 | cfg_iter_mut!(summed_z_m_coeffs) 475 | .zip(&z_a_poly.polynomial().coeffs) 476 | .zip(&z_b_poly.polynomial().coeffs) 477 | .for_each(|((c, a), b)| *c += &(eta_a * a + &(eta_b * b))); 478 | 479 | let summed_z_m = DensePolynomial::from_coefficients_vec(summed_z_m_coeffs); 480 | end_timer!(summed_z_m_poly_time); 481 | 482 | let r_alpha_x_evals_time = start_timer!(|| "Compute r_alpha_x evals"); 483 | let r_alpha_x_evals = 484 | domain_h.batch_eval_unnormalized_bivariate_lagrange_poly_with_diff_inputs(alpha); 485 | end_timer!(r_alpha_x_evals_time); 486 | 487 | let r_alpha_poly_time = start_timer!(|| "Compute r_alpha_x poly"); 488 | let r_alpha_poly = DensePolynomial::from_coefficients_vec(domain_h.ifft(&r_alpha_x_evals)); 489 | end_timer!(r_alpha_poly_time); 490 | 491 | let t_poly_time = start_timer!(|| "Compute t poly"); 492 | let t_poly = Self::calculate_t( 493 | vec![&state.index.a, &state.index.b, &state.index.c].into_iter(), 494 | &[eta_a, eta_b, eta_c], 495 | state.domain_x, 496 | state.domain_h, 497 | r_alpha_x_evals, 498 | ); 499 | end_timer!(t_poly_time); 500 | 501 | let z_poly_time = start_timer!(|| "Compute z poly"); 502 | 503 | let domain_x = GeneralEvaluationDomain::new(state.formatted_input_assignment.len()) 504 | .ok_or(SynthesisError::PolynomialDegreeTooLarge) 505 | .unwrap(); 506 | let x_poly = EvaluationsOnDomain::from_vec_and_domain( 507 | state.formatted_input_assignment.clone(), 508 | domain_x, 509 | ) 510 | .interpolate(); 511 | let w_poly = state.w_poly.as_ref().unwrap(); 512 | let mut z_poly = w_poly.polynomial().mul_by_vanishing_poly(domain_x); 513 | cfg_iter_mut!(z_poly.coeffs) 514 | .zip(&x_poly.coeffs) 515 | .for_each(|(z, x)| *z += x); 516 | assert!(z_poly.degree() < domain_h.size() + zk_bound); 517 | 518 | end_timer!(z_poly_time); 519 | 520 | let q_1_time = start_timer!(|| "Compute q_1 poly"); 521 | 522 | let mul_domain_size = *[ 523 | mask_poly.len(), 524 | r_alpha_poly.coeffs.len() + summed_z_m.coeffs.len(), 525 | t_poly.coeffs.len() + z_poly.len(), 526 | ] 527 | .iter() 528 | .max() 529 | .unwrap(); 530 | let mul_domain = GeneralEvaluationDomain::new(mul_domain_size) 531 | .expect("field is not smooth enough to construct domain"); 532 | let mut r_alpha_evals = r_alpha_poly.evaluate_over_domain_by_ref(mul_domain); 533 | let summed_z_m_evals = summed_z_m.evaluate_over_domain_by_ref(mul_domain); 534 | let z_poly_evals = z_poly.evaluate_over_domain_by_ref(mul_domain); 535 | let t_poly_m_evals = t_poly.evaluate_over_domain_by_ref(mul_domain); 536 | 537 | cfg_iter_mut!(r_alpha_evals.evals) 538 | .zip(&summed_z_m_evals.evals) 539 | .zip(&z_poly_evals.evals) 540 | .zip(&t_poly_m_evals.evals) 541 | .for_each(|(((a, b), &c), d)| { 542 | *a *= b; 543 | *a -= c * d; 544 | }); 545 | let rhs = r_alpha_evals.interpolate(); 546 | let q_1 = mask_poly.polynomial() + &rhs; 547 | end_timer!(q_1_time); 548 | 549 | let sumcheck_time = start_timer!(|| "Compute sumcheck h and g polys"); 550 | let (h_1, x_g_1) = q_1.divide_by_vanishing_poly(domain_h).unwrap(); 551 | let g_1 = DensePolynomial::from_coefficients_slice(&x_g_1.coeffs[1..]); 552 | end_timer!(sumcheck_time); 553 | 554 | let msg = ProverMsg::EmptyMessage; 555 | 556 | assert!(g_1.degree() <= domain_h.size() - 2); 557 | assert!(h_1.degree() <= 2 * domain_h.size() + 2 * zk_bound - 2); 558 | 559 | let oracles = ProverSecondOracles { 560 | t: LabeledPolynomial::new("t".into(), t_poly, None, None), 561 | g_1: LabeledPolynomial::new("g_1".into(), g_1, Some(domain_h.size() - 2), Some(1)), 562 | h_1: LabeledPolynomial::new("h_1".into(), h_1, None, None), 563 | }; 564 | 565 | state.w_poly = None; 566 | state.verifier_first_msg = Some(*ver_message); 567 | end_timer!(round_time); 568 | 569 | (msg, oracles, state) 570 | } 571 | 572 | /// Output the number of oracles sent by the prover in the second round. 573 | pub fn prover_num_second_round_oracles() -> usize { 574 | 3 575 | } 576 | 577 | /// Output the degree bounds of oracles in the second round. 578 | pub fn prover_second_round_degree_bounds( 579 | info: &IndexInfo, 580 | ) -> impl Iterator> { 581 | let h_domain_size = 582 | GeneralEvaluationDomain::::compute_size_of_domain(info.num_constraints).unwrap(); 583 | 584 | vec![None, Some(h_domain_size - 2), None].into_iter() 585 | } 586 | 587 | /// Output the third round message and the next state. 588 | pub fn prover_third_round<'a, R: RngCore>( 589 | ver_message: &VerifierSecondMsg, 590 | prover_state: ProverState<'a, F>, 591 | _r: &mut R, 592 | ) -> Result<(ProverMsg, ProverThirdOracles), Error> { 593 | let round_time = start_timer!(|| "AHP::Prover::ThirdRound"); 594 | 595 | let ProverState { 596 | index, 597 | verifier_first_msg, 598 | domain_h, 599 | domain_k, 600 | .. 601 | } = prover_state; 602 | 603 | let VerifierFirstMsg { 604 | eta_a, 605 | eta_b, 606 | eta_c, 607 | alpha, 608 | } = verifier_first_msg.expect( 609 | "ProverState should include verifier_first_msg when prover_third_round is called", 610 | ); 611 | 612 | let beta = ver_message.beta; 613 | 614 | let v_H_at_alpha = domain_h.evaluate_vanishing_polynomial(alpha); 615 | let v_H_at_beta = domain_h.evaluate_vanishing_polynomial(beta); 616 | 617 | let v_H_alpha_v_H_beta = v_H_at_alpha * v_H_at_beta; 618 | let eta_a_times_v_H_alpha_v_H_beta = eta_a * v_H_alpha_v_H_beta; 619 | let eta_b_times_v_H_alpha_v_H_beta = eta_b * v_H_alpha_v_H_beta; 620 | let eta_c_times_v_H_alpha_v_H_beta = eta_c * v_H_alpha_v_H_beta; 621 | 622 | let joint_arith = &index.joint_arith; 623 | 624 | let a_poly_time = start_timer!(|| "Computing a poly"); 625 | let a_poly = { 626 | let a = joint_arith.val_a.coeffs(); 627 | let b = joint_arith.val_b.coeffs(); 628 | let c = joint_arith.val_c.coeffs(); 629 | let coeffs: Vec = cfg_iter!(a) 630 | .zip(b) 631 | .zip(c) 632 | .map(|((a, b), c)| { 633 | eta_a_times_v_H_alpha_v_H_beta * a 634 | + eta_b_times_v_H_alpha_v_H_beta * b 635 | + eta_c_times_v_H_alpha_v_H_beta * c 636 | }) 637 | .collect(); 638 | DensePolynomial::from_coefficients_vec(coeffs) 639 | }; 640 | end_timer!(a_poly_time); 641 | 642 | let (row_on_K, col_on_K, row_col_on_K) = ( 643 | &joint_arith.evals_on_K.row, 644 | &joint_arith.evals_on_K.col, 645 | &joint_arith.evals_on_K.row_col, 646 | ); 647 | let b_poly_time = start_timer!(|| "Computing b poly"); 648 | let alpha_beta = alpha * beta; 649 | let b_poly = { 650 | let evals: Vec = cfg_iter!(row_on_K.evals) 651 | .zip(&col_on_K.evals) 652 | .zip(&row_col_on_K.evals) 653 | .map(|((r, c), r_c)| alpha_beta - alpha * r - beta * c + r_c) 654 | .collect(); 655 | EvaluationsOnDomain::from_vec_and_domain(evals, domain_k).interpolate() 656 | }; 657 | end_timer!(b_poly_time); 658 | 659 | let f_evals_time = start_timer!(|| "Computing f evals on K"); 660 | let mut inverses: Vec<_> = cfg_into_iter!(0..domain_k.size()) 661 | .map(|i| (beta - row_on_K[i]) * (alpha - col_on_K[i])) 662 | .collect(); 663 | ark_ff::batch_inversion(&mut inverses); 664 | 665 | let (val_a_on_K, val_b_on_K, val_c_on_K) = ( 666 | &joint_arith.evals_on_K.val_a, 667 | &joint_arith.evals_on_K.val_b, 668 | &joint_arith.evals_on_K.val_c, 669 | ); 670 | let f_evals_on_K: Vec<_> = cfg_into_iter!(0..(domain_k.size())) 671 | .map(|i| { 672 | inverses[i] 673 | * (eta_a_times_v_H_alpha_v_H_beta * val_a_on_K[i] 674 | + eta_b_times_v_H_alpha_v_H_beta * val_b_on_K[i] 675 | + eta_c_times_v_H_alpha_v_H_beta * val_c_on_K[i]) 676 | }) 677 | .collect(); 678 | end_timer!(f_evals_time); 679 | 680 | let f_poly_time = start_timer!(|| "Computing f poly"); 681 | let f = EvaluationsOnDomain::from_vec_and_domain(f_evals_on_K, domain_k).interpolate(); 682 | end_timer!(f_poly_time); 683 | 684 | let h_2_poly_time = start_timer!(|| "Computing sumcheck h poly"); 685 | let h_2 = (&a_poly - &(&b_poly * &f)) 686 | .divide_by_vanishing_poly(domain_k) 687 | .unwrap() 688 | .0; 689 | end_timer!(h_2_poly_time); 690 | drop(a_poly); 691 | drop(b_poly); 692 | let g_2 = DensePolynomial::from_coefficients_slice(&f.coeffs[1..]); 693 | drop(f); 694 | 695 | let msg = ProverMsg::EmptyMessage; 696 | 697 | assert!(h_2.degree() <= domain_k.size() - 2); 698 | assert!(g_2.degree() <= domain_k.size() - 2); 699 | let oracles = ProverThirdOracles { 700 | g_2: LabeledPolynomial::new("g_2".to_string(), g_2, Some(domain_k.size() - 2), None), 701 | h_2: LabeledPolynomial::new("h_2".to_string(), h_2, None, None), 702 | }; 703 | end_timer!(round_time); 704 | 705 | Ok((msg, oracles)) 706 | } 707 | 708 | /// Output the number of oracles sent by the prover in the third round. 709 | pub fn prover_num_third_round_oracles() -> usize { 710 | 2 711 | } 712 | 713 | /// Output the degree bounds of oracles in the third round. 714 | pub fn prover_third_round_degree_bounds( 715 | info: &IndexInfo, 716 | ) -> impl Iterator> { 717 | let num_non_zero = info.num_non_zero; 718 | let k_size = GeneralEvaluationDomain::::compute_size_of_domain(num_non_zero).unwrap(); 719 | 720 | vec![Some(k_size - 2), None].into_iter() 721 | } 722 | } 723 | -------------------------------------------------------------------------------- /src/ahp/verifier.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use crate::ahp::indexer::IndexInfo; 4 | use crate::ahp::*; 5 | use ark_std::rand::RngCore; 6 | 7 | use ark_ff::PrimeField; 8 | use ark_poly::{EvaluationDomain, GeneralEvaluationDomain}; 9 | use ark_poly_commit::QuerySet; 10 | 11 | /// State of the AHP verifier 12 | pub struct VerifierState { 13 | pub(crate) domain_h: GeneralEvaluationDomain, 14 | pub(crate) domain_k: GeneralEvaluationDomain, 15 | 16 | pub(crate) first_round_msg: Option>, 17 | pub(crate) second_round_msg: Option>, 18 | 19 | pub(crate) gamma: Option, 20 | } 21 | 22 | /// First message of the verifier. 23 | #[derive(Copy, Clone)] 24 | pub struct VerifierFirstMsg { 25 | /// Query for the random polynomial. 26 | pub alpha: F, 27 | /// Randomizer for the lincheck for `A`. 28 | pub eta_a: F, 29 | /// Randomizer for the lincheck for `B`. 30 | pub eta_b: F, 31 | /// Randomizer for the lincheck for `C`. 32 | pub eta_c: F, 33 | } 34 | 35 | /// Second verifier message. 36 | #[derive(Copy, Clone)] 37 | pub struct VerifierSecondMsg { 38 | /// Query for the second round of polynomials. 39 | pub beta: F, 40 | } 41 | 42 | impl AHPForR1CS { 43 | /// Output the first message and next round state. 44 | pub fn verifier_first_round( 45 | index_info: IndexInfo, 46 | rng: &mut R, 47 | ) -> Result<(VerifierFirstMsg, VerifierState), Error> { 48 | if index_info.num_constraints != index_info.num_variables { 49 | return Err(Error::NonSquareMatrix); 50 | } 51 | 52 | let domain_h = GeneralEvaluationDomain::new(index_info.num_constraints) 53 | .ok_or(SynthesisError::PolynomialDegreeTooLarge)?; 54 | 55 | let domain_k = GeneralEvaluationDomain::new(index_info.num_non_zero) 56 | .ok_or(SynthesisError::PolynomialDegreeTooLarge)?; 57 | 58 | let alpha = domain_h.sample_element_outside_domain(rng); 59 | let eta_a = F::rand(rng); 60 | let eta_b = F::rand(rng); 61 | let eta_c = F::rand(rng); 62 | 63 | let msg = VerifierFirstMsg { 64 | alpha, 65 | eta_a, 66 | eta_b, 67 | eta_c, 68 | }; 69 | 70 | let new_state = VerifierState { 71 | domain_h, 72 | domain_k, 73 | first_round_msg: Some(msg), 74 | second_round_msg: None, 75 | gamma: None, 76 | }; 77 | 78 | Ok((msg, new_state)) 79 | } 80 | 81 | /// Output the second message and next round state. 82 | pub fn verifier_second_round( 83 | mut state: VerifierState, 84 | rng: &mut R, 85 | ) -> (VerifierSecondMsg, VerifierState) { 86 | let beta = state.domain_h.sample_element_outside_domain(rng); 87 | let msg = VerifierSecondMsg { beta }; 88 | state.second_round_msg = Some(msg); 89 | 90 | (msg, state) 91 | } 92 | 93 | /// Output the third message and next round state. 94 | pub fn verifier_third_round( 95 | mut state: VerifierState, 96 | rng: &mut R, 97 | ) -> VerifierState { 98 | state.gamma = Some(F::rand(rng)); 99 | state 100 | } 101 | 102 | /// Output the query state and next round state. 103 | pub fn verifier_query_set<'a, R: RngCore>( 104 | state: VerifierState, 105 | _: &'a mut R, 106 | ) -> (QuerySet, VerifierState) { 107 | let beta = state.second_round_msg.unwrap().beta; 108 | 109 | let gamma = state.gamma.unwrap(); 110 | 111 | let mut query_set = QuerySet::new(); 112 | // For the first linear combination 113 | // Outer sumcheck test: 114 | // s(beta) + r(alpha, beta) * (sum_M eta_M z_M(beta)) - t(beta) * z(beta) 115 | // = h_1(beta) * v_H(beta) + beta * g_1(beta) 116 | // 117 | // Note that z is the interpolation of x || w, so it equals x + v_X * w 118 | // We also use an optimization: instead of explicitly calculating z_c, we 119 | // use the "virtual oracle" z_b * z_c 120 | // 121 | // LinearCombination::new( 122 | // outer_sumcheck 123 | // vec![ 124 | // (F::one(), "mask_poly".into()), 125 | // 126 | // (r_alpha_at_beta * (eta_a + eta_c * z_b_at_beta), "z_a".into()), 127 | // (r_alpha_at_beta * eta_b * z_b_at_beta, LCTerm::One), 128 | // 129 | // (-t_at_beta * v_X_at_beta, "w".into()), 130 | // (-t_at_beta * x_at_beta, LCTerm::One), 131 | // 132 | // (-v_H_at_beta, "h_1".into()), 133 | // (-beta * g_1_at_beta, LCTerm::One), 134 | // ], 135 | // ) 136 | // LinearCombination::new("z_b", vec![(F::one(), z_b)]) 137 | // LinearCombination::new("g_1", vec![(F::one(), g_1)], rhs::new(g_1_at_beta)) 138 | // LinearCombination::new("t", vec![(F::one(), t)]) 139 | query_set.insert(("g_1".into(), ("beta".into(), beta))); 140 | query_set.insert(("z_b".into(), ("beta".into(), beta))); 141 | query_set.insert(("t".into(), ("beta".into(), beta))); 142 | query_set.insert(("outer_sumcheck".into(), ("beta".into(), beta))); 143 | 144 | // For the second linear combination 145 | // Inner sumcheck test: 146 | // h_2(gamma) * v_K(gamma) 147 | // = a(gamma) - b(gamma) * (gamma g_2(gamma) + t(beta) / |K|) 148 | // 149 | // where 150 | // a(X) := sum_M (eta_M v_H(beta) v_H(alpha) val_M(X)) 151 | // b(X) := (beta - row(X)) (alpha - col(X)) 152 | // 153 | // LinearCombination::new("g_2", vec![(F::one(), g_2)]); 154 | // 155 | // LinearCombination::new( 156 | // "denom".into(), 157 | // vec![ 158 | // (alpha * beta, LCTerm::One), 159 | // (-alpha, "row"), 160 | // (-beta, "col"), 161 | // (F::one(), "row_col"), 162 | // ]); 163 | // 164 | // LinearCombination::new( 165 | // "a_poly".into(), 166 | // vec![ 167 | // (eta_a * "a_val".into()), 168 | // (eta_b * "b_val".into()), 169 | // (eta_c * "c_val".into()), 170 | // ], 171 | // ) 172 | // 173 | // let v_H_at_alpha = domain_h.evaluate_vanishing_polynomial(alpha); 174 | // let v_H_at_beta = domain_h.evaluate_vanishing_polynomial(beta); 175 | // let v_K_at_gamma = domain_k.evaluate_vanishing_polynomial(gamma); 176 | // 177 | // let a_poly_lc *= v_H_at_alpha * v_H_at_beta; 178 | // let b_lc = denom 179 | // let h_lc = LinearCombination::new("b_poly", vec![(v_K_at_gamma, "h_2")]); 180 | // 181 | // // This LC is the only one that is evaluated: 182 | // let inner_sumcheck = a_poly_lc - (b_lc * (gamma * &g_2_at_gamma + &(t_at_beta / &k_size))) - h_lc 183 | // main_lc.set_label("inner_sumcheck"); 184 | query_set.insert(("g_2".into(), ("gamma".into(), gamma))); 185 | query_set.insert(("inner_sumcheck".into(), ("gamma".into(), gamma))); 186 | 187 | (query_set, state) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/data_structures.rs: -------------------------------------------------------------------------------- 1 | use crate::ahp::indexer::*; 2 | use crate::ahp::prover::ProverMsg; 3 | use crate::Vec; 4 | use ark_ff::PrimeField; 5 | use ark_poly::univariate::DensePolynomial; 6 | use ark_poly_commit::{BatchLCProof, PolynomialCommitment}; 7 | use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError}; 8 | use ark_std::{ 9 | format, 10 | io::{Read, Write}, 11 | }; 12 | 13 | /* ************************************************************************* */ 14 | /* ************************************************************************* */ 15 | /* ************************************************************************* */ 16 | 17 | /// The universal public parameters for the argument system. 18 | pub type UniversalSRS = >>::UniversalParams; 19 | 20 | /* ************************************************************************* */ 21 | /* ************************************************************************* */ 22 | /* ************************************************************************* */ 23 | 24 | /// Verification key for a specific index (i.e., R1CS matrices). 25 | #[derive(CanonicalSerialize, CanonicalDeserialize)] 26 | pub struct IndexVerifierKey>> { 27 | /// Stores information about the size of the index, as well as its field of 28 | /// definition. 29 | pub index_info: IndexInfo, 30 | /// Commitments to the indexed polynomials. 31 | pub index_comms: Vec, 32 | /// The verifier key for this index, trimmed from the universal SRS. 33 | pub verifier_key: PC::VerifierKey, 34 | } 35 | 36 | impl>> ark_ff::ToBytes 37 | for IndexVerifierKey 38 | { 39 | fn write(&self, mut w: W) -> ark_std::io::Result<()> { 40 | self.index_info.write(&mut w)?; 41 | self.index_comms.write(&mut w) 42 | } 43 | } 44 | 45 | impl>> Clone 46 | for IndexVerifierKey 47 | { 48 | fn clone(&self) -> Self { 49 | Self { 50 | index_comms: self.index_comms.clone(), 51 | index_info: self.index_info.clone(), 52 | verifier_key: self.verifier_key.clone(), 53 | } 54 | } 55 | } 56 | 57 | impl>> IndexVerifierKey { 58 | /// Iterate over the commitments to indexed polynomials in `self`. 59 | pub fn iter(&self) -> impl Iterator { 60 | self.index_comms.iter() 61 | } 62 | } 63 | 64 | /* ************************************************************************* */ 65 | /* ************************************************************************* */ 66 | /* ************************************************************************* */ 67 | 68 | /// Proving key for a specific index (i.e., R1CS matrices). 69 | #[derive(CanonicalSerialize, CanonicalDeserialize)] 70 | pub struct IndexProverKey>> { 71 | /// The index verifier key. 72 | pub index_vk: IndexVerifierKey, 73 | /// The randomness for the index polynomial commitments. 74 | pub index_comm_rands: Vec, 75 | /// The index itself. 76 | pub index: Index, 77 | /// The committer key for this index, trimmed from the universal SRS. 78 | pub committer_key: PC::CommitterKey, 79 | } 80 | 81 | impl>> Clone for IndexProverKey 82 | where 83 | PC::Commitment: Clone, 84 | { 85 | fn clone(&self) -> Self { 86 | Self { 87 | index_vk: self.index_vk.clone(), 88 | index_comm_rands: self.index_comm_rands.clone(), 89 | index: self.index.clone(), 90 | committer_key: self.committer_key.clone(), 91 | } 92 | } 93 | } 94 | 95 | /* ************************************************************************* */ 96 | /* ************************************************************************* */ 97 | /* ************************************************************************* */ 98 | 99 | /// A zkSNARK proof. 100 | #[derive(CanonicalSerialize, CanonicalDeserialize)] 101 | pub struct Proof>> { 102 | /// Commitments to the polynomials produced by the AHP prover. 103 | pub commitments: Vec>, 104 | /// Evaluations of these polynomials. 105 | pub evaluations: Vec, 106 | /// The field elements sent by the prover. 107 | pub prover_messages: Vec>, 108 | /// An evaluation proof from the polynomial commitment. 109 | pub pc_proof: BatchLCProof, PC>, 110 | } 111 | 112 | impl>> Proof { 113 | /// Construct a new proof. 114 | pub fn new( 115 | commitments: Vec>, 116 | evaluations: Vec, 117 | prover_messages: Vec>, 118 | pc_proof: BatchLCProof, PC>, 119 | ) -> Self { 120 | Self { 121 | commitments, 122 | evaluations, 123 | prover_messages, 124 | pc_proof, 125 | } 126 | } 127 | 128 | /// Prints information about the size of the proof. 129 | pub fn print_size_info(&self) { 130 | use ark_poly_commit::PCCommitment; 131 | 132 | let mut num_comms_without_degree_bounds = 0; 133 | let mut num_comms_with_degree_bounds = 0; 134 | let mut size_bytes_comms_without_degree_bounds = 0; 135 | let mut size_bytes_comms_with_degree_bounds = 0; 136 | for c in self.commitments.iter().flat_map(|c| c) { 137 | if !c.has_degree_bound() { 138 | num_comms_without_degree_bounds += 1; 139 | size_bytes_comms_without_degree_bounds += c.serialized_size(); 140 | } else { 141 | num_comms_with_degree_bounds += 1; 142 | size_bytes_comms_with_degree_bounds += c.serialized_size(); 143 | } 144 | } 145 | 146 | let proofs: Vec = self.pc_proof.proof.clone().into(); 147 | let num_proofs = proofs.len(); 148 | let size_bytes_proofs = self.pc_proof.proof.serialized_size(); 149 | 150 | let num_evals = self.evaluations.len(); 151 | let evals_size_in_bytes = self.evaluations.serialized_size(); 152 | let num_prover_messages: usize = self 153 | .prover_messages 154 | .iter() 155 | .map(|v| match v { 156 | ProverMsg::EmptyMessage => 0, 157 | ProverMsg::FieldElements(elems) => elems.len(), 158 | }) 159 | .sum(); 160 | let prover_msg_size_in_bytes = self.prover_messages.serialized_size(); 161 | let arg_size = self.serialized_size(); 162 | let stats = format!( 163 | "Argument size in bytes: {}\n\n\ 164 | Number of commitments without degree bounds: {}\n\ 165 | Size (in bytes) of commitments without degree bounds: {}\n\ 166 | Number of commitments with degree bounds: {}\n\ 167 | Size (in bytes) of commitments with degree bounds: {}\n\n\ 168 | Number of evaluation proofs: {}\n\ 169 | Size (in bytes) of evaluation proofs: {}\n\n\ 170 | Number of evaluations: {}\n\ 171 | Size (in bytes) of evaluations: {}\n\n\ 172 | Number of field elements in prover messages: {}\n\ 173 | Size (in bytes) of prover message: {}\n", 174 | arg_size, 175 | num_comms_without_degree_bounds, 176 | size_bytes_comms_without_degree_bounds, 177 | num_comms_with_degree_bounds, 178 | size_bytes_comms_with_degree_bounds, 179 | num_proofs, 180 | size_bytes_proofs, 181 | num_evals, 182 | evals_size_in_bytes, 183 | num_prover_messages, 184 | prover_msg_size_in_bytes, 185 | ); 186 | add_to_trace!(|| "Statistics about proof", || stats); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::ahp::Error as AHPError; 2 | 3 | /// A `enum` specifying the possible failure modes of the `SNARK`. 4 | #[derive(Debug)] 5 | pub enum Error { 6 | /// The index is too large for the universal public parameters. 7 | IndexTooLarge, 8 | /// There was an error in the underlying holographic IOP. 9 | AHPError(AHPError), 10 | /// There was an error in the underlying polynomial commitment. 11 | PolynomialCommitmentError(E), 12 | } 13 | 14 | impl From for Error { 15 | fn from(err: AHPError) -> Self { 16 | Error::AHPError(err) 17 | } 18 | } 19 | 20 | impl Error { 21 | /// Convert an error in the underlying polynomial commitment scheme 22 | /// to a `Error`. 23 | pub fn from_pc_err(err: E) -> Self { 24 | Error::PolynomialCommitmentError(err) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | //! A crate for the Marlin preprocessing zkSNARK for R1CS. 3 | //! 4 | //! # Note 5 | //! 6 | //! Currently, Marlin only supports R1CS instances where the number of inputs 7 | //! is the same as the number of constraints (i.e., where the constraint 8 | //! matrices are square). Furthermore, Marlin only supports instances where the 9 | //! public inputs are of size one less than a power of 2 (i.e., 2^n - 1). 10 | #![deny(unused_import_braces, unused_qualifications, trivial_casts)] 11 | #![deny(trivial_numeric_casts, private_in_public)] 12 | #![deny(stable_features, unreachable_pub, non_shorthand_field_patterns)] 13 | #![deny(unused_attributes, unused_imports, unused_mut, missing_docs)] 14 | #![deny(renamed_and_removed_lints, stable_features, unused_allocation)] 15 | #![deny(unused_comparisons, bare_trait_objects, unused_must_use, const_err)] 16 | #![forbid(unsafe_code)] 17 | 18 | #[macro_use] 19 | extern crate ark_std; 20 | 21 | use ark_ff::{to_bytes, PrimeField, UniformRand}; 22 | use ark_poly::{univariate::DensePolynomial, EvaluationDomain, GeneralEvaluationDomain}; 23 | use ark_poly_commit::Evaluations; 24 | use ark_poly_commit::{LabeledCommitment, PCUniversalParams, PolynomialCommitment}; 25 | use ark_relations::r1cs::ConstraintSynthesizer; 26 | use ark_std::rand::RngCore; 27 | 28 | use ark_std::{ 29 | collections::BTreeMap, 30 | format, 31 | marker::PhantomData, 32 | string::{String, ToString}, 33 | vec, 34 | vec::Vec, 35 | }; 36 | 37 | #[cfg(not(feature = "std"))] 38 | macro_rules! eprintln { 39 | () => {}; 40 | ($($arg: tt)*) => {}; 41 | } 42 | 43 | /// Implements a Fiat-Shamir based Rng that allows one to incrementally update 44 | /// the seed based on new messages in the proof transcript. 45 | pub mod rng; 46 | use rng::FiatShamirRng; 47 | pub use rng::SimpleHashFiatShamirRng; 48 | 49 | mod error; 50 | pub use error::*; 51 | 52 | mod data_structures; 53 | pub use data_structures::*; 54 | 55 | /// Implements an Algebraic Holographic Proof (AHP) for the R1CS indexed relation. 56 | pub mod ahp; 57 | pub use ahp::AHPForR1CS; 58 | use ahp::EvaluationsProvider; 59 | 60 | #[cfg(test)] 61 | mod test; 62 | 63 | /// The compiled argument system. 64 | pub struct Marlin>, FS: FiatShamirRng>( 65 | #[doc(hidden)] PhantomData, 66 | #[doc(hidden)] PhantomData, 67 | #[doc(hidden)] PhantomData, 68 | ); 69 | 70 | impl>, FS: FiatShamirRng> 71 | Marlin 72 | { 73 | /// The personalization string for this protocol. Used to personalize the 74 | /// Fiat-Shamir rng. 75 | pub const PROTOCOL_NAME: &'static [u8] = b"MARLIN-2019"; 76 | 77 | /// Generate the universal prover and verifier keys for the 78 | /// argument system. 79 | pub fn universal_setup( 80 | num_constraints: usize, 81 | num_variables: usize, 82 | num_non_zero: usize, 83 | rng: &mut R, 84 | ) -> Result, Error> { 85 | let max_degree = AHPForR1CS::::max_degree(num_constraints, num_variables, num_non_zero)?; 86 | let setup_time = start_timer!(|| { 87 | format!( 88 | "Marlin::UniversalSetup with max_degree {}, computed for a maximum of {} constraints, {} vars, {} non_zero", 89 | max_degree, num_constraints, num_variables, num_non_zero, 90 | ) 91 | }); 92 | 93 | let srs = PC::setup(max_degree, None, rng).map_err(Error::from_pc_err); 94 | end_timer!(setup_time); 95 | srs 96 | } 97 | 98 | /// Generate the index-specific (i.e., circuit-specific) prover and verifier 99 | /// keys. This is a deterministic algorithm that anyone can rerun. 100 | pub fn index>( 101 | srs: &UniversalSRS, 102 | c: C, 103 | ) -> Result<(IndexProverKey, IndexVerifierKey), Error> { 104 | let index_time = start_timer!(|| "Marlin::Index"); 105 | 106 | // TODO: Add check that c is in the correct mode. 107 | let index = AHPForR1CS::index(c)?; 108 | if srs.max_degree() < index.max_degree() { 109 | Err(Error::IndexTooLarge)?; 110 | } 111 | 112 | let coeff_support = AHPForR1CS::get_degree_bounds(&index.index_info); 113 | // Marlin only needs degree 2 random polynomials 114 | let supported_hiding_bound = 1; 115 | let (committer_key, verifier_key) = PC::trim( 116 | &srs, 117 | index.max_degree(), 118 | supported_hiding_bound, 119 | Some(&coeff_support), 120 | ) 121 | .map_err(Error::from_pc_err)?; 122 | 123 | let commit_time = start_timer!(|| "Commit to index polynomials"); 124 | let (index_comms, index_comm_rands): (_, _) = 125 | PC::commit(&committer_key, index.iter(), None).map_err(Error::from_pc_err)?; 126 | end_timer!(commit_time); 127 | 128 | let index_comms = index_comms 129 | .into_iter() 130 | .map(|c| c.commitment().clone()) 131 | .collect(); 132 | let index_vk = IndexVerifierKey { 133 | index_info: index.index_info, 134 | index_comms, 135 | verifier_key, 136 | }; 137 | 138 | let index_pk = IndexProverKey { 139 | index, 140 | index_comm_rands, 141 | index_vk: index_vk.clone(), 142 | committer_key, 143 | }; 144 | 145 | end_timer!(index_time); 146 | 147 | Ok((index_pk, index_vk)) 148 | } 149 | 150 | /// Create a zkSNARK asserting that the constraint system is satisfied. 151 | pub fn prove, R: RngCore>( 152 | index_pk: &IndexProverKey, 153 | c: C, 154 | zk_rng: &mut R, 155 | ) -> Result, Error> { 156 | let prover_time = start_timer!(|| "Marlin::Prover"); 157 | // Add check that c is in the correct mode. 158 | 159 | let prover_init_state = AHPForR1CS::prover_init(&index_pk.index, c)?; 160 | let public_input = prover_init_state.public_input(); 161 | let mut fs_rng = FS::initialize( 162 | &to_bytes![&Self::PROTOCOL_NAME, &index_pk.index_vk, &public_input].unwrap(), 163 | ); 164 | 165 | // -------------------------------------------------------------------- 166 | // First round 167 | 168 | let (prover_first_msg, prover_first_oracles, prover_state) = 169 | AHPForR1CS::prover_first_round(prover_init_state, zk_rng)?; 170 | 171 | let first_round_comm_time = start_timer!(|| "Committing to first round polys"); 172 | let (first_comms, first_comm_rands) = PC::commit( 173 | &index_pk.committer_key, 174 | prover_first_oracles.iter(), 175 | Some(zk_rng), 176 | ) 177 | .map_err(Error::from_pc_err)?; 178 | end_timer!(first_round_comm_time); 179 | 180 | fs_rng.absorb(&to_bytes![first_comms, prover_first_msg].unwrap()); 181 | 182 | let (verifier_first_msg, verifier_state) = 183 | AHPForR1CS::verifier_first_round(index_pk.index_vk.index_info, &mut fs_rng)?; 184 | // -------------------------------------------------------------------- 185 | 186 | // -------------------------------------------------------------------- 187 | // Second round 188 | 189 | let (prover_second_msg, prover_second_oracles, prover_state) = 190 | AHPForR1CS::prover_second_round(&verifier_first_msg, prover_state, zk_rng); 191 | 192 | let second_round_comm_time = start_timer!(|| "Committing to second round polys"); 193 | let (second_comms, second_comm_rands) = PC::commit( 194 | &index_pk.committer_key, 195 | prover_second_oracles.iter(), 196 | Some(zk_rng), 197 | ) 198 | .map_err(Error::from_pc_err)?; 199 | end_timer!(second_round_comm_time); 200 | 201 | fs_rng.absorb(&to_bytes![second_comms, prover_second_msg].unwrap()); 202 | 203 | let (verifier_second_msg, verifier_state) = 204 | AHPForR1CS::verifier_second_round(verifier_state, &mut fs_rng); 205 | // -------------------------------------------------------------------- 206 | 207 | // -------------------------------------------------------------------- 208 | // Third round 209 | let (prover_third_msg, prover_third_oracles) = 210 | AHPForR1CS::prover_third_round(&verifier_second_msg, prover_state, zk_rng)?; 211 | 212 | let third_round_comm_time = start_timer!(|| "Committing to third round polys"); 213 | let (third_comms, third_comm_rands) = PC::commit( 214 | &index_pk.committer_key, 215 | prover_third_oracles.iter(), 216 | Some(zk_rng), 217 | ) 218 | .map_err(Error::from_pc_err)?; 219 | end_timer!(third_round_comm_time); 220 | 221 | fs_rng.absorb(&to_bytes![third_comms, prover_third_msg].unwrap()); 222 | 223 | let verifier_state = AHPForR1CS::verifier_third_round(verifier_state, &mut fs_rng); 224 | // -------------------------------------------------------------------- 225 | 226 | // Gather prover polynomials in one vector. 227 | let polynomials: Vec<_> = index_pk 228 | .index 229 | .iter() 230 | .chain(prover_first_oracles.iter()) 231 | .chain(prover_second_oracles.iter()) 232 | .chain(prover_third_oracles.iter()) 233 | .collect(); 234 | 235 | // Gather commitments in one vector. 236 | #[rustfmt::skip] 237 | let commitments = vec![ 238 | first_comms.iter().map(|p| p.commitment().clone()).collect(), 239 | second_comms.iter().map(|p| p.commitment().clone()).collect(), 240 | third_comms.iter().map(|p| p.commitment().clone()).collect(), 241 | ]; 242 | let labeled_comms: Vec<_> = index_pk 243 | .index_vk 244 | .iter() 245 | .cloned() 246 | .zip(&AHPForR1CS::::INDEXER_POLYNOMIALS) 247 | .map(|(c, l)| LabeledCommitment::new(l.to_string(), c, None)) 248 | .chain(first_comms.iter().cloned()) 249 | .chain(second_comms.iter().cloned()) 250 | .chain(third_comms.iter().cloned()) 251 | .collect(); 252 | 253 | // Gather commitment randomness together. 254 | let comm_rands: Vec = index_pk 255 | .index_comm_rands 256 | .clone() 257 | .into_iter() 258 | .chain(first_comm_rands) 259 | .chain(second_comm_rands) 260 | .chain(third_comm_rands) 261 | .collect(); 262 | 263 | // Compute the AHP verifier's query set. 264 | let (query_set, verifier_state) = 265 | AHPForR1CS::verifier_query_set(verifier_state, &mut fs_rng); 266 | let lc_s = AHPForR1CS::construct_linear_combinations( 267 | &public_input, 268 | &polynomials, 269 | &verifier_state, 270 | )?; 271 | 272 | let eval_time = start_timer!(|| "Evaluating linear combinations over query set"); 273 | let mut evaluations = Vec::new(); 274 | for (label, (_, point)) in &query_set { 275 | let lc = lc_s 276 | .iter() 277 | .find(|lc| &lc.label == label) 278 | .ok_or(ahp::Error::MissingEval(label.to_string()))?; 279 | let eval = polynomials.get_lc_eval(&lc, *point)?; 280 | if !AHPForR1CS::::LC_WITH_ZERO_EVAL.contains(&lc.label.as_ref()) { 281 | evaluations.push((label.to_string(), eval)); 282 | } 283 | } 284 | 285 | evaluations.sort_by(|a, b| a.0.cmp(&b.0)); 286 | let evaluations = evaluations.into_iter().map(|x| x.1).collect::>(); 287 | end_timer!(eval_time); 288 | 289 | fs_rng.absorb(&evaluations); 290 | let opening_challenge: F = u128::rand(&mut fs_rng).into(); 291 | 292 | let pc_proof = PC::open_combinations( 293 | &index_pk.committer_key, 294 | &lc_s, 295 | polynomials, 296 | &labeled_comms, 297 | &query_set, 298 | opening_challenge, 299 | &comm_rands, 300 | Some(zk_rng), 301 | ) 302 | .map_err(Error::from_pc_err)?; 303 | 304 | // Gather prover messages together. 305 | let prover_messages = vec![prover_first_msg, prover_second_msg, prover_third_msg]; 306 | 307 | let proof = Proof::new(commitments, evaluations, prover_messages, pc_proof); 308 | proof.print_size_info(); 309 | end_timer!(prover_time); 310 | Ok(proof) 311 | } 312 | 313 | /// Verify that a proof for the constrain system defined by `C` asserts that 314 | /// all constraints are satisfied. 315 | pub fn verify( 316 | index_vk: &IndexVerifierKey, 317 | public_input: &[F], 318 | proof: &Proof, 319 | rng: &mut R, 320 | ) -> Result> { 321 | let verifier_time = start_timer!(|| "Marlin::Verify"); 322 | 323 | let public_input = { 324 | let domain_x = GeneralEvaluationDomain::::new(public_input.len() + 1).unwrap(); 325 | 326 | let mut unpadded_input = public_input.to_vec(); 327 | unpadded_input.resize( 328 | core::cmp::max(public_input.len(), domain_x.size() - 1), 329 | F::zero(), 330 | ); 331 | 332 | unpadded_input 333 | }; 334 | 335 | let mut fs_rng = 336 | FS::initialize(&to_bytes![&Self::PROTOCOL_NAME, &index_vk, &public_input].unwrap()); 337 | 338 | // -------------------------------------------------------------------- 339 | // First round 340 | 341 | let first_comms = &proof.commitments[0]; 342 | fs_rng.absorb(&to_bytes![first_comms, proof.prover_messages[0]].unwrap()); 343 | 344 | let (_, verifier_state) = 345 | AHPForR1CS::verifier_first_round(index_vk.index_info, &mut fs_rng)?; 346 | // -------------------------------------------------------------------- 347 | 348 | // -------------------------------------------------------------------- 349 | // Second round 350 | let second_comms = &proof.commitments[1]; 351 | fs_rng.absorb(&to_bytes![second_comms, proof.prover_messages[1]].unwrap()); 352 | 353 | let (_, verifier_state) = AHPForR1CS::verifier_second_round(verifier_state, &mut fs_rng); 354 | // -------------------------------------------------------------------- 355 | 356 | // -------------------------------------------------------------------- 357 | // Third round 358 | let third_comms = &proof.commitments[2]; 359 | fs_rng.absorb(&to_bytes![third_comms, proof.prover_messages[2]].unwrap()); 360 | 361 | let verifier_state = AHPForR1CS::verifier_third_round(verifier_state, &mut fs_rng); 362 | // -------------------------------------------------------------------- 363 | 364 | // Collect degree bounds for commitments. Indexed polynomials have *no* 365 | // degree bounds because we know the committed index polynomial has the 366 | // correct degree. 367 | let index_info = index_vk.index_info; 368 | let degree_bounds = vec![None; index_vk.index_comms.len()] 369 | .into_iter() 370 | .chain(AHPForR1CS::prover_first_round_degree_bounds(&index_info)) 371 | .chain(AHPForR1CS::prover_second_round_degree_bounds(&index_info)) 372 | .chain(AHPForR1CS::prover_third_round_degree_bounds(&index_info)) 373 | .collect::>(); 374 | 375 | // Gather commitments in one vector. 376 | let commitments: Vec<_> = index_vk 377 | .iter() 378 | .chain(first_comms) 379 | .chain(second_comms) 380 | .chain(third_comms) 381 | .cloned() 382 | .zip(AHPForR1CS::::polynomial_labels()) 383 | .zip(degree_bounds) 384 | .map(|((c, l), d)| LabeledCommitment::new(l, c, d)) 385 | .collect(); 386 | 387 | let (query_set, verifier_state) = 388 | AHPForR1CS::verifier_query_set(verifier_state, &mut fs_rng); 389 | 390 | fs_rng.absorb(&proof.evaluations); 391 | let opening_challenge: F = u128::rand(&mut fs_rng).into(); 392 | 393 | let mut evaluations = Evaluations::new(); 394 | let mut evaluation_labels = Vec::new(); 395 | for (poly_label, (_, point)) in query_set.iter().cloned() { 396 | if AHPForR1CS::::LC_WITH_ZERO_EVAL.contains(&poly_label.as_ref()) { 397 | evaluations.insert((poly_label, point), F::zero()); 398 | } else { 399 | evaluation_labels.push((poly_label, point)); 400 | } 401 | } 402 | evaluation_labels.sort_by(|a, b| a.0.cmp(&b.0)); 403 | for (q, eval) in evaluation_labels.into_iter().zip(&proof.evaluations) { 404 | evaluations.insert(q, *eval); 405 | } 406 | 407 | let lc_s = AHPForR1CS::construct_linear_combinations( 408 | &public_input, 409 | &evaluations, 410 | &verifier_state, 411 | )?; 412 | 413 | let evaluations_are_correct = PC::check_combinations( 414 | &index_vk.verifier_key, 415 | &lc_s, 416 | &commitments, 417 | &query_set, 418 | &evaluations, 419 | &proof.pc_proof, 420 | opening_challenge, 421 | rng, 422 | ) 423 | .map_err(Error::from_pc_err)?; 424 | 425 | if !evaluations_are_correct { 426 | eprintln!("PC::Check failed"); 427 | } 428 | end_timer!(verifier_time, || format!( 429 | " PC::Check for AHP Verifier linear equations: {}", 430 | evaluations_are_correct 431 | )); 432 | Ok(evaluations_are_correct) 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /src/rng.rs: -------------------------------------------------------------------------------- 1 | use crate::Vec; 2 | use ark_ff::{FromBytes, ToBytes}; 3 | use ark_std::convert::From; 4 | use ark_std::marker::PhantomData; 5 | use ark_std::rand::{RngCore, SeedableRng}; 6 | use digest::Digest; 7 | 8 | /// An RNG suitable for Fiat-Shamir transforms 9 | pub trait FiatShamirRng: RngCore { 10 | /// Create a new `Self` with an initial input 11 | fn initialize<'a, T: 'a + ToBytes>(initial_input: &'a T) -> Self; 12 | /// Absorb new inputs into state 13 | fn absorb<'a, T: 'a + ToBytes>(&mut self, new_input: &'a T); 14 | } 15 | 16 | /// A simple `FiatShamirRng` that refreshes its seed by hashing together the previous seed 17 | /// and the new seed material. 18 | pub struct SimpleHashFiatShamirRng { 19 | r: R, 20 | seed: [u8; 32], 21 | #[doc(hidden)] 22 | digest: PhantomData, 23 | } 24 | 25 | impl RngCore for SimpleHashFiatShamirRng { 26 | #[inline] 27 | fn next_u32(&mut self) -> u32 { 28 | self.r.next_u32() 29 | } 30 | 31 | #[inline] 32 | fn next_u64(&mut self) -> u64 { 33 | self.r.next_u64() 34 | } 35 | 36 | #[inline] 37 | fn fill_bytes(&mut self, dest: &mut [u8]) { 38 | self.r.fill_bytes(dest); 39 | } 40 | 41 | #[inline] 42 | fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), ark_std::rand::Error> { 43 | Ok(self.r.fill_bytes(dest)) 44 | } 45 | } 46 | 47 | impl FiatShamirRng for SimpleHashFiatShamirRng 48 | where 49 | R::Seed: From<[u8; 32]>, 50 | { 51 | /// Create a new `Self` by initializing with a fresh seed. 52 | /// `self.seed = H(initial_input)`. 53 | #[inline] 54 | fn initialize<'a, T: 'a + ToBytes>(initial_input: &'a T) -> Self { 55 | let mut bytes = Vec::new(); 56 | initial_input 57 | .write(&mut bytes) 58 | .expect("failed to convert to bytes"); 59 | let seed = FromBytes::read(D::digest(&bytes).as_ref()).expect("failed to get [u8; 32]"); 60 | let r = R::from_seed(::from(seed)); 61 | Self { 62 | r, 63 | seed: seed, 64 | digest: PhantomData, 65 | } 66 | } 67 | 68 | /// Refresh `self.seed` with new material. Achieved by setting 69 | /// `self.seed = H(new_input || self.seed)`. 70 | #[inline] 71 | fn absorb<'a, T: 'a + ToBytes>(&mut self, new_input: &'a T) { 72 | let mut bytes = Vec::new(); 73 | new_input 74 | .write(&mut bytes) 75 | .expect("failed to convert to bytes"); 76 | bytes.extend_from_slice(&self.seed); 77 | self.seed = FromBytes::read(D::digest(&bytes).as_ref()).expect("failed to get [u8; 32]"); 78 | self.r = R::from_seed(::from(self.seed)); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::Field; 2 | use ark_relations::{ 3 | lc, 4 | r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}, 5 | }; 6 | use ark_std::marker::PhantomData; 7 | 8 | #[derive(Copy, Clone)] 9 | struct Circuit { 10 | a: Option, 11 | b: Option, 12 | num_constraints: usize, 13 | num_variables: usize, 14 | } 15 | 16 | impl ConstraintSynthesizer for Circuit { 17 | fn generate_constraints( 18 | self, 19 | cs: ConstraintSystemRef, 20 | ) -> Result<(), SynthesisError> { 21 | let a = cs.new_witness_variable(|| self.a.ok_or(SynthesisError::AssignmentMissing))?; 22 | let b = cs.new_witness_variable(|| self.b.ok_or(SynthesisError::AssignmentMissing))?; 23 | let c = cs.new_input_variable(|| { 24 | let mut a = self.a.ok_or(SynthesisError::AssignmentMissing)?; 25 | let b = self.b.ok_or(SynthesisError::AssignmentMissing)?; 26 | 27 | a.mul_assign(&b); 28 | Ok(a) 29 | })?; 30 | let d = cs.new_input_variable(|| { 31 | let mut a = self.a.ok_or(SynthesisError::AssignmentMissing)?; 32 | let b = self.b.ok_or(SynthesisError::AssignmentMissing)?; 33 | 34 | a.mul_assign(&b); 35 | a.mul_assign(&b); 36 | Ok(a) 37 | })?; 38 | 39 | for _ in 0..(self.num_variables - 3) { 40 | let _ = cs.new_witness_variable(|| self.a.ok_or(SynthesisError::AssignmentMissing))?; 41 | } 42 | 43 | for _ in 0..(self.num_constraints - 1) { 44 | cs.enforce_constraint(lc!() + a, lc!() + b, lc!() + c)?; 45 | } 46 | cs.enforce_constraint(lc!() + c, lc!() + b, lc!() + d)?; 47 | 48 | Ok(()) 49 | } 50 | } 51 | 52 | #[derive(Clone)] 53 | /// Define a constraint system that would trigger outlining. 54 | struct OutlineTestCircuit { 55 | field_phantom: PhantomData, 56 | } 57 | 58 | impl ConstraintSynthesizer for OutlineTestCircuit { 59 | fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { 60 | // This program checks if the input elements are between 0 and 9. 61 | // 62 | // Note that this constraint system is neither the most intuitive way nor 63 | // the most efficient way for such a task. It is for testing purposes, 64 | // as we want to trigger the outlining. 65 | // 66 | let mut inputs = Vec::new(); 67 | for i in 0..5 { 68 | inputs.push(cs.new_input_variable(|| Ok(F::from(i as u128)))?); 69 | } 70 | 71 | for i in 0..5 { 72 | let mut total_count_for_this_input = cs.new_lc(lc!()).unwrap(); 73 | 74 | for bucket in 0..10 { 75 | let count_increment_for_this_bucket = 76 | cs.new_witness_variable(|| Ok(F::from(i == bucket)))?; 77 | 78 | total_count_for_this_input = cs 79 | .new_lc( 80 | lc!() 81 | + (F::one(), total_count_for_this_input) 82 | + (F::one(), count_increment_for_this_bucket.clone()), 83 | ) 84 | .unwrap(); 85 | 86 | // Only when `input[i]` equals `bucket` can `count_increment_for_this_bucket` be nonzero. 87 | // 88 | // A malicious prover can make `count_increment_for_this_bucket` neither 0 nor 1. 89 | // But the constraint on `total_count_for_this_input` will reject such case. 90 | // 91 | // At a high level, only one of the `count_increment_for_this_bucket` among all the buckets 92 | // could be nonzero, which equals `total_count_for_this_input`. Thus, by checking whether 93 | // `total_count_for_this_input` is 1, we know this input number is in the range. 94 | // 95 | cs.enforce_constraint( 96 | lc!() + (F::one(), inputs[i].clone()) 97 | - (F::from(bucket as u128), ark_relations::r1cs::Variable::One), 98 | lc!() + (F::one(), count_increment_for_this_bucket), 99 | lc!(), 100 | )?; 101 | } 102 | 103 | // Enforce `total_count_for_this_input` to be one. 104 | cs.enforce_constraint( 105 | lc!(), 106 | lc!(), 107 | lc!() + (F::one(), total_count_for_this_input.clone()) 108 | - (F::one(), ark_relations::r1cs::Variable::One), 109 | )?; 110 | } 111 | 112 | Ok(()) 113 | } 114 | } 115 | 116 | mod marlin { 117 | use super::*; 118 | use crate::{Marlin, SimpleHashFiatShamirRng}; 119 | 120 | use ark_bls12_381::{Bls12_381, Fr}; 121 | use ark_ff::UniformRand; 122 | use ark_poly::univariate::DensePolynomial; 123 | use ark_poly_commit::marlin_pc::MarlinKZG10; 124 | use ark_std::ops::MulAssign; 125 | use blake2::Blake2s; 126 | use rand_chacha::ChaChaRng; 127 | 128 | type MultiPC = MarlinKZG10>; 129 | type FS = SimpleHashFiatShamirRng; 130 | type MarlinInst = Marlin; 131 | 132 | fn test_circuit(num_constraints: usize, num_variables: usize) { 133 | let rng = &mut ark_std::test_rng(); 134 | 135 | let universal_srs = MarlinInst::universal_setup(100, 25, 300, rng).unwrap(); 136 | 137 | for _ in 0..100 { 138 | let a = Fr::rand(rng); 139 | let b = Fr::rand(rng); 140 | let mut c = a; 141 | c.mul_assign(&b); 142 | let mut d = c; 143 | d.mul_assign(&b); 144 | 145 | let circ = Circuit { 146 | a: Some(a), 147 | b: Some(b), 148 | num_constraints, 149 | num_variables, 150 | }; 151 | 152 | let (index_pk, index_vk) = MarlinInst::index(&universal_srs, circ.clone()).unwrap(); 153 | println!("Called index"); 154 | 155 | let proof = MarlinInst::prove(&index_pk, circ, rng).unwrap(); 156 | println!("Called prover"); 157 | 158 | assert!(MarlinInst::verify(&index_vk, &[c, d], &proof, rng).unwrap()); 159 | println!("Called verifier"); 160 | println!("\nShould not verify (i.e. verifier messages should print below):"); 161 | assert!(!MarlinInst::verify(&index_vk, &[a, a], &proof, rng).unwrap()); 162 | } 163 | } 164 | 165 | #[test] 166 | fn prove_and_verify_with_tall_matrix_big() { 167 | let num_constraints = 100; 168 | let num_variables = 25; 169 | 170 | test_circuit(num_constraints, num_variables); 171 | } 172 | 173 | #[test] 174 | fn prove_and_verify_with_tall_matrix_small() { 175 | let num_constraints = 26; 176 | let num_variables = 25; 177 | 178 | test_circuit(num_constraints, num_variables); 179 | } 180 | 181 | #[test] 182 | fn prove_and_verify_with_squat_matrix_big() { 183 | let num_constraints = 25; 184 | let num_variables = 100; 185 | 186 | test_circuit(num_constraints, num_variables); 187 | } 188 | 189 | #[test] 190 | fn prove_and_verify_with_squat_matrix_small() { 191 | let num_constraints = 25; 192 | let num_variables = 26; 193 | 194 | test_circuit(num_constraints, num_variables); 195 | } 196 | 197 | #[test] 198 | fn prove_and_verify_with_square_matrix() { 199 | let num_constraints = 25; 200 | let num_variables = 25; 201 | 202 | test_circuit(num_constraints, num_variables); 203 | } 204 | 205 | #[test] 206 | /// Test on a constraint system that will trigger outlining. 207 | fn prove_and_test_outlining() { 208 | let rng = &mut ark_std::test_rng(); 209 | 210 | let universal_srs = MarlinInst::universal_setup(150, 150, 150, rng).unwrap(); 211 | 212 | let circ = OutlineTestCircuit { 213 | field_phantom: PhantomData, 214 | }; 215 | 216 | let (index_pk, index_vk) = MarlinInst::index(&universal_srs, circ.clone()).unwrap(); 217 | println!("Called index"); 218 | 219 | let proof = MarlinInst::prove(&index_pk, circ, rng).unwrap(); 220 | println!("Called prover"); 221 | 222 | let mut inputs = Vec::new(); 223 | for i in 0..5 { 224 | inputs.push(Fr::from(i as u128)); 225 | } 226 | 227 | assert!(MarlinInst::verify(&index_vk, &inputs, &proof, rng).unwrap()); 228 | println!("Called verifier"); 229 | } 230 | } 231 | --------------------------------------------------------------------------------