├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yml │ └── linkify_changelog.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── SECURITY.md ├── ark-bcs ├── Cargo.toml └── src │ ├── bcs │ ├── constraints │ │ ├── mod.rs │ │ ├── proof.rs │ │ ├── transcript.rs │ │ └── verifier.rs │ ├── mod.rs │ ├── prover.rs │ ├── simulation_transcript.rs │ ├── tests │ │ ├── constraints │ │ │ ├── mock.rs │ │ │ └── mod.rs │ │ ├── mock.rs │ │ └── mod.rs │ ├── transcript.rs │ └── verifier.rs │ ├── iop │ ├── bookkeeper.rs │ ├── constraints │ │ ├── message.rs │ │ ├── mod.rs │ │ └── oracles.rs │ ├── message.rs │ ├── mod.rs │ ├── oracles.rs │ ├── prover.rs │ └── verifier.rs │ ├── ldt │ ├── constraints │ │ ├── mod.rs │ │ └── rl_ldt.rs │ ├── mod.rs │ └── rl_ldt.rs │ ├── lib.rs │ ├── prelude.rs │ ├── test_utils │ └── mod.rs │ └── tracer.rs ├── rustfmt.toml ├── scripts └── linkify_changelog.py └── sumcheck ├── Cargo.toml └── src ├── constraints.rs ├── lib.rs ├── protocol.rs ├── test_util.rs └── vp.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 | - main 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@v3 18 | - name: Install Rust 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: nightly 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@v3 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 | - uses: actions/cache@v3 54 | with: 55 | path: | 56 | ~/.cargo/registry 57 | ~/.cargo/git 58 | target 59 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 60 | 61 | - name: Check examples 62 | uses: actions-rs/cargo@v1 63 | with: 64 | command: check 65 | args: --examples --workspace 66 | 67 | - name: Check examples with all features on stable 68 | uses: actions-rs/cargo@v1 69 | with: 70 | command: check 71 | args: --examples --all-features --workspace 72 | if: matrix.rust == 'stable' 73 | 74 | - name: Check benchmarks on nightly 75 | uses: actions-rs/cargo@v1 76 | with: 77 | command: check 78 | args: --all-features --examples --workspace --benches 79 | if: matrix.rust == 'nightly' 80 | 81 | - name: Test 82 | uses: actions-rs/cargo@v1 83 | with: 84 | command: test 85 | args: "--workspace --all-features" 86 | 87 | check_no_std: 88 | name: Check no_std 89 | runs-on: ubuntu-latest 90 | steps: 91 | - name: Checkout 92 | uses: actions/checkout@v3 93 | 94 | - name: Install Rust (${{ matrix.rust }}) 95 | uses: actions-rs/toolchain@v1 96 | with: 97 | toolchain: stable 98 | target: aarch64-unknown-none 99 | override: true 100 | 101 | - name: Check 102 | uses: actions-rs/cargo@v1 103 | with: 104 | command: check 105 | args: --no-default-features --examples --workspace --exclude *_example --target aarch64-unknown-none 106 | 107 | - name: Build 108 | uses: actions-rs/cargo@v1 109 | with: 110 | command: build 111 | args: --no-default-features --workspace --exclude *_example --target aarch64-unknown-none 112 | -------------------------------------------------------------------------------- /.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 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | */Cargo.lock 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # some ide 14 | .idea 15 | .vscode 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | *The library is still under heavy development, and thus the API is highly unstable. 2 | Changelog will be updated when first release is published.* 3 | 4 | --- 5 | 6 | ## Pending 7 | 8 | ### Breaking changes 9 | 10 | ### Features 11 | 12 | 13 | ### Improvements 14 | 15 | 16 | ### Bug fixes 17 | 18 | 19 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | # NOTE TO MAINTAINERS 3 | # REPLACE $REPO_NAME AND $DEFAULT_BRANCH 4 | # And check .github/PULL_REQUEST_TEMPLATE's default branch 5 | 6 | Thank you for considering making contributions to `arkworks-rs/$REPO_NAME`! 7 | 8 | Contributing to this repo can be done in several forms, such as participating in discussion or proposing code changes. 9 | To ensure a smooth workflow for all contributors, the following general procedure for contributing has been established: 10 | 11 | 1) Either open or find an issue you'd like to help with 12 | 2) Participate in thoughtful discussion on that issue 13 | 3) If you would like to contribute: 14 | * If the issue is a feature proposal, ensure that the proposal has been accepted 15 | * Ensure that nobody else has already begun working on this issue. 16 | If they have, please try to contact them to collaborate 17 | * If nobody has been assigned for the issue and you would like to work on it, make a comment on the issue to inform the community of your intentions to begin work. (So we can avoid duplication of efforts) 18 | * We suggest using standard Github best practices for contributing: fork the repo, branch from the HEAD of `$DEFAULT_BRANCH`, make some commits on your branch, and submit a PR from the branch to `$DEFAULT_BRANCH`. 19 | More detail on this is below 20 | * Be sure to include a relevant change log entry in the Pending section of CHANGELOG.md (see file for log format) 21 | * If the change is breaking, we may add migration instructions. 22 | 23 | Note that for very small or clear problems (such as typos), or well isolated improvements, it is not required to an open issue to submit a PR. 24 | But be aware that for more complex problems/features touching multiple parts of the codebase, if a PR is opened before an adequate design discussion has taken place in a github issue, that PR runs a larger likelihood of being rejected. 25 | 26 | Looking for a good place to start contributing? How about checking out some good first issues 27 | 28 | ## Branch Structure 29 | 30 | `$REPO_NAME` has its default branch as `$DEFAULT_BRANCH`, which is where PRs are merged into. Releases will be periodically made, on no set schedule. 31 | All other branches should be assumed to be miscellaneous feature development branches. 32 | 33 | All downstream users of the library should be using tagged versions of the library pulled from cargo. 34 | 35 | ## How to work on a fork 36 | Please skip this section if you're familiar with contributing to opensource github projects. 37 | 38 | First fork the repo from the github UI, and clone it locally. 39 | Then in the repo, you want to add the repo you forked from as a new remote. You do this as: 40 | ```bash 41 | git remote add upstream git@github.com:arkworks-rs/$REPO_NAME.git 42 | ``` 43 | 44 | Then the way you make code contributions is to first think of a branch name that describes your change. 45 | Then do the following: 46 | ```bash 47 | git checkout $DEFAULT_BRANCH 48 | git pull upstream $DEFAULT_BRANCH 49 | git checkout -b $NEW_BRANCH_NAME 50 | ``` 51 | and then work as normal on that branch, and pull request to upstream master when you're done =) 52 | 53 | ## Updating documentation 54 | 55 | All PRs should aim to leave the code more documented than it started with. 56 | Please don't assume that its easy to infer what the code is doing, 57 | as that is almost always not the case for these complex protocols. 58 | (Even when you understand the paper!) 59 | 60 | Its often very useful to describe what is the high level view of what a code block is doing, 61 | and either refer to the relevant section of a paper or include a short proof/argument for why it makes sense before the actual logic. 62 | 63 | ## Performance improvements 64 | 65 | All performance improvements should be accompanied with benchmarks improving, or otherwise have it be clear that things have improved. 66 | For some areas of the codebase, performance roughly follows the number of field multiplications, but there are also many areas where 67 | hard to predict low level system effects such as cache locality and superscalar operations become important for performance. 68 | Thus performance can often become very non-intuitive / diverge from minimizing the number of arithmetic operations. -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "ark-bcs", 4 | "sumcheck", 5 | ] 6 | 7 | [profile.release] 8 | opt-level = 3 9 | lto = "thin" 10 | incremental = true 11 | panic = 'abort' 12 | 13 | [profile.bench] 14 | opt-level = 3 15 | debug = false 16 | rpath = false 17 | lto = "thin" 18 | incremental = true 19 | debug-assertions = false 20 | 21 | [profile.dev] 22 | opt-level = 0 23 | panic = 'abort' 24 | 25 | [profile.test] 26 | opt-level = 3 27 | lto = "thin" 28 | incremental = true 29 | debug-assertions = true 30 | debug = true 31 | 32 | [patch.crates-io] 33 | ark-crypto-primitives = { git = "https://github.com/arkworks-rs/crypto-primitives", branch = "main" } 34 | ark-sponge = { git = "https://github.com/arkworks-rs/sponge" } 35 | ark-r1cs-std = { git = "https://github.com/arkworks-rs/r1cs-std" } 36 | ark-ec = { git = "https://github.com/arkworks-rs/algebra" } 37 | ark-ff = { git = "https://github.com/arkworks-rs/algebra" } 38 | ark-poly = { git = "https://github.com/arkworks-rs/algebra" } 39 | ark-bls12-381 = { git = "https://github.com/arkworks-rs/curves" } 40 | ark-bls12-377 = { git = "https://github.com/arkworks-rs/curves" } 41 | ark-ed-on-bls12-381 = { git = "https://github.com/arkworks-rs/curves" } 42 | ark-serialize = { git = "https://github.com/arkworks-rs/algebra" } 43 | ark-std = { git = "https://github.com/arkworks-rs/std" } 44 | ark-snark = { git = "https://github.com/arkworks-rs/snark" } 45 | ark-ldt = { git = "https://github.com/arkworks-rs/ldt" } -------------------------------------------------------------------------------- /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 |

arkworks::bcs

2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 | 14 | `ark-bcs` is a Rust library that provides implementations of public coin RS-IOP and BCS Transform. This library is 15 | released under the MIT License and the Apache v2 License (see [License](#license)). 16 | 17 | **WARNING:** This is an academic prototype, and in particular has not received careful code review. This implementation 18 | is NOT ready for production use. 19 | 20 | ## Overview 21 | 22 | An RS-IOP is an interactive protocol where prover can send message oracles with degree bound. This library provides an 23 | interface of public coin RS-IOP protocol, an efficient implementation of LDT to enforce degree bound, and a BCS 24 | transformation algorithm to convert RS-IOP to non-interactive succinct proof. 25 | 26 | This implementation uses public-coin IOP assumption that all verifier messages are sampled uniformly at random, and all 27 | verification logic can be delayed to query and decision phase. 28 | 29 | `ark-bcs` differs from [BCS Paper](https://eprint.iacr.org/2016/116) in several aspects: 30 | 31 | - Instead of explicitly using a hash chain, this implementation uses `CryptographicSponge` 32 | in [`ark-sponge`](https://github.com/arkworks-rs/sponge/) as random oracle. 33 | - This implementation has low-degree test built-in, and can handle RS-IOP. 34 | - Multiple oracles with same evaluation domain share a merkle tree and be submitted in one round, which greatly reduces 35 | verification overhead and number of constraints. 36 | - Each leaf of an low-degree oracle is a coset instead of an individual field element, which significantly reduces 37 | merkle tree overhead on FRI query. 38 | 39 | ## Build Guide 40 | 41 | The library compiles on the `stable` toolchain of the Rust compiler. To install the latest version of Rust, first 42 | install `rustup` by following the instructions [here](https://rustup.rs/), or via your platform's package manager. 43 | Once `rustup` is installed, install the Rust toolchain by invoking: 44 | 45 | ```bash 46 | rustup install stable 47 | ``` 48 | 49 | After that, use `cargo` (the standard Rust build tool) to build the library: 50 | 51 | ```bash 52 | git clone https://github.com/arkworks-rs/bcs.git 53 | cd bcs 54 | cargo build --release 55 | ``` 56 | 57 | This library comes with some unit and integration tests. Run these tests with: 58 | 59 | ```bash 60 | cargo test 61 | ``` 62 | 63 | ## License 64 | 65 | This library is licensed under either of the following licenses, at your discretion. 66 | 67 | * [Apache License Version 2.0](LICENSE-APACHE) 68 | * [MIT License](LICENSE-MIT) 69 | 70 | Unless you explicitly state otherwise, any contribution that you submit to this library shall be dual licensed as 71 | above (as defined in the Apache v2 License), without any additional terms or conditions. 72 | 73 | ## Reference papers 74 | 75 | [Aurora: Transparent Succinct Arguments for R1CS][bcrsvw19]
76 | Eli Ben-Sasson, Alessandro Chiesa, Michael Riabzev, Nicholas Spooner, Madars Virza, Nicholas P. Ward 77 | 78 | [Fast Reed-Solomon Interactive Oracle Proofs of Proximity][bbhr17]
79 | Eli Ben-Sasson, Iddo Bentov, Ynon Horesh, Michael Riabzev 80 | 81 | [Interactive Oracle Proofs][bcs16]
82 | Eli Ben-Sasson, Alessandro Chiesa, Nicolas Spooner 83 | 84 | 85 | [bcs16]: https://eprint.iacr.org/2016/116 86 | 87 | [bcrsvw19]: https://eprint.iacr.org/2018/828 88 | 89 | [bbhr17]: https://eccc.weizmann.ac.il/report/2017/134/ 90 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /ark-bcs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ark-bcs" 3 | version = "0.3.0" 4 | authors = [ 5 | "arkworks contributors" 6 | ] 7 | description = "A library for Interactive Oracle Proof System and BCS Transform" 8 | repository = "https://github.com/arkworks-rs/ark-bcs" 9 | documentation = "https://docs.rs/ark-bcs" 10 | keywords = ["cryptography"] 11 | categories = ["cryptography"] 12 | include = ["Cargo.toml", "src", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] 13 | license = "MIT/Apache-2.0" 14 | edition = "2018" 15 | 16 | [dependencies] 17 | ark-serialize = { version = "^0.3.0", default-features = false, features = ["derive"] } 18 | ark-ff = { version = "^0.3.0", default-features = false } 19 | ark-poly = { version = "^0.3.0", default-features = false } 20 | ark-sponge = { version = "^0.3.0", default-features = false } 21 | ark-crypto-primitives = { version = "^0.3.0", default-features = false } 22 | ark-std = { version = "^0.3.0", default-features = false } 23 | ark-ldt = { version = "^0.1.0", default-features = false } 24 | 25 | ark-relations = { version = "^0.3.0", default-features = false, optional = true } 26 | ark-r1cs-std = { version = "^0.3.1", default-features = false, optional = true } 27 | 28 | tracing = { version = "0.1", default-features = false, features = ["attributes"] } 29 | derivative = { version = "2.0", features = ["use_core"] } 30 | hashbrown = "0.11.2" 31 | 32 | [dev-dependencies] 33 | ark-ed-on-bls12-381 = { version = "^0.3.0", default-features = false } 34 | ark-bls12-381 = { version = "^0.3.0", default-features = false, features = ["curve"] } 35 | ark-bls12-377 = { version = "^0.3.0", default-features = false, features = ["curve"] } 36 | 37 | [features] 38 | default = ["std", "parallel"] 39 | std = ["ark-serialize/std", "ark-ff/std", "ark-poly/std", "ark-sponge/std", "ark-crypto-primitives/std", 40 | "ark-std/std", "ark-relations/std", "ark-r1cs-std/std", "ark-ldt/std"] 41 | r1cs = ["ark-relations", "ark-r1cs-std", "ark-sponge/r1cs", "ark-crypto-primitives/r1cs", "ark-ldt/r1cs"] 42 | parallel = ["std", "ark-ff/parallel", "ark-poly/parallel", "ark-std/parallel"] 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /ark-bcs/src/bcs/constraints/mod.rs: -------------------------------------------------------------------------------- 1 | use ark_crypto_primitives::{ 2 | crh::TwoToOneCRHSchemeGadget, 3 | merkle_tree::{constraints::ConfigGadget, Config}, 4 | CRHSchemeGadget, 5 | }; 6 | use ark_ff::PrimeField; 7 | 8 | /// Defines BCS prover constraints and proof variable. 9 | pub mod proof; 10 | /// Defines BCS transcript gadget. 11 | pub mod transcript; 12 | /// Defines BCS proof verifier gadget. 13 | pub mod verifier; 14 | 15 | /// Hash parameters constraints for merkle tree. 16 | pub struct MTHashParametersVar> { 17 | /// parameter for leaf hash function 18 | pub leaf_params: <>::LeafHash as CRHSchemeGadget< 19 | ::LeafHash, 20 | CF, 21 | >>::ParametersVar, 22 | /// parameter for two-to-one hash function 23 | pub inner_params: <>::TwoToOneHash as TwoToOneCRHSchemeGadget< 24 | ::TwoToOneHash, 25 | CF, 26 | >>::ParametersVar, 27 | } 28 | -------------------------------------------------------------------------------- /ark-bcs/src/bcs/constraints/proof.rs: -------------------------------------------------------------------------------- 1 | use crate::{bcs::prover::BCSProof, iop::constraints::oracles::SuccinctRoundMessageVar}; 2 | use ark_crypto_primitives::{ 3 | merkle_tree::{constraints::ConfigGadget, Config}, 4 | PathVar, 5 | }; 6 | use ark_ff::PrimeField; 7 | use ark_r1cs_std::{ 8 | alloc::{AllocVar, AllocationMode}, 9 | fields::fp::FpVar, 10 | }; 11 | use ark_relations::r1cs::{Namespace, SynthesisError}; 12 | use ark_sponge::{constraints::AbsorbGadget, Absorb}; 13 | use ark_std::{borrow::Borrow, vec::Vec}; 14 | 15 | /// Variable for BCS Proof. 16 | /// BCSProof contains all prover messages that use succinct oracle, and thus is 17 | /// itself succinct. 18 | pub struct BCSProofVar 19 | where 20 | MT: Config, 21 | MTG: ConfigGadget]>, 22 | CF: PrimeField, 23 | MT::InnerDigest: Absorb, 24 | MTG::InnerDigest: AbsorbGadget, 25 | { 26 | /// Messages sent by prover in commit phase. Each item in the vector 27 | /// represents a list of message oracles (reed solomon codes go first) 28 | /// with same length. The length constraints do not hold for short messages 29 | /// (IP message). All non-IP messages in the same prover round share the 30 | /// same merkle tree. Each merkle tree leaf is a vector which each 31 | /// element correspond to the same coset of different oracles. 32 | pub prover_iop_messages_by_round: Vec>, 33 | /// Merkle tree roots for all prover messages (including main prover and ldt 34 | /// prover). 35 | pub prover_messages_mt_root: Vec>, 36 | /// Merkle tree paths for queried prover messages in main protocol. 37 | /// `prover_messages_mt_path[i][j]` is the path for jth query at ith round 38 | /// of prover message. 39 | pub prover_oracles_mt_path: Vec>>, 40 | } 41 | 42 | impl AllocVar, CF> for BCSProofVar 43 | where 44 | MT: Config, 45 | MTG: ConfigGadget]>, 46 | CF: PrimeField, 47 | MT::InnerDigest: Absorb, 48 | MTG::InnerDigest: AbsorbGadget, 49 | { 50 | fn new_variable>>( 51 | cs: impl Into>, 52 | f: impl FnOnce() -> Result, 53 | mode: AllocationMode, 54 | ) -> Result { 55 | let native = f()?; 56 | let native = native.borrow(); 57 | let cs = cs.into(); 58 | let prover_iop_messages_by_round = Vec::>::new_variable( 59 | cs.clone(), 60 | || Ok(native.prover_iop_messages_by_round.clone()), 61 | mode, 62 | )?; 63 | let prover_messages_mt_root = native 64 | .prover_messages_mt_root 65 | .iter() 66 | .map(|root| { 67 | root.as_ref().map_or(Ok(None), |root| { 68 | Ok(Some(MTG::InnerDigest::new_variable( 69 | cs.clone(), 70 | || Ok(root), 71 | mode, 72 | )?)) 73 | }) 74 | }) 75 | .collect::, _>>()?; 76 | let prover_oracles_mt_path = native 77 | .prover_oracles_mt_path 78 | .iter() 79 | .map(|paths| { 80 | Vec::>::new_variable(cs.clone(), || Ok(paths.as_slice()), mode) 81 | }) 82 | .collect::, _>>()?; 83 | Ok(Self { 84 | prover_iop_messages_by_round, 85 | prover_messages_mt_root, 86 | prover_oracles_mt_path, 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /ark-bcs/src/bcs/constraints/verifier.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bcs::constraints::{ 3 | proof::BCSProofVar, transcript::SimulationTranscriptVar, MTHashParametersVar, 4 | }, 5 | iop::{ 6 | bookkeeper::NameSpace, 7 | constraints::{message::MessagesCollectionVar, IOPVerifierWithGadget}, 8 | }, 9 | ldt::{constraints::LDTWithGadget, NoLDT}, 10 | }; 11 | use ark_crypto_primitives::merkle_tree::{constraints::ConfigGadget, Config}; 12 | use ark_ff::PrimeField; 13 | use ark_ldt::domain::Radix2CosetDomain; 14 | use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar, prelude::EqGadget, R1CSVar}; 15 | use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; 16 | use ark_sponge::{ 17 | constraints::{AbsorbGadget, SpongeWithGadget}, 18 | Absorb, 19 | }; 20 | use ark_std::{marker::PhantomData, vec::Vec}; 21 | 22 | /// Verifier Gadget for BCS proof variable. 23 | pub struct BCSVerifierGadget 24 | where 25 | MT: Config, 26 | MTG: ConfigGadget, 27 | CF: PrimeField + Absorb, 28 | { 29 | _merkle_tree_config: PhantomData<(MT, MTG)>, 30 | _field: PhantomData, 31 | } 32 | 33 | impl BCSVerifierGadget 34 | where 35 | MT: Config, 36 | MTG: ConfigGadget]>, 37 | CF: PrimeField + Absorb, 38 | MT::InnerDigest: Absorb, 39 | MTG::InnerDigest: AbsorbGadget, 40 | { 41 | /// Given a BCS transformed (RS-)IOP proof, verify the correctness of this 42 | /// proof. `sponge` should be the same state as in beginning of 43 | /// `BCSProver::prove` function. 44 | pub fn verify( 45 | cs: ConstraintSystemRef, 46 | sponge: S::Var, 47 | proof: &BCSProofVar, 48 | public_input: &V::PublicInputVar, 49 | verifier_parameter: &V::VerifierParameterVar, 50 | ldt_params: &L::LDTParameters, 51 | hash_params: &MTHashParametersVar, 52 | ) -> Result 53 | where 54 | V: IOPVerifierWithGadget, 55 | L: LDTWithGadget, 56 | S: SpongeWithGadget, 57 | { 58 | // simulate main prove: reconstruct verifier messages to restore verifier state 59 | let mut transcript = SimulationTranscriptVar::new_transcript( 60 | proof, 61 | sponge, 62 | L::codeword_domain(ldt_params), 63 | L::localization_param(ldt_params), 64 | iop_trace!("BCS root"), 65 | ); 66 | let root_namespace = NameSpace::root(iop_trace!("IOP Root: BCS Proof Verify")); 67 | 68 | V::register_iop_structure_var( 69 | NameSpace::root(iop_trace!("BCS Verify")), 70 | &mut transcript, 71 | verifier_parameter, 72 | )?; 73 | assert!( 74 | !transcript.is_pending_message_available(), 75 | "Sanity check failed: pending verifier message not submitted" 76 | ); 77 | 78 | let codewords = transcript.bookkeeper.dump_all_prover_messages_in_order(); 79 | 80 | let ldt_namespace = transcript.new_namespace(root_namespace, iop_trace!("LDT")); 81 | 82 | let num_rs_oracles = codewords 83 | .clone() 84 | .into_iter() 85 | .map(|x| { 86 | transcript.expected_prover_messages_info[x.index] 87 | .reed_solomon_code_degree_bound 88 | .len() 89 | }) 90 | .sum::(); 91 | let num_virtual_oracles = transcript.registered_virtual_oracles.len(); 92 | 93 | // simulate LDT prove: reconstruct LDT verifier messages to restore verifier 94 | // state 95 | L::register_iop_structure_var( 96 | ldt_namespace, 97 | ldt_params, 98 | num_rs_oracles + num_virtual_oracles, 99 | &mut transcript, 100 | )?; 101 | debug_assert!( 102 | !transcript.is_pending_message_available(), 103 | "Sanity check failed: pending verifier message not submitted" 104 | ); 105 | 106 | // ends commit phase 107 | // start query phase 108 | 109 | // prover message view helps record verify query 110 | assert_eq!( 111 | proof.prover_iop_messages_by_round.len(), 112 | transcript.expected_prover_messages_info.len(), 113 | "incorrect rounds in commit phase" 114 | ); 115 | let prover_message_view = proof 116 | .prover_iop_messages_by_round 117 | .iter() 118 | .zip(transcript.expected_prover_messages_info.iter()) 119 | .map(|(m, info)| m.get_view(info.clone())) 120 | .collect::>(); 121 | 122 | let mut transcript_messages = MessagesCollectionVar::new( 123 | prover_message_view, 124 | transcript 125 | .registered_virtual_oracles 126 | .into_iter() 127 | .map(|x| Some(x)) 128 | .collect(), 129 | transcript.reconstructed_verifier_messages, 130 | transcript.bookkeeper, 131 | ); 132 | 133 | let mut sponge = transcript.sponge; 134 | 135 | // verify LDT 136 | L::query_and_decide_var::( 137 | ldt_namespace, 138 | ldt_params, 139 | &mut sponge, 140 | &codewords, 141 | &mut transcript_messages, 142 | )?; 143 | 144 | // verify the protocol 145 | let verifier_result_var = V::query_and_decide_var( 146 | cs.clone(), 147 | NameSpace::root(iop_trace!("BCS Verify")), 148 | verifier_parameter, 149 | public_input, 150 | &mut sponge, 151 | &mut transcript_messages, 152 | )?; 153 | 154 | // verify all authentication paths 155 | 156 | let all_paths = &proof.prover_oracles_mt_path; 157 | let all_mt_roots = &proof.prover_messages_mt_root; 158 | 159 | assert_eq!(transcript_messages.real_oracles.len(), all_paths.len()); 160 | assert_eq!(transcript_messages.real_oracles.len(), all_mt_roots.len()); 161 | 162 | transcript_messages 163 | .real_oracles 164 | .iter() 165 | .zip(all_paths) 166 | .zip(all_mt_roots) 167 | .try_for_each(|((round_oracle, paths), mt_root)| { 168 | assert_eq!(round_oracle.coset_queries.len(), paths.len()); 169 | assert_eq!( 170 | round_oracle.coset_queries.len(), 171 | round_oracle.oracle.queried_cosets.len(), 172 | "insufficient queries in verifier code" 173 | ); 174 | 175 | let mt_root = if round_oracle.coset_queries.len() > 0 { 176 | mt_root 177 | .as_ref() 178 | .expect("round oracle has query but has no mt_root") 179 | } else { 180 | return Ok(()); // no queries this round: no need to verify 181 | }; 182 | round_oracle 183 | .coset_queries 184 | .iter() 185 | .zip(round_oracle.oracle.queried_cosets.iter()) 186 | .zip(paths.iter()) 187 | .try_for_each(|((index, coset), path)| { 188 | let mut path = path.clone(); 189 | let old_path = path.get_leaf_position().value().unwrap_or_default(); 190 | path.set_leaf_position(index.clone()); 191 | let new_path = path.get_leaf_position().value().unwrap_or_default(); 192 | assert_eq!(old_path, new_path); 193 | path.verify_membership( 194 | &hash_params.leaf_params, 195 | &hash_params.inner_params, 196 | mt_root, 197 | // flatten by concatenating cosets of all queries 198 | coset 199 | .iter() 200 | .flatten() 201 | .map(|x| x.clone()) 202 | .collect::>() 203 | .as_slice(), 204 | )? 205 | .enforce_equal(&Boolean::TRUE) 206 | })?; 207 | Ok(()) 208 | })?; 209 | 210 | Ok(verifier_result_var) 211 | } 212 | 213 | /// Verify without LDT. If verifier tries to get a low-degree oracle, this 214 | /// function will panic. 215 | pub fn verify_with_ldt_disabled( 216 | cs: ConstraintSystemRef, 217 | sponge: S::Var, 218 | proof: &BCSProofVar, 219 | public_input: &V::PublicInputVar, 220 | verifier_parameter: &V::VerifierParameterVar, 221 | hash_params: &MTHashParametersVar, 222 | ) -> Result 223 | where 224 | V: IOPVerifierWithGadget, 225 | S: SpongeWithGadget, 226 | { 227 | Self::verify::, S>( 228 | cs, 229 | sponge, 230 | proof, 231 | public_input, 232 | verifier_parameter, 233 | &None, 234 | hash_params, 235 | ) 236 | } 237 | 238 | /// Verify without LDT. 239 | /// 240 | /// ** Warning **: If verifier tries to get an low-degree oracle, no LDT 241 | /// will be automatically performed. 242 | pub fn verify_with_dummy_ldt( 243 | cs: ConstraintSystemRef, 244 | sponge: S::Var, 245 | proof: &BCSProofVar, 246 | public_input: &V::PublicInputVar, 247 | verifier_parameter: &V::VerifierParameterVar, 248 | hash_params: &MTHashParametersVar, 249 | ldt_codeword_domain: Radix2CosetDomain, 250 | ldt_codeword_localization_parameter: usize, 251 | ) -> Result 252 | where 253 | V: IOPVerifierWithGadget, 254 | S: SpongeWithGadget, 255 | { 256 | Self::verify::, S>( 257 | cs, 258 | sponge, 259 | proof, 260 | public_input, 261 | verifier_parameter, 262 | &Some((ldt_codeword_domain, ldt_codeword_localization_parameter)), 263 | hash_params, 264 | ) 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /ark-bcs/src/bcs/mod.rs: -------------------------------------------------------------------------------- 1 | use ark_crypto_primitives::merkle_tree::{Config as MTConfig, LeafParam, TwoToOneParam}; 2 | 3 | /// BCS prover. 4 | pub mod prover; 5 | /// BCS simulation transcript used by IOP Verifier. 6 | pub mod simulation_transcript; 7 | /// BCS transcript used by IOP Prover. 8 | pub mod transcript; 9 | /// BCS verifier. 10 | pub mod verifier; 11 | 12 | #[cfg(feature = "r1cs")] 13 | /// R1CS Constraints for BCS. 14 | pub mod constraints; 15 | #[cfg(test)] 16 | pub(crate) mod tests; 17 | 18 | /// Specify the merkle tree hash parameters used for this protocol. 19 | #[derive(Derivative)] 20 | #[derivative(Clone(bound = "P: MTConfig"))] 21 | pub struct MTHashParameters { 22 | /// Leaf hash parameter of merkle tree. 23 | pub leaf_hash_param: LeafParam

, 24 | /// Inner hash (TwoToOneHash) parameter of merkle tree. 25 | pub inner_hash_param: TwoToOneParam

, 26 | } 27 | -------------------------------------------------------------------------------- /ark-bcs/src/bcs/prover.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bcs::{transcript::Transcript, MTHashParameters}, 3 | iop::{ 4 | bookkeeper::NameSpace, message::MessagesCollection, oracles::SuccinctRoundMessage, 5 | prover::IOPProver, verifier::IOPVerifierForProver, ProverParam, 6 | }, 7 | ldt::{NoLDT, LDT}, 8 | Error, 9 | }; 10 | use ark_crypto_primitives::{merkle_tree::Config as MTConfig, Path}; 11 | use ark_ff::PrimeField; 12 | use ark_ldt::domain::Radix2CosetDomain; 13 | use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; 14 | use ark_sponge::{Absorb, CryptographicSponge}; 15 | use ark_std::vec::Vec; 16 | 17 | /// BCSProof contains all prover messages that use succinct oracle, and thus is 18 | /// itself succinct. 19 | #[derive(CanonicalSerialize, CanonicalDeserialize, Derivative)] 20 | #[derivative(Clone(bound = "MT: MTConfig, F: PrimeField"))] 21 | pub struct BCSProof 22 | where 23 | MT: MTConfig, 24 | F: PrimeField, 25 | MT::InnerDigest: Absorb, 26 | { 27 | /// Messages sent by prover in commit phase. Each item in the vector 28 | /// represents a list of message oracles (reed solomon codes go first) 29 | /// with same length. The length constraints do not hold for short messages 30 | /// (IP message). All non-IP messages in the same prover round share the 31 | /// same merkle tree. Each merkle tree leaf is a vector which each 32 | /// element correspond to the same coset of different oracles. 33 | pub prover_iop_messages_by_round: Vec>, 34 | 35 | /// Merkle tree roots for all prover messages (including main prover and ldt 36 | /// prover). 37 | pub prover_messages_mt_root: Vec>, 38 | /// Merkle tree paths for queried prover messages in main protocol. 39 | /// `prover_messages_mt_path[i][j]` is the path for jth query at ith round 40 | /// of prover message. 41 | pub prover_oracles_mt_path: Vec>>, 42 | } 43 | 44 | impl BCSProof 45 | where 46 | MT: MTConfig, 47 | F: PrimeField + Absorb, 48 | MT::InnerDigest: Absorb, 49 | { 50 | /// Generate proof from any IOPProver and IOPVerifier with consistent 51 | /// parameter and public input. 52 | pub fn generate( 53 | sponge: S, 54 | public_input: &P::PublicInput, 55 | private_input: &P::PrivateInput, 56 | prover_parameter: &P::ProverParameter, 57 | ldt_params: &L::LDTParameters, 58 | hash_params: MTHashParameters, 59 | ) -> Result 60 | where 61 | L: LDT, 62 | P: IOPProver, 63 | V: IOPVerifierForProver, 64 | S: CryptographicSponge, 65 | { 66 | let verifier_parameter = prover_parameter.to_verifier_param(); 67 | 68 | // create a BCS transcript 69 | let mut transcript = { 70 | Transcript::new( 71 | sponge, 72 | hash_params, 73 | L::codeword_domain(ldt_params), 74 | L::localization_param(ldt_params), 75 | iop_trace!("BCS Proof Generation"), 76 | ) 77 | }; 78 | 79 | let root_namespace = NameSpace::root(iop_trace!("BCS Proof Generation: Commit Phase")); 80 | 81 | // run prover code, using transcript to sample verifier message 82 | // This is not a subprotocol, so we use root namespace (/). 83 | P::prove( 84 | root_namespace, 85 | public_input, 86 | private_input, 87 | &mut transcript, 88 | prover_parameter, 89 | )?; 90 | 91 | // sanity check: pending message should be None 92 | debug_assert!( 93 | !transcript.is_pending_message_available(), 94 | "Sanity check failed: pending message not submitted." 95 | ); 96 | 97 | // perform LDT to enforce degree bound on low-degree oracles 98 | 99 | let ldt_namespace = transcript.new_namespace(root_namespace, iop_trace!("LDT")); 100 | let codewords = transcript.bookkeeper.dump_all_prover_messages_in_order(); 101 | 102 | L::prove(ldt_namespace, ldt_params, &mut transcript, &codewords)?; 103 | 104 | debug_assert!( 105 | !transcript.is_pending_message_available(), 106 | "Sanity check failed: pending message not submitted." 107 | ); 108 | 109 | // extract things from main transcript 110 | let mut sponge = transcript.sponge; 111 | 112 | let mut transcript_messages = MessagesCollection::new( 113 | transcript.prover_message_oracles, 114 | transcript 115 | .registered_virtual_oracles 116 | .into_iter() 117 | .map(|v| Some(v.0)) 118 | .collect(), 119 | transcript.verifier_messages, 120 | transcript.bookkeeper, 121 | ); 122 | 123 | // run LDT verifier code to obtain all queries. We will use this query to 124 | // generate succinct oracles from message recording oracle. 125 | 126 | L::query_and_decide( 127 | ldt_namespace, 128 | ldt_params, 129 | &mut sponge, 130 | &codewords, 131 | &mut transcript_messages, 132 | )?; 133 | 134 | // run main verifier code to obtain all queries 135 | 136 | V::query_and_decide( 137 | NameSpace::root(iop_trace!("BCS Proof Generation: Query and Decision Phase")), 138 | &verifier_parameter, 139 | public_input, 140 | &mut sponge, 141 | &mut transcript_messages, 142 | )?; 143 | 144 | // convert oracles to succinct oracle 145 | let all_succinct_oracles: Vec<_> = transcript_messages 146 | .real_oracles 147 | .iter() 148 | .map(|x| x.get_succinct()) 149 | .collect(); 150 | 151 | let all_queries: Vec<_> = transcript_messages 152 | .real_oracles 153 | .iter() 154 | .map(|msg| msg.queried_coset_index.clone()) 155 | .collect(); 156 | 157 | let merkle_trees = transcript.merkle_tree_for_each_round; 158 | 159 | // generate all merkle tree paths 160 | debug_assert_eq!(merkle_trees.len(), all_queries.len()); 161 | let all_mt_paths = all_queries 162 | .iter() 163 | .zip(merkle_trees.iter()) 164 | .map(|(queries, mt)| { 165 | queries 166 | .iter() 167 | .map(|query| { 168 | mt.as_ref() 169 | .expect("this oracle contains query but has no merkle tree") 170 | .generate_proof(*query) 171 | .expect("fail to generate mt path") 172 | }) 173 | .collect() 174 | }) 175 | .collect(); 176 | 177 | let all_mt_roots: Vec<_> = merkle_trees 178 | .iter() 179 | .map(|x| x.as_ref().map(|tree| tree.root())) 180 | .collect(); 181 | 182 | Ok(BCSProof { 183 | prover_iop_messages_by_round: all_succinct_oracles, 184 | prover_messages_mt_root: all_mt_roots, 185 | prover_oracles_mt_path: all_mt_paths, 186 | }) 187 | } 188 | 189 | /// Generate proof without LDT. Panic if prover tries to send lower degree 190 | /// oracles. 191 | pub fn generate_with_ldt_disabled( 192 | sponge: S, 193 | public_input: &P::PublicInput, 194 | private_input: &P::PrivateInput, 195 | prover_parameter: &P::ProverParameter, 196 | hash_params: MTHashParameters, 197 | ) -> Result 198 | where 199 | V: IOPVerifierForProver, 200 | P: IOPProver, 201 | S: CryptographicSponge, 202 | { 203 | Self::generate::, _>( 204 | sponge, 205 | public_input, 206 | private_input, 207 | prover_parameter, 208 | &None, 209 | hash_params, 210 | ) 211 | } 212 | 213 | /// Generate proof without LDT. Prover send low degree oracles using 214 | /// `ldt_codeword_domain` and `ldt_codeword_localization_parameter`. 215 | pub fn generate_with_dummy_ldt( 216 | sponge: S, 217 | public_input: &P::PublicInput, 218 | private_input: &P::PrivateInput, 219 | prover_parameter: &P::ProverParameter, 220 | hash_params: MTHashParameters, 221 | ldt_codeword_domain: Radix2CosetDomain, 222 | ldt_codeword_localization_parameter: usize, 223 | ) -> Result 224 | where 225 | V: IOPVerifierForProver, 226 | P: IOPProver, 227 | S: CryptographicSponge, 228 | { 229 | Self::generate::, _>( 230 | sponge, 231 | public_input, 232 | private_input, 233 | prover_parameter, 234 | &Some((ldt_codeword_domain, ldt_codeword_localization_parameter)), 235 | hash_params, 236 | ) 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /ark-bcs/src/bcs/tests/constraints/mock.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bcs::{ 3 | constraints::transcript::SimulationTranscriptVar, 4 | tests::mock::{BCSTestVirtualOracle, MockTest1Verifier}, 5 | }, 6 | iop::{ 7 | bookkeeper::NameSpace, 8 | constraints::{ 9 | message::MessagesCollectionVar, oracles::VirtualOracleVar, IOPVerifierWithGadget, 10 | Nothing, 11 | }, 12 | message::{OracleIndex, ProverRoundMessageInfo}, 13 | }, 14 | prelude::MsgRoundRef, 15 | }; 16 | use ark_crypto_primitives::merkle_tree::{constraints::ConfigGadget, Config}; 17 | use ark_ff::PrimeField; 18 | use ark_r1cs_std::{ 19 | alloc::AllocVar, 20 | bits::uint8::UInt8, 21 | boolean::Boolean, 22 | eq::EqGadget, 23 | fields::{fp::FpVar, FieldVar}, 24 | poly::{domain::Radix2DomainVar, polynomial::univariate::dense::DensePolynomialVar}, 25 | ToBitsGadget, ToConstraintFieldGadget, 26 | }; 27 | use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; 28 | use ark_sponge::{ 29 | constraints::{AbsorbGadget, SpongeWithGadget}, 30 | Absorb, 31 | }; 32 | use ark_std::{test_rng, vec, vec::Vec}; 33 | 34 | impl VirtualOracleVar for BCSTestVirtualOracle { 35 | fn constituent_oracle_handles(&self) -> Vec<(MsgRoundRef, Vec)> { 36 | vec![(self.round, vec![(0, true).into()])] // take first oracle with 37 | // degree bound 38 | } 39 | 40 | fn evaluate_var( 41 | &self, 42 | coset_domain: Radix2DomainVar, 43 | constituent_oracles: &[Vec>], 44 | ) -> Result>, SynthesisError> { 45 | let msg2_points = &constituent_oracles[0]; 46 | let poly_var = DensePolynomialVar::from_coefficients_vec(vec![ 47 | FpVar::Constant(F::from(1u64)), 48 | FpVar::Constant(F::from(2u64)), 49 | FpVar::Constant(F::from(1u64)), 50 | ]); 51 | let eval = coset_domain 52 | .elements() 53 | .into_iter() 54 | .map(|x| poly_var.evaluate(&x)) 55 | .collect::, SynthesisError>>()?; 56 | assert_eq!(eval.len(), msg2_points.len()); 57 | Ok(msg2_points.iter().zip(eval).map(|(x, y)| x * y).collect()) 58 | } 59 | } 60 | 61 | impl, CF: PrimeField + Absorb> IOPVerifierWithGadget 62 | for MockTest1Verifier 63 | { 64 | type VerifierParameterVar = Nothing; 65 | type VerifierOutputVar = Boolean; 66 | type PublicInputVar = (); 67 | 68 | fn register_iop_structure_var]>>( 69 | namespace: NameSpace, 70 | transcript: &mut SimulationTranscriptVar, 71 | _verifier_parameter: &Self::VerifierParameterVar, 72 | ) -> Result<(), SynthesisError> 73 | where 74 | MT::InnerDigest: Absorb, 75 | MTG::InnerDigest: AbsorbGadget, 76 | { 77 | // prover send 78 | let expected_info = 79 | ProverRoundMessageInfo::new_using_custom_length_and_localization(256, 2) 80 | .with_num_message_oracles(2) 81 | .with_num_short_messages(1) 82 | .build(); 83 | transcript.receive_prover_current_round(namespace, expected_info, iop_trace!())?; 84 | 85 | // verifier send 86 | transcript.squeeze_verifier_field_elements(3)?; 87 | transcript.squeeze_verifier_field_bytes(16)?; 88 | transcript.submit_verifier_current_round(namespace, iop_trace!()); 89 | 90 | // verifier send2 91 | transcript.squeeze_verifier_field_bits(19)?; 92 | transcript.submit_verifier_current_round(namespace, iop_trace!()); 93 | 94 | // prover send 95 | let expected_info = 96 | ProverRoundMessageInfo::new_using_custom_length_and_localization(256, 0) 97 | .with_num_message_oracles(1) 98 | .with_num_short_messages(1) 99 | .build(); 100 | transcript.receive_prover_current_round(namespace, expected_info, iop_trace!())?; 101 | 102 | // prover send2 103 | let expected_info = ProverRoundMessageInfo::new_using_codeword_domain(transcript) 104 | .with_reed_solomon_codes_degree_bounds(vec![8]) 105 | .with_num_short_messages(1) 106 | .build(); 107 | let prover_oracle_2 = 108 | transcript.receive_prover_current_round(namespace, expected_info, iop_trace!())?; 109 | 110 | // prover send virtual oracle 111 | 112 | let vo = BCSTestVirtualOracle::new(prover_oracle_2); 113 | 114 | transcript.register_prover_virtual_round( 115 | namespace, 116 | vo, 117 | vec![10], 118 | vec![10], 119 | iop_trace!("mock vo"), 120 | ); 121 | 122 | Ok(()) 123 | } 124 | 125 | fn query_and_decide_var( 126 | cs: ConstraintSystemRef, 127 | namespace: NameSpace, 128 | _verifier_parameter: &Self::VerifierParameterVar, 129 | _public_input_var: &Self::PublicInputVar, 130 | _sponge: &mut S::Var, 131 | transcript_messages: &mut MessagesCollectionVar, 132 | ) -> Result { 133 | // verify if message is indeed correct 134 | let mut rng = test_rng(); 135 | let cs = ark_relations::ns!(cs, "mock iop query_and_decide"); 136 | let mut random_fe = || FpVar::new_witness(cs.cs(), || Ok(CF::rand(&mut rng))).unwrap(); 137 | 138 | let pm1_1: Vec<_> = (0..4).map(|_| random_fe()).collect(); 139 | let pm1_2: Vec<_> = (0..256).map(|_| random_fe()).collect(); 140 | let pm1_3: Vec<_> = (0..256).map(|_| random_fe()).collect(); 141 | 142 | transcript_messages 143 | .prover_round((namespace, 0)) 144 | .short_message(0, iop_trace!()) 145 | .enforce_equal(pm1_1.as_slice())?; 146 | 147 | transcript_messages 148 | .prover_round((namespace, 0)) 149 | .query_point( 150 | &[ 151 | UInt8::new_witness(cs.cs(), || Ok(123))?.to_bits_le()?, 152 | UInt8::new_witness(cs.cs(), || Ok(223))?.to_bits_le()?, 153 | ], 154 | iop_trace!(), 155 | )? 156 | .into_iter() 157 | .zip( 158 | vec![ 159 | vec![pm1_2[123].clone(), pm1_3[123].clone()], 160 | vec![pm1_2[223].clone(), pm1_3[223].clone()], 161 | ] 162 | .into_iter(), 163 | ) 164 | .try_for_each(|(left, right)| left.enforce_equal(&right))?; 165 | 166 | assert!(cs.cs().is_satisfied().unwrap()); 167 | let vm1_1 = transcript_messages.verifier_round((namespace, 0))[0] 168 | .clone() 169 | .try_into_field_elements() 170 | .unwrap(); 171 | assert_eq!(vm1_1.len(), 3); 172 | let vm1_2 = transcript_messages.verifier_round((namespace, 0))[1] 173 | .clone() 174 | .try_into_bytes() 175 | .unwrap(); 176 | assert_eq!(vm1_2.len(), 16); 177 | let vm2_1 = transcript_messages.verifier_round((namespace, 1))[0] 178 | .clone() 179 | .try_into_bits() 180 | .unwrap(); 181 | assert_eq!(vm2_1.len(), 19); 182 | 183 | let pm2_1: Vec<_> = vm1_1.into_iter().map(|x| x.square().unwrap()).collect(); 184 | pm2_1.enforce_equal( 185 | transcript_messages 186 | .prover_round((namespace, 1)) 187 | .short_message(0, iop_trace!()) 188 | .as_slice(), 189 | )?; 190 | 191 | let pm2_2: Vec<_> = (0..256u128) 192 | .map(|x| { 193 | FpVar::new_witness(cs.cs(), || Ok(CF::from(x))).unwrap() 194 | + vm1_2.to_constraint_field().unwrap()[0].clone() 195 | }) 196 | .collect(); 197 | 198 | transcript_messages 199 | .prover_round((namespace, 1)) 200 | .query_point( 201 | &[ 202 | UInt8::constant(19).to_bits_le()?, 203 | UInt8::constant(29).to_bits_le()?, 204 | UInt8::constant(39).to_bits_le()?, 205 | ], 206 | iop_trace!(), 207 | )? 208 | .into_iter() 209 | .zip( 210 | vec![ 211 | vec![pm2_2[19].clone()], 212 | vec![pm2_2[29].clone()], 213 | vec![pm2_2[39].clone()], 214 | ] 215 | .into_iter(), 216 | ) 217 | .for_each(|(left, right)| left.enforce_equal(&right).unwrap()); 218 | 219 | let pm3_1: Vec<_> = (0..6).map(|_| random_fe()).collect(); 220 | 221 | pm3_1.enforce_equal( 222 | transcript_messages 223 | .prover_round((namespace, 2)) 224 | .short_message(0, iop_trace!()) 225 | .as_slice(), 226 | )?; 227 | 228 | transcript_messages 229 | .prover_round((namespace, 2)) 230 | .query_point( 231 | &[vec![Boolean::TRUE], vec![Boolean::FALSE, Boolean::TRUE]], 232 | iop_trace!(), 233 | )?; // query 1, 2 234 | Ok(Boolean::TRUE) 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /ark-bcs/src/bcs/tests/constraints/mod.rs: -------------------------------------------------------------------------------- 1 | use ark_crypto_primitives::{ 2 | crh::poseidon, 3 | merkle_tree::{constraints::ConfigGadget, IdentityDigestConverter}, 4 | }; 5 | use ark_r1cs_std::fields::fp::FpVar; 6 | 7 | use crate::{ 8 | bcs::{ 9 | constraints::{ 10 | proof::BCSProofVar, transcript::SimulationTranscriptVar, verifier::BCSVerifierGadget, 11 | MTHashParametersVar, 12 | }, 13 | prover::BCSProof, 14 | tests::{ 15 | mock::{MockTest1Verifier, MockTestProver}, 16 | FieldMTConfig, Fr, 17 | }, 18 | MTHashParameters, 19 | }, 20 | iop::{ 21 | bookkeeper::NameSpace, 22 | constraints::{IOPVerifierWithGadget, Nothing}, 23 | }, 24 | ldt::{ 25 | rl_ldt::{LinearCombinationLDT, LinearCombinationLDTParameters}, 26 | LDT, 27 | }, 28 | test_utils::poseidon_parameters, 29 | }; 30 | use ark_crypto_primitives::crh::poseidon::constraints::CRHParametersVar; 31 | use ark_ldt::{domain::Radix2CosetDomain, fri::FRIParameters}; 32 | use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; 33 | use ark_relations::r1cs::ConstraintSystem; 34 | use ark_sponge::{ 35 | constraints::CryptographicSpongeVar, 36 | poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}, 37 | CryptographicSponge, 38 | }; 39 | use ark_std::{vec, One}; 40 | 41 | mod mock; 42 | 43 | type HG = poseidon::constraints::CRHGadget; 44 | type TwoToOneHG = poseidon::constraints::TwoToOneCRHGadget; 45 | 46 | impl ConfigGadget for FieldMTConfig { 47 | type Leaf = [FpVar]; 48 | type LeafDigest = FpVar; 49 | type LeafInnerConverter = IdentityDigestConverter>; 50 | type InnerDigest = FpVar; 51 | type LeafHash = HG; 52 | type TwoToOneHash = TwoToOneHG; 53 | } 54 | 55 | #[test] 56 | fn test_bcs() { 57 | let fri_parameters = FRIParameters::new( 58 | 64, 59 | vec![1, 2, 1], 60 | Radix2CosetDomain::new_radix2_coset(128, Fr::one()), 61 | ); 62 | let ldt_parameters = LinearCombinationLDTParameters { 63 | fri_parameters, 64 | num_queries: 1, 65 | }; 66 | let sponge = PoseidonSponge::new(&poseidon_parameters()); 67 | let mt_hash_param = MTHashParameters:: { 68 | leaf_hash_param: poseidon_parameters(), 69 | inner_hash_param: poseidon_parameters(), 70 | }; 71 | let bcs_proof = BCSProof::generate::< 72 | MockTest1Verifier, 73 | MockTestProver, 74 | LinearCombinationLDT, 75 | _, 76 | >( 77 | sponge, 78 | &(), 79 | &(), 80 | &(), 81 | &ldt_parameters, 82 | mt_hash_param.clone(), 83 | ) 84 | .expect("fail to prove"); 85 | let cs = ConstraintSystem::::new_ref(); 86 | let mt_hash_param = MTHashParametersVar:: { 87 | leaf_params: CRHParametersVar::new_constant(cs.clone(), poseidon_parameters()).unwrap(), 88 | inner_params: CRHParametersVar::new_constant(cs.clone(), poseidon_parameters()).unwrap(), 89 | }; 90 | 91 | let bcs_proof_var = 92 | BCSProofVar::<_, FieldMTConfig, _>::new_witness(cs.clone(), || Ok(&bcs_proof)).unwrap(); 93 | 94 | // verify if simulation transcript reconstructs correctly 95 | let sponge = PoseidonSpongeVar::new(cs.clone(), &poseidon_parameters()); 96 | let mut simulation_transcript = 97 | SimulationTranscriptVar::<_, _, _, PoseidonSponge<_>>::new_transcript( 98 | &bcs_proof_var, 99 | sponge, 100 | LinearCombinationLDT::codeword_domain(&ldt_parameters), 101 | LinearCombinationLDT::localization_param(&ldt_parameters), 102 | iop_trace!("test bcs"), 103 | ); 104 | 105 | MockTest1Verifier::register_iop_structure_var( 106 | NameSpace::root(iop_trace!("BCS test")), 107 | &mut simulation_transcript, 108 | &Nothing, 109 | ) 110 | .unwrap(); 111 | 112 | // verify should have all enforced constraints satisfied 113 | let sponge = PoseidonSpongeVar::new(cs.clone(), &poseidon_parameters()); 114 | let result = BCSVerifierGadget::verify::< 115 | MockTest1Verifier, 116 | LinearCombinationLDT, 117 | PoseidonSponge, 118 | >( 119 | cs.clone(), 120 | sponge, 121 | &bcs_proof_var, 122 | &(), 123 | &Nothing, 124 | &ldt_parameters, 125 | &mt_hash_param, 126 | ) 127 | .expect("error during verify"); 128 | assert!(result.value().unwrap()); 129 | 130 | assert!(cs.is_satisfied().unwrap()); 131 | } 132 | -------------------------------------------------------------------------------- /ark-bcs/src/bcs/tests/mock.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bcs::{simulation_transcript::SimulationTranscript, transcript::Transcript}, 3 | iop::{ 4 | bookkeeper::NameSpace, 5 | message::{MessagesCollection, OracleIndex, ProverRoundMessageInfo, VerifierMessage}, 6 | oracles::{RoundOracle, VirtualOracle}, 7 | prover::IOPProver, 8 | verifier::IOPVerifier, 9 | }, 10 | prelude::MsgRoundRef, 11 | Error, 12 | }; 13 | use ark_crypto_primitives::merkle_tree::Config as MTConfig; 14 | use ark_ff::{PrimeField, ToConstraintField}; 15 | use ark_ldt::domain::Radix2CosetDomain; 16 | use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial}; 17 | use ark_sponge::{Absorb, CryptographicSponge, FieldElementSize}; 18 | use ark_std::{marker::PhantomData, test_rng, vec, vec::Vec}; 19 | use tracing::Level; 20 | 21 | pub(crate) struct MockTestProver { 22 | _field: PhantomData, 23 | } 24 | 25 | pub(crate) struct BCSTestVirtualOracle { 26 | pub(crate) round: MsgRoundRef, 27 | _field: PhantomData, 28 | } 29 | 30 | impl BCSTestVirtualOracle { 31 | #[allow(unused)] 32 | pub(crate) fn new(round: MsgRoundRef) -> Self { 33 | BCSTestVirtualOracle { 34 | round, 35 | _field: PhantomData, 36 | } 37 | } 38 | } 39 | 40 | impl VirtualOracle for BCSTestVirtualOracle { 41 | fn constituent_oracle_handles(&self) -> Vec<(MsgRoundRef, Vec)> { 42 | vec![(self.round, vec![(0, true).into()])] // take first oracle with 43 | // degree bound 44 | } 45 | 46 | fn evaluate( 47 | &self, 48 | coset_domain: Radix2CosetDomain, 49 | constituent_oracles: &[Vec], 50 | ) -> Vec { 51 | // calculate f(x) * (x^2 + 2x + 1) 52 | let msg2_points = &constituent_oracles[0]; 53 | let poly = DensePolynomial::from_coefficients_vec(vec![F::one(), F::from(2u64), F::one()]); 54 | let eval = coset_domain.evaluate(&poly); 55 | assert_eq!(msg2_points.len(), eval.len()); 56 | msg2_points.iter().zip(eval).map(|(&x, y)| x * y).collect() 57 | } 58 | } 59 | 60 | impl IOPProver for MockTestProver { 61 | type ProverParameter = (); 62 | type PublicInput = (); 63 | type PrivateInput = (); 64 | 65 | fn prove, S: CryptographicSponge>( 66 | namespace: NameSpace, 67 | _public_input: &Self::PublicInput, 68 | _private_input: &Self::PrivateInput, 69 | transcript: &mut Transcript, 70 | _prover_parameter: &Self::ProverParameter, 71 | ) -> Result<(), Error> 72 | where 73 | MT::InnerDigest: Absorb, 74 | { 75 | let span = tracing::span!(Level::INFO, "main prove"); 76 | let _enter = span.enter(); 77 | 78 | let mut rng = test_rng(); 79 | 80 | // prover send 81 | let msg1 = (0..4).map(|_| F::rand(&mut rng)).collect::>(); 82 | // transcript.send_message(msg1); 83 | let msg2 = (0..256).map(|_| F::rand(&mut rng)).collect::>(); 84 | // transcript 85 | // .send_message_oracle_with_localization(msg2, 2) 86 | // .unwrap(); 87 | let msg3 = (0..256).map(|_| F::rand(&mut rng)).collect::>(); 88 | transcript 89 | .add_prover_round_with_custom_length_and_localization(256, 2) 90 | .send_short_message(msg1) 91 | .send_oracle_message_without_degree_bound(msg2) 92 | .send_oracle_message_without_degree_bound(msg3) 93 | .submit(namespace, iop_trace!("mock send"))?; 94 | 95 | // verifier send 96 | let vm1 = transcript.squeeze_verifier_field_elements(&[ 97 | FieldElementSize::Full, 98 | FieldElementSize::Full, 99 | FieldElementSize::Full, 100 | ]); 101 | let vm2 = transcript.squeeze_verifier_bytes(16); 102 | transcript.submit_verifier_current_round(namespace, iop_trace!("mock send")); 103 | 104 | // verifier send2 105 | transcript.squeeze_verifier_bits(19); 106 | transcript.submit_verifier_current_round(namespace, iop_trace!("mock send2")); 107 | 108 | let msg1 = vm1.into_iter().map(|x| x.square()); 109 | let msg2 = (0..256u128).map(|x| { 110 | let rhs: F = vm2.to_field_elements().unwrap()[0]; 111 | F::from(x) + rhs 112 | }); 113 | transcript 114 | .add_prover_round_with_custom_length_and_localization(256, 0) 115 | .send_short_message(msg1) 116 | .send_oracle_message_without_degree_bound(msg2) 117 | .submit(namespace, iop_trace!("mock send2"))?; 118 | 119 | // prover send 2 120 | let msg1 = (0..6).map(|_| F::rand(&mut rng)).collect::>(); 121 | let msg2 = DensePolynomial::from_coefficients_vec(vec![ 122 | F::from(0x12345u128), 123 | F::from(0x23456u128), 124 | F::from(0x34567u128), 125 | F::from(0x45678u128), 126 | F::from(0x56789u128), 127 | ]); 128 | 129 | let prover_oracle_2 = transcript 130 | .add_prover_round_with_codeword_domain() 131 | .send_short_message(msg1) 132 | .send_univariate_polynomial(&msg2, 8) 133 | .submit(namespace, iop_trace!("mock send3"))?; 134 | 135 | // prover send virtual oracle 136 | // always make sure arguments have type! 137 | let virtual_oracle = BCSTestVirtualOracle { 138 | round: prover_oracle_2, 139 | _field: PhantomData, 140 | }; 141 | 142 | // warning: make sure you register this virtual round again in your verifier 143 | // (and its constraints) otherwise verification will fail 144 | transcript.register_prover_virtual_round( 145 | namespace, 146 | virtual_oracle, 147 | vec![10], 148 | vec![10], 149 | iop_trace!("mock vo"), 150 | ); 151 | 152 | Ok(()) 153 | } 154 | } 155 | 156 | pub(crate) struct MockTest1Verifier { 157 | _field: PhantomData, 158 | } 159 | 160 | impl IOPVerifier for MockTest1Verifier { 161 | type VerifierOutput = bool; 162 | type VerifierParameter = (); 163 | type PublicInput = (); 164 | 165 | fn register_iop_structure>( 166 | namespace: NameSpace, 167 | transcript: &mut SimulationTranscript, 168 | _verifier_parameter: &Self::VerifierParameter, 169 | ) where 170 | MT::InnerDigest: Absorb, 171 | { 172 | let span = tracing::span!(Level::INFO, "main register"); 173 | let _enter = span.enter(); 174 | 175 | let expected_info = 176 | ProverRoundMessageInfo::new_using_custom_length_and_localization(256, 2) 177 | .with_num_message_oracles(2) 178 | .with_num_short_messages(1) 179 | .build(); 180 | transcript.receive_prover_current_round(namespace, expected_info, iop_trace!()); 181 | 182 | // verifier send 183 | transcript.squeeze_verifier_field_elements(&[ 184 | FieldElementSize::Full, 185 | FieldElementSize::Full, 186 | FieldElementSize::Full, 187 | ]); 188 | transcript.squeeze_verifier_field_bytes(16); 189 | transcript.submit_verifier_current_round(namespace, iop_trace!()); 190 | 191 | // verifier send2 192 | transcript.squeeze_verifier_field_bits(19); 193 | transcript.submit_verifier_current_round(namespace, iop_trace!()); 194 | 195 | // prover send 196 | let expected_info = 197 | ProverRoundMessageInfo::new_using_custom_length_and_localization(256, 0) 198 | .with_num_message_oracles(1) 199 | .with_num_short_messages(1) 200 | .build(); 201 | 202 | transcript.receive_prover_current_round(namespace, expected_info, iop_trace!()); 203 | 204 | // prover send2 205 | let expected_info = ProverRoundMessageInfo::new_using_codeword_domain(transcript) 206 | .with_reed_solomon_codes_degree_bounds(vec![8]) 207 | .with_num_short_messages(1) 208 | .build(); 209 | let prover_oracle_2 = 210 | transcript.receive_prover_current_round(namespace, expected_info, iop_trace!()); 211 | 212 | // prover send virtual oracle 213 | // always make sure arguments have type! 214 | 215 | let vo = BCSTestVirtualOracle { 216 | round: prover_oracle_2, 217 | _field: PhantomData, 218 | }; 219 | 220 | transcript.register_prover_virtual_round( 221 | namespace, 222 | vo, 223 | vec![10], 224 | vec![10], 225 | iop_trace!("mock vo"), 226 | ); 227 | } 228 | 229 | fn query_and_decide>( 230 | namespace: NameSpace, 231 | _verifier_parameter: &Self::VerifierParameter, 232 | _public_input: &Self::PublicInput, 233 | _sponge: &mut S, 234 | transcript_messages: &mut MessagesCollection, 235 | ) -> Result { 236 | let span = tracing::span!(Level::INFO, "main query decide"); 237 | let _enter = span.enter(); 238 | // verify if message is indeed correct 239 | let mut rng = test_rng(); 240 | let pm1_1: Vec<_> = (0..4).map(|_| F::rand(&mut rng)).collect(); 241 | let pm1_2: Vec<_> = (0..256).map(|_| F::rand(&mut rng)).collect(); 242 | let pm1_3: Vec<_> = (0..256).map(|_| F::rand(&mut rng)).collect(); 243 | 244 | assert_eq!( 245 | transcript_messages 246 | .prover_round((namespace, 0)) 247 | .short_message(0, iop_trace!()), 248 | &pm1_1 249 | ); 250 | assert_eq!( 251 | transcript_messages 252 | .prover_round((namespace, 0)) 253 | .query_point(&[123, 223], iop_trace!("mock query 0")), 254 | vec![vec![pm1_2[123], pm1_3[123]], vec![pm1_2[223], pm1_3[223]]] 255 | ); 256 | 257 | let vm1_1 = if let VerifierMessage::FieldElements(fe) = 258 | transcript_messages.verifier_round((namespace, 0))[0].clone() 259 | { 260 | assert_eq!(fe.len(), 3); 261 | fe 262 | } else { 263 | panic!("invalid vm message type") 264 | }; 265 | let vm1_2 = if let VerifierMessage::Bytes(bytes) = 266 | transcript_messages.verifier_round((namespace, 0))[1].clone() 267 | { 268 | assert_eq!(bytes.len(), 16); 269 | bytes 270 | } else { 271 | panic!("invalid vm message type"); 272 | }; 273 | 274 | if let VerifierMessage::Bits(bits) = &transcript_messages.verifier_round((namespace, 1))[0] 275 | { 276 | assert_eq!(bits.len(), 19); 277 | } else { 278 | panic!("invalid vm message type"); 279 | } 280 | 281 | let pm2_1: Vec<_> = vm1_1.into_iter().map(|x| x.square()).collect(); 282 | 283 | assert_eq!( 284 | transcript_messages 285 | .prover_round((namespace, 1)) 286 | .short_message(0, iop_trace!()), 287 | &pm2_1 288 | ); 289 | 290 | let pm2_2: Vec<_> = (0..256u128) 291 | .map(|x| { 292 | let rhs: F = vm1_2.to_field_elements().unwrap()[0]; 293 | F::from(x) + rhs 294 | }) 295 | .collect(); 296 | 297 | assert_eq!( 298 | transcript_messages 299 | .prover_round((namespace, 1)) 300 | .query_point(&[19, 29, 39], iop_trace!()), 301 | vec![vec![pm2_2[19]], vec![pm2_2[29]], vec![pm2_2[39]]] 302 | ); 303 | 304 | let pm3_1: Vec<_> = (0..6).map(|_| F::rand(&mut rng)).collect(); 305 | assert_eq!( 306 | transcript_messages 307 | .prover_round((namespace, 2)) 308 | .short_message(0, iop_trace!()), 309 | &pm3_1 310 | ); 311 | // just query some points 312 | transcript_messages 313 | .prover_round((namespace, 2)) 314 | .query_point(&vec![1, 2], iop_trace!()); 315 | 316 | Ok(true) 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /ark-bcs/src/bcs/tests/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "r1cs")] 2 | mod constraints; 3 | /// Contains the mock IOP prover and verifier to solely test correctness of 4 | /// transcript. 5 | pub(crate) mod mock; 6 | 7 | use crate::{ 8 | bcs::{ 9 | prover::BCSProof, 10 | simulation_transcript::SimulationTranscript, 11 | tests::mock::{MockTest1Verifier, MockTestProver}, 12 | verifier::BCSVerifier, 13 | MTHashParameters, 14 | }, 15 | iop::{bookkeeper::NameSpace, verifier::IOPVerifier}, 16 | ldt::{ 17 | rl_ldt::{LinearCombinationLDT, LinearCombinationLDTParameters}, 18 | LDT, 19 | }, 20 | test_utils::poseidon_parameters, 21 | }; 22 | use ark_crypto_primitives::{ 23 | crh::poseidon, 24 | merkle_tree::{Config, IdentityDigestConverter}, 25 | }; 26 | use ark_ldt::{domain::Radix2CosetDomain, fri::FRIParameters}; 27 | use ark_sponge::{poseidon::PoseidonSponge, CryptographicSponge}; 28 | use ark_std::{vec, One}; 29 | 30 | pub(crate) type Fr = ark_bls12_381::Fr; 31 | pub(crate) type H = poseidon::CRH; 32 | pub(crate) type TwoToOneH = poseidon::TwoToOneCRH; 33 | 34 | pub(crate) struct FieldMTConfig; 35 | impl Config for FieldMTConfig { 36 | type Leaf = [Fr]; 37 | type LeafDigest = Fr; 38 | type LeafInnerDigestConverter = IdentityDigestConverter; 39 | type InnerDigest = Fr; 40 | type LeafHash = H; 41 | type TwoToOneHash = TwoToOneH; 42 | } 43 | 44 | #[test] 45 | /// Test if restore_state_from_commit_phase message works. This test uses a 46 | /// dummy protocol described as `MockTestProver`. 47 | fn test_bcs() { 48 | let fri_parameters = FRIParameters::new( 49 | 64, 50 | vec![2, 2, 1], 51 | Radix2CosetDomain::new_radix2_coset(128, Fr::one()), 52 | ); 53 | let ldt_parameters = LinearCombinationLDTParameters { 54 | fri_parameters, 55 | num_queries: 7, 56 | }; 57 | let sponge = PoseidonSponge::new(&poseidon_parameters()); 58 | let mt_hash_param = MTHashParameters:: { 59 | leaf_hash_param: poseidon_parameters(), 60 | inner_hash_param: poseidon_parameters(), 61 | }; 62 | let bcs_proof = BCSProof::generate::< 63 | MockTest1Verifier, 64 | MockTestProver, 65 | LinearCombinationLDT, 66 | _, 67 | >( 68 | sponge, 69 | &(), 70 | &(), 71 | &(), 72 | &ldt_parameters, 73 | mt_hash_param.clone(), 74 | ) 75 | .expect("fail to prove"); 76 | 77 | // verify if simulation transcript reconstructs correctly 78 | let sponge = PoseidonSponge::new(&poseidon_parameters()); 79 | let mut simulation_transcript = SimulationTranscript::new_transcript( 80 | &bcs_proof, 81 | sponge, 82 | LinearCombinationLDT::codeword_domain(&ldt_parameters), 83 | LinearCombinationLDT::localization_param(&ldt_parameters), 84 | iop_trace!("test bcs"), 85 | ); 86 | MockTest1Verifier::register_iop_structure( 87 | NameSpace::root(iop_trace!("BCS Test")), 88 | &mut simulation_transcript, 89 | &(), 90 | ); 91 | // verify should return no error 92 | let sponge = PoseidonSponge::new(&poseidon_parameters()); 93 | assert!( 94 | BCSVerifier::verify::, LinearCombinationLDT, _>( 95 | sponge, 96 | &bcs_proof, 97 | &(), 98 | &(), 99 | &ldt_parameters, 100 | mt_hash_param 101 | ) 102 | .expect("verification failed"), 103 | "test verifier returns false" 104 | ); 105 | } 106 | -------------------------------------------------------------------------------- /ark-bcs/src/bcs/verifier.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bcs::{prover::BCSProof, simulation_transcript::SimulationTranscript, MTHashParameters}, 3 | iop::{bookkeeper::NameSpace, message::MessagesCollection, verifier::IOPVerifier}, 4 | ldt::{NoLDT, LDT}, 5 | Error, 6 | }; 7 | use ark_crypto_primitives::merkle_tree::Config as MTConfig; 8 | use ark_ff::PrimeField; 9 | use ark_ldt::domain::Radix2CosetDomain; 10 | use ark_sponge::{Absorb, CryptographicSponge}; 11 | use ark_std::{marker::PhantomData, vec::Vec}; 12 | 13 | /// Verifier for BCS proof. 14 | pub struct BCSVerifier 15 | where 16 | MT: MTConfig, 17 | F: PrimeField + Absorb, 18 | MT::InnerDigest: Absorb, 19 | { 20 | _merkle_tree_config: PhantomData, 21 | _field: PhantomData, 22 | } 23 | 24 | impl BCSVerifier 25 | where 26 | MT: MTConfig, 27 | F: PrimeField + Absorb, 28 | MT::InnerDigest: Absorb, 29 | { 30 | /// Given a BCS transformed (RS-)IOP proof, verify the correctness of this 31 | /// proof. `sponge` should be the same state as in beginning of 32 | /// `BCSProver::prove` function. 33 | pub fn verify( 34 | sponge: S, 35 | proof: &BCSProof, 36 | public_input: &V::PublicInput, 37 | verifier_parameter: &V::VerifierParameter, 38 | ldt_params: &L::LDTParameters, 39 | hash_params: MTHashParameters, 40 | ) -> Result 41 | where 42 | V: IOPVerifier, 43 | L: LDT, 44 | S: CryptographicSponge, 45 | { 46 | // simulate main prove: reconstruct verifier messages to restore verifier state 47 | let mut transcript = SimulationTranscript::new_transcript( 48 | proof, 49 | sponge, 50 | L::codeword_domain(ldt_params), 51 | L::localization_param(ldt_params), 52 | iop_trace!("IOP Root: BCS proof verify"), 53 | ); 54 | 55 | let root_namespace = NameSpace::root(iop_trace!("BCS Verify: commit phase")); 56 | 57 | V::register_iop_structure::(root_namespace, &mut transcript, verifier_parameter); 58 | // sanity check: transcript has not pending message 59 | assert!( 60 | !transcript.is_pending_message_available(), 61 | "Sanity check failed: pending verifier message not submitted" 62 | ); 63 | 64 | let codewords = transcript.bookkeeper.dump_all_prover_messages_in_order(); 65 | 66 | let ldt_namespace = transcript.new_namespace(root_namespace, iop_trace!("LDT")); 67 | 68 | // simulate LDT prove: reconstruct LDT verifier messages to restore LDT verifier 69 | // state 70 | let num_rs_oracles = codewords 71 | .clone() 72 | .into_iter() 73 | .map(|x| { 74 | transcript.expected_prover_messages_info[x.index] 75 | .reed_solomon_code_degree_bound 76 | .len() 77 | }) 78 | .sum::(); 79 | let num_virtual_oracles = transcript.registered_virtual_oracles.len(); // TODO: change to sum of number of oracle in each virtual round 80 | 81 | L::register_iop_structure( 82 | ldt_namespace, 83 | ldt_params, 84 | num_rs_oracles + num_virtual_oracles, 85 | &mut transcript, 86 | ); 87 | 88 | debug_assert!( 89 | !transcript.is_pending_message_available(), 90 | "Sanity check failed, pending verifier message not submitted" 91 | ); 92 | 93 | // end commit phase 94 | // start query phase 95 | 96 | // prover message view helps record verify query 97 | assert_eq!( 98 | proof.prover_iop_messages_by_round.len(), 99 | transcript.expected_prover_messages_info.len(), 100 | "incorrect rounds in commit phase" 101 | ); 102 | let prover_message_view = proof 103 | .prover_iop_messages_by_round 104 | .iter() 105 | .zip(transcript.expected_prover_messages_info.iter()) 106 | .map(|(m, info)| m.get_view(info.clone())) 107 | .collect::>(); 108 | 109 | let mut transcript_messages = MessagesCollection::new( 110 | prover_message_view, 111 | transcript 112 | .registered_virtual_oracles 113 | .into_iter() 114 | .map(Some) 115 | .collect(), 116 | transcript.reconstructed_verifier_messages, 117 | transcript.bookkeeper, 118 | ); 119 | let mut sponge = transcript.sponge; 120 | 121 | // verify LDT 122 | 123 | L::query_and_decide( 124 | ldt_namespace, 125 | ldt_params, 126 | &mut sponge, 127 | &codewords, 128 | &mut transcript_messages, 129 | )?; 130 | 131 | // verify the protocol (we can use a new view) 132 | let verifier_result = V::query_and_decide( 133 | root_namespace, 134 | verifier_parameter, 135 | public_input, 136 | &mut sponge, 137 | &mut transcript_messages, 138 | )?; 139 | 140 | // verify all authentication paths 141 | 142 | // we clone all the paths because we need to replace its leaf position with 143 | // verifier calculated one 144 | let all_paths = proof.prover_oracles_mt_path.clone(); 145 | let all_mt_roots = &proof.prover_messages_mt_root; 146 | 147 | assert_eq!(transcript_messages.real_oracles.len(), all_paths.len()); 148 | assert_eq!(transcript_messages.real_oracles.len(), all_mt_roots.len()); 149 | 150 | transcript_messages 151 | .real_oracles 152 | .iter() 153 | .zip(all_paths) 154 | .zip(all_mt_roots) 155 | .for_each(|((round_oracle, paths), mt_root)| { 156 | assert_eq!(round_oracle.coset_queries.len(), paths.len()); 157 | assert_eq!( 158 | round_oracle.coset_queries.len(), 159 | round_oracle.underlying_message.queried_cosets.len(), 160 | "insufficient queries in verifier code" 161 | ); 162 | let mt_root = if !round_oracle.coset_queries.is_empty() { 163 | mt_root 164 | .as_ref() 165 | .expect("round oracle has query but has no mt_root") 166 | } else { 167 | return; 168 | }; 169 | round_oracle 170 | .coset_queries 171 | .iter() 172 | .zip(round_oracle.underlying_message.queried_cosets.iter()) 173 | .zip(paths.into_iter()) 174 | .for_each(|((index, coset), mut path)| { 175 | debug_assert_eq!(path.leaf_index, *index); 176 | path.leaf_index = *index; 177 | assert!( 178 | path.verify( 179 | &hash_params.leaf_hash_param, 180 | &hash_params.inner_hash_param, 181 | mt_root, 182 | // flatten by concatenating cosets of all oracles 183 | coset 184 | .clone() 185 | .into_iter() 186 | .flatten() 187 | .collect::>() 188 | .as_slice() 189 | ) 190 | .expect("cannot verify"), 191 | "merkle tree verification failed" 192 | ) 193 | }) 194 | }); 195 | 196 | Ok(verifier_result) 197 | } 198 | 199 | /// Verify without LDT. If verifier tries to get a low-degree oracle, this 200 | /// function will panic. 201 | pub fn verify_with_ldt_disabled( 202 | sponge: S, 203 | proof: &BCSProof, 204 | public_input: &V::PublicInput, 205 | verifier_parameter: &V::VerifierParameter, 206 | hash_params: MTHashParameters, 207 | ) -> Result 208 | where 209 | V: IOPVerifier, 210 | S: CryptographicSponge, 211 | { 212 | Self::verify::, S>( 213 | sponge, 214 | proof, 215 | public_input, 216 | verifier_parameter, 217 | &None, 218 | hash_params, 219 | ) 220 | } 221 | 222 | /// Verify without LDT. 223 | /// 224 | /// ** Warning **: If verifier tries to get an low-degree oracle, no LDT 225 | /// will be automatically performed. 226 | pub fn verify_with_dummy_ldt( 227 | sponge: S, 228 | proof: &BCSProof, 229 | public_input: &V::PublicInput, 230 | verifier_parameter: &V::VerifierParameter, 231 | hash_params: MTHashParameters, 232 | ldt_codeword_domain: Radix2CosetDomain, 233 | ldt_codeword_localization_parameter: usize, 234 | ) -> Result 235 | where 236 | V: IOPVerifier, 237 | S: CryptographicSponge, 238 | { 239 | Self::verify::, S>( 240 | sponge, 241 | proof, 242 | public_input, 243 | verifier_parameter, 244 | &Some((ldt_codeword_domain, ldt_codeword_localization_parameter)), 245 | hash_params, 246 | ) 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /ark-bcs/src/iop/bookkeeper.rs: -------------------------------------------------------------------------------- 1 | use ark_std::collections::BTreeMap; 2 | 3 | use crate::{iop::message::MsgRoundRef, tracer::TraceInfo}; 4 | use ark_std::vec::Vec; 5 | 6 | /// Namespace is a unique id of the protocol in a transcript. 7 | /// `Namespace{id=0}` is always reserved for root namespace. 8 | #[derive(Copy, Clone, Debug, Derivative)] 9 | #[derivative(PartialEq, PartialOrd, Ord, Eq)] 10 | pub struct NameSpace { 11 | /// The global id of current namespace in the transcript. 12 | pub id: u64, 13 | /// Trace for the current namespace 14 | #[derivative(PartialEq = "ignore", PartialOrd = "ignore", Ord = "ignore")] 15 | pub trace: TraceInfo, 16 | /// The protocol id of the parent protocol in current transcript. 17 | /// if `self.id==0`, then this field should be 0. 18 | #[derivative(PartialEq = "ignore", PartialOrd = "ignore", Ord = "ignore")] 19 | pub parent_id: u64, 20 | } 21 | 22 | impl NameSpace { 23 | /// Root namespace 24 | pub const fn root(trace: TraceInfo) -> Self { 25 | Self { 26 | id: 0, 27 | trace, 28 | parent_id: 0, 29 | } 30 | } 31 | 32 | /// Returns a Namespace 33 | pub(crate) const fn new(id: u64, trace: TraceInfo, parent_id: u64) -> Self { 34 | Self { 35 | id, 36 | trace, 37 | parent_id, 38 | } 39 | } 40 | } 41 | 42 | #[derive(Clone)] 43 | /// Stores the ownership relation of each message to its protocol. 44 | /// All data is managed by `ark-bcs` and users do not need to create the 45 | /// Bookkeeper by themselves. 46 | /// 47 | /// **In almost all cases, users do not need to interact with this struct.** 48 | pub struct MessageBookkeeper { 49 | /// Store the messages by namespace id 50 | pub(crate) messages_store: BTreeMap, 51 | /// An adjacancy list the subspaces called for current namespace, in order. 52 | pub(crate) ns_map: BTreeMap>, 53 | /// Store the namespace details (e.g. trace) by id 54 | pub(crate) ns_details: BTreeMap, 55 | next_namespace_index: u64, 56 | } 57 | 58 | impl MessageBookkeeper { 59 | pub(crate) fn new(trace: TraceInfo) -> Self { 60 | let mut result = Self { 61 | messages_store: BTreeMap::default(), 62 | ns_map: BTreeMap::default(), 63 | ns_details: BTreeMap::default(), 64 | next_namespace_index: 0, 65 | }; 66 | // initialize root namespace 67 | result.messages_store.insert(0, Default::default()); 68 | result.ns_map.insert(0, Default::default()); 69 | result.ns_details.insert(0, NameSpace::new(0, trace, 0)); 70 | result.next_namespace_index = 1; 71 | result 72 | } 73 | 74 | pub(crate) fn new_namespace(&mut self, trace: TraceInfo, parent_id: u64) -> NameSpace { 75 | let ns = NameSpace::new(self.next_namespace_index, trace, parent_id); 76 | // add new namespace details 77 | self.ns_details.insert(ns.id, ns); 78 | // initialize the messages store for new namespace 79 | self.messages_store.insert( 80 | ns.id, 81 | MessageIndices { 82 | prover_rounds: Vec::new(), 83 | verifier_messages: Vec::new(), 84 | }, 85 | ); 86 | // initialize subspace store for new namespace 87 | self.ns_map.insert(ns.id, Vec::new()); 88 | // attach new namespace as subspace for parent namespace 89 | self.ns_map.get_mut(&parent_id).unwrap().push(ns.id); 90 | 91 | self.next_namespace_index += 1; 92 | ns 93 | } 94 | 95 | /// Return all prover message reference sent at this point, in order. 96 | pub(crate) fn dump_all_prover_messages_in_order(&self) -> Vec { 97 | self.messages_store 98 | .values() 99 | .flat_map(|v| v.prover_rounds.iter()) 100 | .copied() 101 | .collect() 102 | } 103 | 104 | /// Get the id the subspace that got created at the `index`th call to the 105 | /// `new_subspace` 106 | pub(crate) fn get_subspace_id(&self, namespace_id: u64, index: usize) -> u64 { 107 | *self 108 | .ns_map 109 | .get(&namespace_id) 110 | .expect("namespace does not exist") 111 | .get(index) 112 | .expect("index out of range") 113 | } 114 | 115 | /// Get the subspace that got created at the `index`th call to the 116 | /// `new_subspace` 117 | pub(crate) fn get_subspace(&self, namespace: NameSpace, index: usize) -> NameSpace { 118 | let subspace_id = self.get_subspace_id(namespace.id, index); 119 | *self 120 | .ns_details 121 | .get(&subspace_id) 122 | .unwrap_or_else(|| panic!("Invalid Subspace ID: {}", subspace_id)) 123 | } 124 | 125 | pub(crate) fn attach_prover_round_to_namespace( 126 | &mut self, 127 | namespace: NameSpace, 128 | round_index: usize, 129 | is_virtual: bool, 130 | trace: TraceInfo, 131 | ) -> MsgRoundRef { 132 | let namespace_node = self 133 | .messages_store 134 | .get_mut(&namespace.id) 135 | .expect("namespace not found"); 136 | let oracle_ref = MsgRoundRef::new(round_index, trace, is_virtual); 137 | namespace_node.prover_rounds.push(oracle_ref); 138 | oracle_ref 139 | } 140 | 141 | pub(crate) fn attach_verifier_round_to_namespace( 142 | &mut self, 143 | namespace: NameSpace, 144 | round_index: usize, 145 | trace: TraceInfo, 146 | ) -> MsgRoundRef { 147 | let namespace_node = self 148 | .messages_store 149 | .get_mut(&namespace.id) 150 | .expect("namespace not found"); 151 | let oracle_ref = MsgRoundRef::new(round_index, trace, false); 152 | namespace_node.verifier_messages.push(oracle_ref); 153 | oracle_ref 154 | } 155 | 156 | /// Return the message indices for current namespace. 157 | pub(crate) fn get_message_indices(&self, namespace: NameSpace) -> &MessageIndices { 158 | self.messages_store 159 | .get(&namespace.id) 160 | .expect("message indices not exist") 161 | } 162 | } 163 | 164 | /// contains indices of current protocol messages. 165 | #[derive(Clone, Derivative, Default)] 166 | #[derivative(Debug)] 167 | pub struct MessageIndices { 168 | /// Indices of prover message round oracles in this namespace. 169 | pub prover_rounds: Vec, 170 | /// Indices of verifier round oracles in this namespace. 171 | pub verifier_messages: Vec, 172 | } 173 | 174 | /// Cam be converted to `MsgRoundRef` 175 | pub trait ToMsgRoundRef { 176 | /// Convert to `MsgRoundRef` 177 | fn to_prover_msg_round_ref(&self, c: &impl BookkeeperContainer) -> MsgRoundRef; 178 | 179 | /// Convert to `MsgRoundRef` 180 | fn to_verifier_msg_round_ref(&self, c: &impl BookkeeperContainer) -> MsgRoundRef; 181 | } 182 | 183 | impl ToMsgRoundRef for MsgRoundRef { 184 | fn to_prover_msg_round_ref(&self, _c: &impl BookkeeperContainer) -> MsgRoundRef { 185 | *self 186 | } 187 | 188 | fn to_verifier_msg_round_ref(&self, _c: &impl BookkeeperContainer) -> MsgRoundRef { 189 | *self 190 | } 191 | } 192 | 193 | impl ToMsgRoundRef for (NameSpace, usize) { 194 | fn to_prover_msg_round_ref(&self, c: &impl BookkeeperContainer) -> MsgRoundRef { 195 | let msg_ref = c._bookkeeper().get_message_indices(self.0).prover_rounds[self.1]; 196 | msg_ref 197 | } 198 | 199 | fn to_verifier_msg_round_ref(&self, c: &impl BookkeeperContainer) -> MsgRoundRef { 200 | let msg_ref = c 201 | ._bookkeeper() 202 | .get_message_indices(self.0) 203 | .verifier_messages[self.1]; 204 | msg_ref 205 | } 206 | } 207 | 208 | /// This trait is used to reduce code duplication between native and 209 | /// constraints, as both of them use message bookkeeper to keep track of the 210 | /// round. 211 | pub trait BookkeeperContainer { 212 | /// Return the underlying bookkeeper. Normally user does not need to call 213 | /// this function. 214 | fn _bookkeeper(&self) -> &MessageBookkeeper; 215 | 216 | /// Given the current namespace, and the index of the namespace of 217 | /// subprotocol namespace, return the subprotocol namespace. `index` is 218 | /// the time where the subprotocol namespace is created in 219 | /// `register_iop_structure`. 220 | fn get_subprotocol_namespace(&self, namespace: NameSpace, index: usize) -> NameSpace { 221 | self._bookkeeper().get_subspace(namespace, index) 222 | } 223 | 224 | /// Get number of prove rounds in namespace. 225 | fn num_prover_rounds_in_namespace(&self, namespace: NameSpace) -> usize { 226 | self._bookkeeper() 227 | .get_message_indices(namespace) 228 | .prover_rounds 229 | .len() 230 | } 231 | 232 | /// Get number of verifier rounds in namespace. 233 | fn num_verifier_rounds_in_namespace(&self, namespace: NameSpace) -> usize { 234 | self._bookkeeper() 235 | .get_message_indices(namespace) 236 | .verifier_messages 237 | .len() 238 | } 239 | 240 | // fetch all sent messages as referecnes 241 | 242 | /// Return all prover rounds message in the namespace as round reference. 243 | fn prover_round_refs_in_namespace(&self, namespace: NameSpace) -> &Vec { 244 | &self 245 | ._bookkeeper() 246 | .get_message_indices(namespace) 247 | .prover_rounds 248 | } 249 | /// Return all prover rounds message as round reference. 250 | fn verifier_round_refs_in_namespace(&self, namespace: NameSpace) -> &Vec { 251 | &self 252 | ._bookkeeper() 253 | .get_message_indices(namespace) 254 | .verifier_messages 255 | } 256 | } 257 | 258 | impl BookkeeperContainer for MessageBookkeeper { 259 | fn _bookkeeper(&self) -> &MessageBookkeeper { 260 | self 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /ark-bcs/src/iop/constraints/message.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | iop::{ 3 | bookkeeper::{BookkeeperContainer, MessageBookkeeper, ToMsgRoundRef}, 4 | message::{CosetQueryResult, MsgRoundRef, ProverRoundMessageInfo, VerifierMessage}, 5 | }, 6 | tracer::TraceInfo, 7 | }; 8 | use ark_ff::PrimeField; 9 | use ark_r1cs_std::{fields::fp::FpVar, prelude::*}; 10 | use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError}; 11 | use ark_std::{borrow::Borrow, vec::Vec}; 12 | 13 | use super::oracles::{SuccinctRoundOracleVar, VirtualOracleVarWithInfo}; 14 | 15 | impl R1CSVar for VerifierMessageVar { 16 | type Value = VerifierMessage; 17 | 18 | fn cs(&self) -> ConstraintSystemRef { 19 | match self { 20 | Self::Bits(v) => v[0].cs(), 21 | Self::Bytes(v) => v[0].cs(), 22 | Self::FieldElements(v) => v[0].cs(), 23 | } 24 | } 25 | 26 | fn value(&self) -> Result { 27 | match self { 28 | Self::Bits(v) => Ok(Self::Value::Bits(v.value()?)), 29 | Self::Bytes(v) => Ok(Self::Value::Bytes(v.value()?)), 30 | Self::FieldElements(v) => Ok(Self::Value::FieldElements(v.value()?)), 31 | } 32 | } 33 | } 34 | 35 | /// Stores sent prover and verifier messages variables in order. 36 | /// Message can be accessed using namespace, or `MsgRoundRef`. 37 | /// This struct is used by verifier to access prover message oracles and 38 | /// verifier messages. 39 | pub struct MessagesCollectionVar<'a, F: PrimeField> { 40 | pub(crate) real_oracles: Vec>, 41 | #[allow(unused)] 42 | pub(crate) virtual_oracles: Vec>>, 43 | pub(crate) verifier_messages: Vec>>, 44 | pub(crate) bookkeeper: MessageBookkeeper, 45 | } 46 | 47 | impl<'a, F: PrimeField> BookkeeperContainer for MessagesCollectionVar<'a, F> { 48 | fn _bookkeeper(&self) -> &MessageBookkeeper { 49 | &self.bookkeeper 50 | } 51 | } 52 | 53 | impl<'a, F: PrimeField> MessagesCollectionVar<'a, F> { 54 | pub(crate) fn new( 55 | real_oracles: Vec>, 56 | virtual_oracles: Vec>>, 57 | verifier_messages: Vec>>, 58 | bookkeeper: MessageBookkeeper, 59 | ) -> Self { 60 | Self { 61 | real_oracles, 62 | virtual_oracles, 63 | verifier_messages, 64 | bookkeeper, 65 | } 66 | } 67 | 68 | /// Get verifier message at at requested round. 69 | pub fn verifier_round(&self, at: impl ToMsgRoundRef) -> &Vec> { 70 | let at = at.to_verifier_msg_round_ref(&self.bookkeeper); 71 | &self.verifier_messages[at.index] 72 | } 73 | 74 | /// Get prover message at at requested round. 75 | pub fn prover_round<'b>(&'b mut self, at: impl ToMsgRoundRef) -> AtProverRoundVar<'a, 'b, F> { 76 | let round = at.to_prover_msg_round_ref(&self.bookkeeper); 77 | AtProverRoundVar::<'a, 'b, F> { _self: self, round } 78 | } 79 | 80 | /// Take a virtual oracle and return a shadow `self` that can be used by 81 | /// virtual oracle. Current `self` will be temporarily unavailable when 82 | /// querying to prevent circular dependency. 83 | fn take_virtual_oracle(&mut self, round: MsgRoundRef) -> (VirtualOracleVarWithInfo, Self) { 84 | assert!(round.is_virtual); 85 | 86 | // move a virtual oracle, and make it temporarily available when querying to 87 | // prevent circular dependency 88 | let virtual_round = ark_std::mem::take( 89 | self.virtual_oracles 90 | .get_mut(round.index) 91 | .expect("round out of range"), 92 | ) 93 | .expect("Virtual oracle contains circular query: For example, A -> B -> C -> A"); 94 | 95 | // construct a shadow MessageCollection to query the virtual oracle. 96 | let shadow_self = Self { 97 | bookkeeper: self.bookkeeper.clone(), 98 | real_oracles: ark_std::mem::take(&mut self.real_oracles), 99 | virtual_oracles: ark_std::mem::take(&mut self.virtual_oracles), 100 | verifier_messages: ark_std::mem::take(&mut self.verifier_messages), 101 | }; 102 | 103 | (virtual_round, shadow_self) 104 | } 105 | 106 | fn restore_from_shadow_self( 107 | &mut self, 108 | shadow_self: Self, 109 | round: MsgRoundRef, 110 | vo: VirtualOracleVarWithInfo, 111 | ) { 112 | self.real_oracles = shadow_self.real_oracles; 113 | self.virtual_oracles = shadow_self.virtual_oracles; 114 | self.verifier_messages = shadow_self.verifier_messages; 115 | self.virtual_oracles[round.index] = Some(vo); 116 | } 117 | 118 | /// Get metadata of current prover round message. 119 | pub fn get_prover_round_info(&self, at: impl ToMsgRoundRef) -> ProverRoundMessageInfo { 120 | let at = at.to_prover_msg_round_ref(&self.bookkeeper); 121 | if at.is_virtual { 122 | self.virtual_oracles 123 | .get(at.index) 124 | .expect("round out of range") 125 | .as_ref() 126 | .expect("Virtual oracle contains circular query: For example, A -> B -> C -> A") 127 | .get_info() 128 | } else { 129 | self.real_oracles[at.index].get_info().clone() 130 | } 131 | } 132 | } 133 | 134 | /// A temporary struct to for querying/viewing prover round message. 135 | pub struct AtProverRoundVar<'a, 'b, F: PrimeField> { 136 | pub(crate) _self: &'b mut MessagesCollectionVar<'a, F>, 137 | pub(crate) round: MsgRoundRef, 138 | } 139 | 140 | impl<'a, 'b, F: PrimeField> AtProverRoundVar<'a, 'b, F> { 141 | /// Query the prover message as an evaluation oracle at the requested round 142 | /// at a point. 143 | pub fn query_point( 144 | &mut self, 145 | positions: &[Vec>], 146 | _tracer: TraceInfo, 147 | ) -> Result>>, SynthesisError> { 148 | let round = self.round; 149 | let _self = &mut self._self; 150 | if !round.is_virtual { 151 | return _self.real_oracles[round.index].query(positions); 152 | } 153 | 154 | let (virtual_round, mut shadow_self) = _self.take_virtual_oracle(round); 155 | 156 | let query_result = virtual_round.query_point(positions, &mut shadow_self); 157 | 158 | // restore self 159 | _self.restore_from_shadow_self(shadow_self, round, virtual_round); 160 | 161 | // return the query result 162 | query_result 163 | } 164 | 165 | /// Return the queried coset at `coset_index` of all oracles in this round. 166 | /// `result[i][j][k]` is coset index `i` -> oracle index `j` -> element `k` 167 | /// in this coset. 168 | pub fn query_coset( 169 | &mut self, 170 | positions: &[Vec>], 171 | _tracer: TraceInfo, 172 | ) -> Result>, SynthesisError> { 173 | let round = self.round; 174 | let _self = &mut self._self; 175 | if !round.is_virtual { 176 | return Ok(_self.real_oracles[round.index].query_coset(positions)); 177 | } 178 | 179 | let (virtual_round, mut shadow_self) = _self.take_virtual_oracle(round); 180 | 181 | let query_result = virtual_round.query_coset(positions, &mut shadow_self)?; 182 | 183 | _self.restore_from_shadow_self(shadow_self, round, virtual_round); 184 | 185 | Ok(query_result) 186 | } 187 | /// Get prover's short messages sent at this round. Short messages are not 188 | /// serialized in Merkle tree. Instead, those IP-style short messages are 189 | /// directly included in proof variable. 190 | pub fn short_message(&mut self, index: usize, _tracer: TraceInfo) -> Vec> { 191 | let at = self.round; 192 | if at.is_virtual { 193 | unimplemented!("Virtual oracle does not have short message"); 194 | } else { 195 | self._self.real_oracles[at.index].get_short_message(index) 196 | } 197 | } 198 | } 199 | 200 | #[derive(Clone)] 201 | /// Verifier message variable used in transcript gadget 202 | pub enum VerifierMessageVar { 203 | /// Field elements 204 | FieldElements(Vec>), 205 | /// bits 206 | Bits(Vec>), 207 | /// bytes 208 | Bytes(Vec>), 209 | } 210 | 211 | impl VerifierMessageVar { 212 | /// If `self` contains field elements, return those elements. Otherwise 213 | /// return `None`. 214 | pub fn try_into_field_elements(self) -> Option>> { 215 | if let VerifierMessageVar::FieldElements(fe) = self { 216 | Some(fe) 217 | } else { 218 | None 219 | } 220 | } 221 | 222 | /// If `self` contains bits, return those bits. Otherwise return `None`. 223 | pub fn try_into_bits(self) -> Option>> { 224 | if let VerifierMessageVar::Bits(bits) = self { 225 | Some(bits) 226 | } else { 227 | None 228 | } 229 | } 230 | 231 | /// If `self` contains bytes, return those bytes. Otherwise return `None`. 232 | pub fn try_into_bytes(self) -> Option>> { 233 | if let VerifierMessageVar::Bytes(bytes) = self { 234 | Some(bytes) 235 | } else { 236 | None 237 | } 238 | } 239 | } 240 | 241 | impl AllocVar, F> for VerifierMessageVar { 242 | fn new_variable>>( 243 | cs: impl Into>, 244 | f: impl FnOnce() -> Result, 245 | mode: AllocationMode, 246 | ) -> Result { 247 | let cs = cs.into(); 248 | let msg = f()?; 249 | let msg = msg.borrow(); 250 | match msg { 251 | VerifierMessage::FieldElements(elements) => { 252 | let var: Result, _> = elements 253 | .iter() 254 | .map(|x| FpVar::new_variable(cs.clone(), || Ok(*x), mode)) 255 | .collect(); 256 | Ok(VerifierMessageVar::FieldElements(var?)) 257 | }, 258 | VerifierMessage::Bits(bits) => { 259 | let var: Result, _> = bits 260 | .iter() 261 | .map(|x| Boolean::new_variable(cs.clone(), || Ok(*x), mode)) 262 | .collect(); 263 | Ok(VerifierMessageVar::Bits(var?)) 264 | }, 265 | VerifierMessage::Bytes(bytes) => { 266 | let var: Result, _> = bytes 267 | .iter() 268 | .map(|x| UInt8::new_variable(cs.clone(), || Ok(*x), mode)) 269 | .collect(); 270 | Ok(VerifierMessageVar::Bytes(var?)) 271 | }, 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /ark-bcs/src/iop/constraints/mod.rs: -------------------------------------------------------------------------------- 1 | use ark_crypto_primitives::merkle_tree::{constraints::ConfigGadget, Config}; 2 | use ark_ff::PrimeField; 3 | use ark_r1cs_std::{fields::fp::FpVar, prelude::*}; 4 | use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError}; 5 | use ark_sponge::{ 6 | constraints::{AbsorbGadget, SpongeWithGadget}, 7 | Absorb, 8 | }; 9 | use ark_std::{borrow::Borrow, ops::Deref}; 10 | 11 | use crate::{bcs::constraints::transcript::SimulationTranscriptVar, iop::verifier::IOPVerifier}; 12 | 13 | use self::message::MessagesCollectionVar; 14 | 15 | use super::bookkeeper::NameSpace; 16 | 17 | /// Defines prover and verifier message variable. 18 | pub mod message; 19 | /// Defines message oracles. 20 | pub mod oracles; 21 | 22 | /// Constraints for IOP Verifier. 23 | /// 24 | /// The verifier for public coin IOP has two phases. 25 | /// * **Commit Phase**: Verifier send message that is uniformly sampled from a 26 | /// random oracle. Verifier 27 | /// will receive prover oracle, that can use used to query later. This protocol 28 | /// relies on public coin assumption described in [BCS16, section 4.1](https://eprint.iacr.org/2016/116.pdf#subsection.4.1), that the verifier does not 29 | /// main state and postpones any query to after the commit phase. 30 | /// * **Query And Decision Phase**: Verifier sends query and receive answer from 31 | /// message oracle. 32 | pub trait IOPVerifierWithGadget: IOPVerifier 33 | where 34 | S: SpongeWithGadget, 35 | CF: PrimeField + Absorb, 36 | { 37 | /// Verifier Parameter: `VerifierParameterVar` may include R1CS variables. 38 | /// `VerifierParameterVar` is required to be allocatable from 39 | /// `Self::VerifierParameter`. In case `Self::VerifierParameter` is `()`, 40 | /// use `Nothing`. 41 | type VerifierParameterVar: AllocVar; 42 | /// Verifier Output 43 | type VerifierOutputVar; 44 | /// Public Input Variable 45 | type PublicInputVar: ?Sized; 46 | 47 | /// Simulate interaction with prover in commit phase, reconstruct verifier 48 | /// messages and verifier state using the sponge provided in the 49 | /// simulation transcript. Returns the verifier state for query and decision 50 | /// phase. 51 | /// 52 | /// When writing test, use `transcript.check_correctness` after calling this 53 | /// method to verify the correctness of this method. 54 | fn register_iop_structure_var]>>( 55 | namespace: NameSpace, 56 | transcript: &mut SimulationTranscriptVar, 57 | verifier_parameter: &Self::VerifierParameterVar, 58 | ) -> Result<(), SynthesisError> 59 | where 60 | MT::InnerDigest: Absorb, 61 | MTG::InnerDigest: AbsorbGadget; 62 | 63 | /// Query the oracle using the random oracle. Run the verifier code, and 64 | /// return verifier output that is valid if prover claim is true. 65 | /// Verifier will return an error if prover message is obviously false, 66 | /// or oracle cannot answer the query. 67 | /// 68 | /// To access prover message oracle and previous verifier messages of 69 | /// current namespace, use bookkeeper. 70 | fn query_and_decide_var<'a>( 71 | cs: ConstraintSystemRef, 72 | namespace: NameSpace, 73 | verifier_parameter: &Self::VerifierParameterVar, 74 | public_input_var: &Self::PublicInputVar, 75 | sponge: &mut S::Var, 76 | transcript_messages: &mut MessagesCollectionVar<'a, CF>, 77 | ) -> Result; 78 | } 79 | 80 | /// `Nothing` is equivalent to `()`, but additionally implements `AllocVar` for 81 | /// `()`. 82 | pub struct Nothing; 83 | 84 | impl AllocVar for Nothing { 85 | fn new_variable>( 86 | _cs: impl Into>, 87 | _f: impl FnOnce() -> Result, 88 | _mode: AllocationMode, 89 | ) -> Result { 90 | Ok(Nothing) 91 | } 92 | } 93 | 94 | impl AllocVar<(), F> for Nothing { 95 | fn new_variable>( 96 | _cs: impl Into>, 97 | _f: impl FnOnce() -> Result, 98 | _mode: AllocationMode, 99 | ) -> Result { 100 | Ok(Nothing) 101 | } 102 | } 103 | 104 | #[repr(transparent)] 105 | /// A wrapper used in `VerifierParameterVar`, which implements `AllocVar` for 106 | /// `VerifierParameter`. 107 | pub struct SameAsNative(pub T); 108 | impl Deref for SameAsNative { 109 | type Target = T; 110 | fn deref(&self) -> &Self::Target { 111 | &self.0 112 | } 113 | } 114 | 115 | impl AllocVar for SameAsNative { 116 | fn new_variable>( 117 | _cs: impl Into>, 118 | f: impl FnOnce() -> Result, 119 | _mode: AllocationMode, 120 | ) -> Result { 121 | f().map(|v| SameAsNative(v.borrow().clone())) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /ark-bcs/src/iop/constraints/oracles.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | iop::{ 3 | message::{CosetQueryResult, LeavesType, OracleIndex, ProverRoundMessageInfo}, 4 | oracles::SuccinctRoundMessage, 5 | }, 6 | prelude::MsgRoundRef, 7 | }; 8 | use ark_ff::PrimeField; 9 | use ark_ldt::domain::Radix2CosetDomain; 10 | use ark_r1cs_std::{ 11 | alloc::{AllocVar, AllocationMode}, 12 | boolean::Boolean, 13 | fields::fp::FpVar, 14 | poly::domain::Radix2DomainVar, 15 | select::CondSelectGadget, 16 | }; 17 | use ark_relations::r1cs::{Namespace, SynthesisError}; 18 | use ark_std::{borrow::Borrow, boxed::Box, collections::BTreeSet, mem::take, vec, vec::Vec}; 19 | 20 | use super::message::MessagesCollectionVar; 21 | 22 | #[derive(Clone)] 23 | /// Round oracle variable that contains only queried leaves. 24 | pub struct SuccinctRoundMessageVar { 25 | /// Leaves at query indices. 26 | pub queried_cosets: Vec>>>, 27 | // note that queries will be provided by verifier instead 28 | /// Store the non-oracle IP messages in this round 29 | pub short_messages: Vec>>, 30 | } 31 | 32 | impl SuccinctRoundMessageVar { 33 | /// Return a view of succinct round oracle var. View contains a reference to 34 | /// the oracle, as well as recorded queries and position pointer. 35 | pub fn get_view(&self, info: ProverRoundMessageInfo) -> SuccinctRoundOracleVar { 36 | SuccinctRoundOracleVar { 37 | info, 38 | oracle: &self, 39 | coset_queries: Vec::new(), 40 | current_query_pos: 0, 41 | } 42 | } 43 | } 44 | 45 | impl AllocVar, F> for SuccinctRoundMessageVar { 46 | fn new_variable>>( 47 | cs: impl Into>, 48 | f: impl FnOnce() -> Result, 49 | mode: AllocationMode, 50 | ) -> Result { 51 | let cs = cs.into(); 52 | let native = f()?; 53 | let native = native.borrow(); 54 | let queried_cosets = native 55 | .queried_cosets 56 | .iter() 57 | .map(|coset_for_all_oracles| { 58 | coset_for_all_oracles 59 | .iter() 60 | .map(|x| Vec::new_variable(cs.clone(), || Ok(x.clone()), mode)) 61 | .collect::, _>>() 62 | }) 63 | .collect::, _>>()?; 64 | let short_messages = native 65 | .short_messages 66 | .iter() 67 | .map(|msg| { 68 | msg.iter() 69 | .map(|x| FpVar::new_variable(cs.clone(), || Ok(*x), mode)) 70 | .collect::, _>>() 71 | }) 72 | .collect::, _>>()?; 73 | Ok(Self { 74 | queried_cosets, 75 | short_messages, 76 | }) 77 | } 78 | } 79 | 80 | #[derive(Clone)] 81 | /// A reference to the succinct oracle variable plus a state recording current 82 | /// query position. 83 | pub struct SuccinctRoundOracleVar<'a, F: PrimeField> { 84 | pub(crate) oracle: &'a SuccinctRoundMessageVar, 85 | /// Round Message Info expected by Verifier 86 | pub info: ProverRoundMessageInfo, 87 | /// queries calculated by the verifier 88 | pub coset_queries: Vec>>, 89 | current_query_pos: usize, 90 | } 91 | 92 | impl<'a, F: PrimeField> SuccinctRoundOracleVar<'a, F> { 93 | /// Return the leaves of at `position` of all oracle. `result[i][j]` is leaf 94 | /// `i` at oracle `j`. 95 | pub fn query( 96 | &mut self, 97 | position: &[Vec>], 98 | ) -> Result>>, SynthesisError> { 99 | // convert the position to coset_index 100 | let log_coset_size = self.get_info().localization_parameter; 101 | let log_num_cosets = ark_std::log2(self.oracle_length()) as usize - log_coset_size; 102 | let log_oracle_length = ark_std::log2(self.oracle_length()) as usize; 103 | assert_eq!(log_oracle_length, log_coset_size + log_num_cosets); 104 | // pad position to appropriate length 105 | let position = position 106 | .iter() 107 | .map(|bits| fit_bits_to_length(bits, log_oracle_length)) 108 | .collect::>(); 109 | // coset index = position % num_cosets = the least significant `log_num_cosets` 110 | // bits of pos element index in coset = position / num_cosets = all 111 | // other bits 112 | let (coset_index, element_index_in_coset) = 113 | point_query_to_coset_query(&position, log_num_cosets); 114 | let queried_coset = self.query_coset_without_tracer(&coset_index); 115 | coset_query_response_to_point_query_response(queried_coset, element_index_in_coset) 116 | } 117 | 118 | /// Return the queried coset at `coset_index` of all oracles. 119 | /// `result[i][j][k]` is coset index `i` -> oracle index `j` -> element `k` 120 | /// in this coset. 121 | pub fn query_coset(&mut self, coset_index: &[Vec>]) -> CosetQueryResult> { 122 | self.query_coset_without_tracer(coset_index) 123 | } 124 | 125 | fn query_coset_without_tracer( 126 | &mut self, 127 | coset_index: &[Vec>], 128 | ) -> CosetQueryResult> { 129 | self.coset_queries.extend_from_slice(coset_index); 130 | assert!( 131 | self.current_query_pos + coset_index.len() <= self.oracle.queried_cosets.len(), 132 | "too many queries!" 133 | ); 134 | let result = self.oracle.queried_cosets 135 | [self.current_query_pos..self.current_query_pos + coset_index.len()] 136 | .to_vec(); 137 | self.current_query_pos += coset_index.len(); 138 | result.into() 139 | } 140 | 141 | /// Number of reed_solomon_codes oracles in this round. 142 | pub fn num_reed_solomon_codes_oracles(&self) -> usize { 143 | self.info.reed_solomon_code_degree_bound.len() 144 | } 145 | 146 | /// length of each oracle 147 | pub fn oracle_length(&self) -> usize { 148 | self.info.length 149 | } 150 | 151 | /// Get oracle info, including number of oracles for each type and degree 152 | /// bound of each RS code oracle. 153 | pub fn get_info(&self) -> ProverRoundMessageInfo { 154 | self.info.clone() 155 | } 156 | 157 | /// Get degree bound of all reed-solomon codes in this round. 158 | pub fn get_degree_bound(&self) -> Vec { 159 | self.get_info().reed_solomon_code_degree_bound.clone() 160 | } 161 | 162 | /// Get non-oracle `i`th non-oracle short message in this round. 163 | pub fn get_short_message(&self, index: usize) -> Vec> { 164 | self.oracle.short_messages[index].clone() 165 | } 166 | } 167 | 168 | fn point_query_to_coset_query( 169 | point_indices: &[Vec>], 170 | log_num_cosets: usize, 171 | ) -> (Vec>>, Vec>>) { 172 | let coset_index = point_indices 173 | .iter() 174 | .map(|pos| pos[..log_num_cosets].to_vec()) 175 | .collect::>(); 176 | let element_index_in_coset = point_indices 177 | .iter() 178 | .map(|pos| pos[log_num_cosets..].to_vec()) 179 | .collect::>(); 180 | (coset_index, element_index_in_coset) 181 | } 182 | 183 | fn coset_query_response_to_point_query_response( 184 | queried_coset: CosetQueryResult>, 185 | element_index_in_coset: Vec>>, 186 | ) -> Result>>, SynthesisError> { 187 | queried_coset.into_iter() 188 | .zip(element_index_in_coset.into_iter()) 189 | .map(|(coset_for_all_oracles, element_index)| { 190 | coset_for_all_oracles.into_iter() 191 | // number of constraints here is O(Log(coset size)) 192 | .map(|coset| 193 | // `conditionally_select_power_of_two_vector` need big endian position 194 | FpVar::conditionally_select_power_of_two_vector(&element_index.clone().into_iter().rev().collect::>(), 195 | &coset)) 196 | .collect::>, _>>() 197 | }).collect::>>, _>>() 198 | } 199 | 200 | /// A virtual oracle variable who make query to other virtual or non-virtual 201 | /// oracles. 202 | pub struct VirtualOracleVarWithInfo { 203 | coset_evaluator: Box>, 204 | pub(crate) codeword_domain: Radix2CosetDomain, 205 | pub(crate) localization_param: usize, 206 | pub(crate) test_bound: Vec, 207 | #[allow(unused)] 208 | pub(crate) constraint_bound: Vec, 209 | } 210 | 211 | impl VirtualOracleVarWithInfo { 212 | /// Create a new virtual round given a coset evaluator. Note that one 213 | /// virtual round can have multiple virtual oracles. 214 | pub fn new( 215 | coset_evaluator: Box>, 216 | codeword_domain: Radix2CosetDomain, 217 | localization_param: usize, 218 | test_bound: Vec, 219 | constraint_bound: Vec, 220 | ) -> Self { 221 | Self { 222 | coset_evaluator, 223 | codeword_domain, 224 | localization_param, 225 | test_bound, 226 | constraint_bound, 227 | } 228 | } 229 | 230 | /// Query the virtual oracle points at `positions` in the codeword domain. 231 | pub fn query_point( 232 | &self, 233 | positions: &[Vec>], 234 | iop_messages: &mut MessagesCollectionVar, 235 | ) -> Result>>, SynthesisError> { 236 | // convert the position to coset_index 237 | let log_coset_size = self.get_info().localization_parameter; 238 | let log_num_cosets = ark_std::log2(self.get_info().length) as usize - log_coset_size; 239 | 240 | let (coset_index, element_index_in_coset) = 241 | point_query_to_coset_query(positions, log_num_cosets); 242 | 243 | let queried_coset = self.query_coset(&coset_index, iop_messages)?; 244 | coset_query_response_to_point_query_response(queried_coset, element_index_in_coset) 245 | } 246 | 247 | /// Return the queried coset at `coset_index` of all oracles. 248 | /// `result[i][j][k]` is coset index `i` -> oracle index `j` -> element `k` 249 | /// in this coset. 250 | pub fn query_coset( 251 | &self, 252 | coset_index: &[Vec>], 253 | iop_messages: &mut MessagesCollectionVar, 254 | ) -> Result>, SynthesisError> { 255 | let constituent_oracle_handles = self.coset_evaluator.constituent_oracle_handles(); 256 | let codeword_domain_var = Radix2DomainVar::new( 257 | self.codeword_domain.gen(), 258 | self.codeword_domain.dim() as u64, 259 | FpVar::Constant(self.codeword_domain.offset), 260 | )?; 261 | let constituent_oracles = constituent_oracle_handles // TODO: has bug here 262 | .into_iter() 263 | .map(|(round, idxes)| { 264 | // check idxes have unique elements 265 | debug_assert!( 266 | idxes.iter().collect::>().len() == idxes.len(), 267 | "idxes must be unique" 268 | ); 269 | let query_responses = iop_messages.prover_round(round).query_coset( 270 | &coset_index, 271 | iop_trace!("constituent oracle for virtual oracle"), 272 | )?; 273 | 274 | Ok(query_responses.into_iter() // iterate over cosets 275 | .map(|mut c| { // shape (num_oracles_in_this_round, num_elements_in_coset) 276 | idxes.iter().map(|idx| take(&mut c[idx.idx])).collect::>() // shape (num_oracles_needed_for_this_round, num_elements_in_coset) 277 | }).collect::>()) 278 | // shape: (num_cosets, num_oracles_needed_for_this_round, 279 | // num_elements_in_coset) 280 | }) 281 | .collect::, SynthesisError>>()? 282 | .into_iter() 283 | .fold(vec![vec![]; coset_index.len()], |mut acc, r| { 284 | // shape of r is (num_cosets, num_oracles_needed_for_this_round, 285 | // num_elements_in_coset) result shape: (num_cosets, 286 | // num_oracles_needed_for_all_rounds, num_elements_in_coset) 287 | acc.iter_mut().zip(r).for_each(|(a, r)| { 288 | a.extend(r); 289 | }); 290 | acc 291 | }); 292 | // shape: (num_cosets, num_oracles_needed_for_all_rounds, num_elements_in_coset) 293 | 294 | let queried_cosets = coset_index 295 | .iter() 296 | .map(|i| { 297 | codeword_domain_var.query_position_to_coset(&i[..], self.localization_param as u64) 298 | }) 299 | .collect::, SynthesisError>>()?; 300 | 301 | let query_result = constituent_oracles 302 | .into_iter() 303 | .zip(queried_cosets) 304 | .map(|(cons, coset)| self.coset_evaluator.evaluate_var(coset, &cons)) 305 | .collect::>, SynthesisError>>()?; 306 | 307 | Ok(CosetQueryResult::from_single_oracle_result(query_result)) 308 | } 309 | 310 | /// Get oracle info, including number of oracles for each type and degree 311 | /// bound of each RS code oracle. 312 | pub fn get_info(&self) -> ProverRoundMessageInfo { 313 | ProverRoundMessageInfo::make( 314 | LeavesType::UseCodewordDomain, 315 | self.codeword_domain.size(), 316 | self.localization_param, 317 | ) 318 | .with_reed_solomon_codes_degree_bounds(self.test_bound.clone()) 319 | .build() 320 | } 321 | } 322 | 323 | /// fix a bit array to a certain length by remove extra element on the end or 324 | /// pad with zero 325 | fn fit_bits_to_length(bits: &[Boolean], length: usize) -> Vec> { 326 | if bits.len() < length { 327 | bits.to_vec() 328 | .into_iter() 329 | .chain((0..(length - bits.len())).map(|_| Boolean::FALSE)) 330 | .collect() 331 | } else { 332 | (&bits[0..length]).to_vec() 333 | } 334 | } 335 | 336 | /// An extension trait for `VirtualOracle`, which adds supports for R1CS 337 | /// constraints. 338 | pub trait VirtualOracleVar: 'static { 339 | /// query constituent oracles as a message round handle, and the indices of 340 | /// oracles needed in that round 341 | fn constituent_oracle_handles(&self) -> Vec<(MsgRoundRef, Vec)>; 342 | 343 | /// generate new constraints to evaluate this virtual oracle, using 344 | /// evaluations of constituent oracles on `coset_domain` 345 | fn evaluate_var( 346 | &self, 347 | coset_domain: Radix2DomainVar, 348 | constituent_oracles: &[Vec>], 349 | ) -> Result>, SynthesisError>; 350 | } 351 | -------------------------------------------------------------------------------- /ark-bcs/src/iop/mod.rs: -------------------------------------------------------------------------------- 1 | use ark_std::fmt::Debug; 2 | 3 | /// Bookkeeping references to round oracles 4 | pub mod bookkeeper; 5 | /// Constraints for Public Coin IOP Verifier 6 | #[cfg(feature = "r1cs")] 7 | pub mod constraints; 8 | /// Defines a prover message oracle. 9 | pub mod message; 10 | pub mod oracles; 11 | /// Public Coin IOP Prover 12 | pub mod prover; 13 | /// Public coin IOP verifier 14 | pub mod verifier; 15 | 16 | /// Prover parameter used by IOP Prover. Any IOP prover parameter is a superset 17 | /// of IOP verifier parameter. 18 | pub trait ProverParam: Clone + Debug { 19 | /// Verifier state should be a improper subset of `self`. 20 | type VerifierParameter: VerifierParam; 21 | /// Derive verifier parameter from prover parameter. 22 | fn to_verifier_param(&self) -> Self::VerifierParameter; 23 | } 24 | 25 | impl ProverParam for () { 26 | type VerifierParameter = (); 27 | 28 | fn to_verifier_param(&self) -> Self::VerifierParameter { 29 | // return nothing 30 | } 31 | } 32 | 33 | /// Parameter used by the IOP Verifier. 34 | pub trait VerifierParam: Clone + Debug {} 35 | impl VerifierParam for T {} 36 | -------------------------------------------------------------------------------- /ark-bcs/src/iop/oracles.rs: -------------------------------------------------------------------------------- 1 | //! Code with message recording oracles and succinct oracles. 2 | //! Verifier will not interact with those oracle directly. Instead, they will be 3 | //! wrapped by MessageCollection. 4 | 5 | use ark_ff::PrimeField; 6 | use ark_ldt::domain::Radix2CosetDomain; 7 | use ark_std::{collections::BTreeSet, mem::take}; 8 | 9 | use super::message::{MessagesCollection, ProverRoundMessageInfo}; 10 | use crate::{ 11 | iop::message::{CosetQueryResult, LeavesType, OracleIndex}, 12 | prelude::MsgRoundRef, 13 | }; 14 | use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; 15 | use ark_std::{boxed::Box, vec, vec::Vec}; 16 | 17 | /// A trait for all oracle messages (including RS-code oracles, Non RS-code 18 | /// oracles, and IP short messages) sent in one round. Those oracles (except IP 19 | /// short messages) need to have same length. 20 | /// 21 | /// All oracle messages in the same prover round should will share one merkle 22 | /// tree. Each merkle tree leaf is a vector which each element correspond to the 23 | /// same location of different oracles. The response of each query is itself a 24 | /// vector where `result[i]` is oracle `i`'s leaf on this query position. All 25 | /// `reed_solomon_codes` oracle will come first, and then message oracles. 26 | pub trait RoundOracle: Sized { 27 | /// Get short message in the oracle by index. 28 | fn get_short_message(&self, index: usize) -> &Vec; 29 | 30 | /// Return the leaves of at `position` of all oracle. `result[i][j]` is leaf 31 | /// `i` at oracle `j`. 32 | #[tracing::instrument(skip(self))] 33 | fn query(&mut self, position: &[usize]) -> Vec> { 34 | // convert the position to coset_index 35 | let log_coset_size = self.get_info().localization_parameter; 36 | let log_num_cosets = ark_std::log2(self.get_info().length) as usize - log_coset_size; 37 | let (coset_index, element_index_in_coset) = 38 | point_query_to_coset_query(position, log_num_cosets); 39 | 40 | let queried_coset = self.query_coset_without_tracer(&coset_index); 41 | 42 | coset_query_response_to_point_query_response(queried_coset, element_index_in_coset) 43 | } 44 | 45 | /// Return the queried coset at `coset_index` of all oracles. 46 | /// `result[i][j][k]` is coset index `i` -> oracle index `j` -> element `k` 47 | /// in this coset. 48 | #[tracing::instrument(skip(self))] 49 | fn query_coset(&mut self, coset_index: &[usize]) -> CosetQueryResult { 50 | self.query_coset_without_tracer(coset_index) 51 | } 52 | 53 | /// Return the queried coset at `coset_index` of all oracles, but without 54 | /// tracing information. `result[i][j][k]` is coset index `i` -> oracle 55 | /// index `j` -> element `k` in this coset. 56 | fn query_coset_without_tracer(&mut self, coset_index: &[usize]) -> CosetQueryResult; 57 | 58 | /// Number of reed_solomon_codes oracles in this round. 59 | fn num_reed_solomon_codes_oracles(&self) -> usize; 60 | 61 | /// length of each oracle 62 | fn oracle_length(&self) -> usize; 63 | 64 | /// Get oracle info, including number of oracles for each type and degree 65 | /// bound of each RS code oracle. 66 | fn get_info(&self) -> ProverRoundMessageInfo; 67 | 68 | /// Get degree bound of all reed-solomon codes in this round. 69 | fn get_degree_bound(&self) -> Vec { 70 | self.get_info().reed_solomon_code_degree_bound 71 | } 72 | } 73 | 74 | /// Given point indices, return coset index and element index in coset. 75 | fn point_query_to_coset_query( 76 | point_indices: &[usize], 77 | log_num_cosets: usize, 78 | ) -> (Vec, Vec) { 79 | // coset index = position % num_cosets = the least significant `log_num_cosets` 80 | // bits of pos element index in coset = position / num_cosets = all 81 | // other bits 82 | let coset_index = point_indices 83 | .iter() 84 | .map(|&pos| pos & ((1 << log_num_cosets) - 1)) 85 | .collect::>(); 86 | let element_index_in_coset = point_indices 87 | .iter() 88 | .map(|&pos| pos >> log_num_cosets) 89 | .collect::>(); 90 | (coset_index, element_index_in_coset) 91 | } 92 | 93 | /// Given queried coset elements, recovered the original point query responses. 94 | fn coset_query_response_to_point_query_response( 95 | queried_coset: CosetQueryResult, 96 | element_index_in_coset: Vec, 97 | ) -> Vec> { 98 | queried_coset 99 | .into_iter() 100 | .zip(element_index_in_coset.into_iter()) 101 | .map(|(coset_for_all_oracles, element_index)| { 102 | coset_for_all_oracles 103 | .into_iter() 104 | .map(|coset| coset[element_index]) 105 | .collect::>() 106 | }) 107 | .collect() 108 | } 109 | 110 | #[derive(Clone)] 111 | /// Contains all oracle messages in this round, and is storing queries, in 112 | /// order. **Sponge absorb order**: Sponge will first absorb all merkle tree 113 | /// roots for `reed_solomon_codes`, then all merkle tree 114 | /// roots for `message_oracles`, then all entire messages for `short_messages`. 115 | pub struct RecordingRoundOracle { 116 | /// Oracle Info 117 | pub info: ProverRoundMessageInfo, 118 | /// Store the queried coset index, in order 119 | pub queried_coset_index: Vec, 120 | /// All cosets. Axes: `[coset index, oracle index (RS-code first), element 121 | /// position in coset]` 122 | pub all_coset_elements: Vec>>, 123 | /// low degree oracle evaluations in this round. The data stored is a 124 | /// duplicate to part of `all_coset_elements`, but is handy for prover wants 125 | /// to access it later. 126 | pub(crate) reed_solomon_codes: Vec<(Vec, usize)>, 127 | /// message oracles with no degree bound in this round. The data stored is a 128 | /// duplicate to part of `all_coset_elements`, but is handy if the prover 129 | /// wants to access it later. 130 | pub(crate) message_oracles: Vec>, 131 | /// Store the non-oracle IP messages in this round 132 | pub(crate) short_messages: Vec>, 133 | } 134 | 135 | impl RecordingRoundOracle { 136 | /// Get reed solomon codes sent in this round. 137 | pub fn reed_solomon_codes(&self) -> &Vec<(Vec, usize)> { 138 | &self.reed_solomon_codes 139 | } 140 | /// Get message oracles with degree bound sent in this round. 141 | pub fn message_oracles(&self) -> &Vec> { 142 | &self.message_oracles 143 | } 144 | /// Get short non-oracle messages sent in this round. 145 | pub fn short_messages(&self) -> &Vec> { 146 | &self.short_messages 147 | } 148 | 149 | /// Return a succinct oracle, which only contains queried responses. 150 | pub fn get_succinct(&self) -> SuccinctRoundMessage { 151 | let queried_cosets = self 152 | .queried_coset_index 153 | .iter() 154 | .map(|coset_index| self.all_coset_elements[*coset_index].clone()) 155 | .collect::>(); 156 | SuccinctRoundMessage { 157 | queried_cosets, 158 | short_messages: self.short_messages.clone(), 159 | } 160 | } 161 | } 162 | 163 | impl RoundOracle for RecordingRoundOracle { 164 | fn get_short_message(&self, index: usize) -> &Vec { 165 | &self.short_messages[index] 166 | } 167 | 168 | fn query_coset_without_tracer(&mut self, coset_index: &[usize]) -> CosetQueryResult { 169 | // record the coset query 170 | self.queried_coset_index.extend_from_slice(coset_index); 171 | let result = coset_index 172 | .iter() 173 | .map(|coset_index| { 174 | self.all_coset_elements[*coset_index % self.all_coset_elements.len()].clone() 175 | }) 176 | .collect(); 177 | result 178 | } 179 | 180 | fn num_reed_solomon_codes_oracles(&self) -> usize { 181 | self.info.num_reed_solomon_codes_oracles() 182 | } 183 | 184 | fn oracle_length(&self) -> usize { 185 | self.info.length 186 | } 187 | 188 | fn get_info(&self) -> ProverRoundMessageInfo { 189 | self.info.clone() 190 | } 191 | } 192 | 193 | /// Succinct Round message that is going to be included in the proof. 194 | #[derive(Clone, CanonicalSerialize, CanonicalDeserialize)] 195 | pub struct SuccinctRoundMessage { 196 | /// Queried cosets. Axes `[query order, oracle index (RS-code first), 197 | /// element position in coset]` 198 | pub queried_cosets: Vec>>, 199 | // note that we do not store query position here, as they will be calculated in verifier 200 | /// Store the non-oracle IP messages in this round 201 | pub short_messages: Vec>, 202 | } 203 | 204 | impl SuccinctRoundMessage { 205 | /// Return a view of `self` such that the view records queries to the 206 | /// oracle. 207 | pub fn get_view(&self, info: ProverRoundMessageInfo) -> SuccinctRoundOracle { 208 | SuccinctRoundOracle { 209 | info, 210 | underlying_message: self, 211 | coset_queries: Vec::new(), 212 | current_query_pos: 0, 213 | } 214 | } 215 | } 216 | 217 | /// A reference to the succinct round message plus a state recording current 218 | /// query position. 219 | #[derive(Clone)] 220 | pub struct SuccinctRoundOracle<'a, F: PrimeField> { 221 | pub(crate) underlying_message: &'a SuccinctRoundMessage, 222 | /// Round Message Info expected by verifier 223 | pub info: ProverRoundMessageInfo, 224 | /// Supposed queries of the verifier in order. 225 | pub coset_queries: Vec, 226 | current_query_pos: usize, 227 | } 228 | 229 | impl<'a, F: PrimeField> RoundOracle for SuccinctRoundOracle<'a, F> { 230 | fn get_short_message(&self, index: usize) -> &Vec { 231 | &self.underlying_message.short_messages[index] 232 | } 233 | 234 | fn query_coset_without_tracer(&mut self, coset_index: &[usize]) -> CosetQueryResult { 235 | self.coset_queries.extend_from_slice(coset_index); 236 | assert!( 237 | self.current_query_pos + coset_index.len() 238 | <= self.underlying_message.queried_cosets.len(), 239 | "too many queries!" 240 | ); 241 | let result = self.underlying_message.queried_cosets 242 | [self.current_query_pos..self.current_query_pos + coset_index.len()] 243 | .to_vec(); 244 | self.current_query_pos += coset_index.len(); 245 | result.into() 246 | } 247 | 248 | fn num_reed_solomon_codes_oracles(&self) -> usize { 249 | self.info.reed_solomon_code_degree_bound.len() 250 | } 251 | 252 | fn oracle_length(&self) -> usize { 253 | self.info.length 254 | } 255 | 256 | fn get_info(&self) -> ProverRoundMessageInfo { 257 | self.info.clone() 258 | } 259 | } 260 | 261 | /// A virtual oracle who make query to other virtual or non-virtual oracles. 262 | pub struct VirtualOracleWithInfo { 263 | coset_evaluator: Box>, 264 | pub(crate) codeword_domain: Radix2CosetDomain, 265 | pub(crate) localization_param: usize, 266 | pub(crate) test_bound: Vec, 267 | #[allow(unused)] 268 | pub(crate) constraint_bound: Vec, 269 | // TODO: number of oracles 270 | } 271 | 272 | impl VirtualOracleWithInfo { 273 | /// Create a new virtual round given a coset evaluator. Note that one 274 | /// virtual round can have multiple virtual oracles. 275 | pub fn new( 276 | coset_evaluator: Box>, 277 | codeword_domain: Radix2CosetDomain, 278 | localization_param: usize, 279 | test_bound: Vec, 280 | constraint_bound: Vec, 281 | ) -> Self { 282 | Self { 283 | coset_evaluator, 284 | codeword_domain, 285 | localization_param, 286 | test_bound, 287 | constraint_bound, 288 | } 289 | } 290 | 291 | /// Query the virtual oracle points at `positions` in the codeword domain. 292 | pub fn query_point>( 293 | &self, 294 | positions: &[usize], 295 | iop_messages: &mut MessagesCollection, 296 | ) -> Vec> { 297 | let log_coset_size = self.localization_param; // is also localization param 298 | let log_num_cosets = ark_std::log2(self.codeword_domain.size()) as usize - log_coset_size; 299 | 300 | let (coset_index, element_index_in_coset) = 301 | point_query_to_coset_query(positions, log_num_cosets); 302 | 303 | let queried_coset = self.query_coset(&coset_index, iop_messages); 304 | coset_query_response_to_point_query_response(queried_coset, element_index_in_coset) 305 | } 306 | 307 | /// Query the virtual oracle cosets at `coset_index` in the codeword domain. 308 | pub fn query_coset>( 309 | &self, 310 | coset_index: &[usize], 311 | iop_messages: &mut MessagesCollection, 312 | ) -> CosetQueryResult { 313 | // first, construct constituent oracles 314 | let constituent_oracle_handles = self.coset_evaluator.constituent_oracle_handles(); 315 | 316 | // constituent_oracles[i][j][k] is coset_index[i] -> oracle_index[j] -> 317 | // element_index[k] 318 | let constituent_oracles = constituent_oracle_handles 319 | .into_iter() 320 | .map(|(round, idxes)| { 321 | // check idxes have unique elements 322 | debug_assert!( 323 | idxes.iter().collect::>().len() == idxes.len(), 324 | "idxes must be unique" 325 | ); 326 | let query_responses = iop_messages.prover_round(round).query_coset( 327 | coset_index, 328 | iop_trace!("constituent oracle for virtual oracle"), 329 | ); 330 | 331 | query_responses.into_iter() // iterate over cosets 332 | .map(|mut c| { // shape (num_oracles_in_this_round, num_elements_in_coset) 333 | idxes.iter().map(|idx| take(&mut c[idx.idx])).collect::>() // shape (num_oracles_needed_for_this_round, num_elements_in_coset) 334 | }).collect::>() 335 | // shape: (num_cosets, num_oracles_needed_for_this_round, 336 | // num_elements_in_coset) 337 | }) 338 | .fold(vec![vec![]; coset_index.len()], |mut acc, r| { 339 | // shape of r is (num_cosets, num_oracles_needed_for_this_round, 340 | // num_elements_in_coset) result shape: (num_cosets, 341 | // num_oracles_needed_for_all_rounds, num_elements_in_coset) 342 | acc.iter_mut().zip(r).for_each(|(a, r)| { 343 | a.extend(r); 344 | }); 345 | acc 346 | }); 347 | // shape: (num_cosets, num_oracles_needed_for_all_rounds, num_elements_in_coset) 348 | 349 | // convert coset index to cosets 350 | let queried_cosets = coset_index 351 | .iter() 352 | .map(|&i| { 353 | self.codeword_domain 354 | .query_position_to_coset(i, self.localization_param) 355 | .1 356 | }) 357 | .collect::>(); 358 | 359 | let query_result = constituent_oracles 360 | .into_iter() 361 | .zip(queried_cosets) 362 | .map(|(cons, coset)| self.coset_evaluator.evaluate(coset, &cons)) 363 | .collect::>>(); 364 | 365 | CosetQueryResult::from_single_oracle_result(query_result) 366 | } 367 | 368 | /// Get information about this oracle. 369 | pub fn get_info(&self) -> ProverRoundMessageInfo { 370 | ProverRoundMessageInfo::make( 371 | LeavesType::UseCodewordDomain, 372 | self.codeword_domain.size(), 373 | self.localization_param, 374 | ) 375 | .with_reed_solomon_codes_degree_bounds(self.test_bound.clone()) 376 | .build() 377 | } 378 | } 379 | 380 | /// evaluator for virtual oracle 381 | /// It is enforced that implementors do not contain any reference with lifetime. 382 | pub trait VirtualOracle: 'static { 383 | /// query constituent oracles as a message round handle, and the indices of 384 | /// oracles needed in that round 385 | fn constituent_oracle_handles(&self) -> Vec<(MsgRoundRef, Vec)>; 386 | /// evaluate this virtual oracle, using evaluations of constituent oracles 387 | /// on `coset_domain` 388 | fn evaluate( 389 | &self, 390 | coset_domain: Radix2CosetDomain, 391 | constituent_oracles: &[Vec], 392 | ) -> Vec; 393 | } 394 | -------------------------------------------------------------------------------- /ark-bcs/src/iop/prover.rs: -------------------------------------------------------------------------------- 1 | use crate::{bcs::transcript::Transcript, iop::ProverParam}; 2 | use ark_crypto_primitives::merkle_tree::Config as MTConfig; 3 | use ark_ff::PrimeField; 4 | use ark_sponge::{Absorb, CryptographicSponge}; 5 | 6 | use super::bookkeeper::NameSpace; 7 | 8 | /// A Prover for Public Coin IOP. This is intended to be used as an endpoint 9 | /// protocol. Any subprotocol does not need to implement this trait. 10 | /// Any implementation of this trait can be transformed to SNARG by BCS. 11 | pub trait IOPProver { 12 | /// Prover parameter should be a superset of verifier parameter. 13 | type ProverParameter: ProverParam; 14 | 15 | /// Public input 16 | type PublicInput: ?Sized; 17 | /// Private input 18 | type PrivateInput: ?Sized; 19 | 20 | /// Run the interactive prover, given the initial state, transcript, and 21 | /// parameter. If the prover involves a subprotocol, consider create a 22 | /// separate namespace for them. 23 | fn prove, S: CryptographicSponge>( 24 | namespace: NameSpace, 25 | public_input: &Self::PublicInput, 26 | private_input: &Self::PrivateInput, 27 | transcript: &mut Transcript, 28 | prover_parameter: &Self::ProverParameter, 29 | ) -> Result<(), crate::Error> 30 | where 31 | MT::InnerDigest: Absorb; 32 | } 33 | -------------------------------------------------------------------------------- /ark-bcs/src/iop/verifier.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::PrimeField; 2 | use ark_sponge::{Absorb, CryptographicSponge}; 3 | 4 | use crate::{ 5 | bcs::simulation_transcript::SimulationTranscript, 6 | iop::{message::MessagesCollection, prover::IOPProver, ProverParam, VerifierParam}, 7 | Error, 8 | }; 9 | use ark_crypto_primitives::merkle_tree::Config as MTConfig; 10 | 11 | use super::{bookkeeper::NameSpace, oracles::RoundOracle}; 12 | 13 | /// The verifier for public coin IOP has two phases. This is intended to be 14 | /// used as an endpoint protocol. Any subprotocol does not need to implement 15 | /// this trait. Any implementation of this trait can be transformed to SNARG by 16 | /// BCS. 17 | /// * **Commit Phase**: Verifier send message that is uniformly sampled from a 18 | /// random oracle. Verifier 19 | /// will receive prover oracle, that can use used to query later. This protocol 20 | /// relies on public coin assumption described in [BCS16, section 4.1](https://eprint.iacr.org/2016/116.pdf#subsection.4.1), that the verifier does not 21 | /// main state and postpones any query to after the commit phase. 22 | /// * **Query And Decision Phase**: Verifier sends query and receive answer from 23 | /// message oracle. 24 | pub trait IOPVerifier { 25 | /// Verifier Output 26 | /// 27 | /// TODO: Consider if we need to make sure `success` state is in 28 | /// `VerifierOutput` by using a trait. If verification failed, set `success` 29 | /// to false instead of panicking or returning `Err` result. 30 | type VerifierOutput: Clone; 31 | /// Verifier Parameter. Verifier parameter can be accessed in 32 | /// `register_iop_structure`, and can affect transcript structure 33 | /// (e.g. number of rounds and degree bound). 34 | type VerifierParameter: VerifierParam; 35 | /// Public input. Public input cannot be accessed in 36 | /// `register_iop_structure`, and thus cannot affect transcript 37 | /// structure (e.g. number of rounds). 38 | type PublicInput: ?Sized; 39 | 40 | /// Simulate interaction with prover in commit phase, reconstruct verifier 41 | /// messages and verifier state using the sponge provided in the 42 | /// simulation transcript. Returns the verifier state for query and decision 43 | /// phase. 44 | /// 45 | /// When writing test, use `transcript.check_correctness` after calling this 46 | /// method to verify the correctness of this method. 47 | fn register_iop_structure>( 48 | namespace: NameSpace, 49 | transcript: &mut SimulationTranscript, 50 | verifier_parameter: &Self::VerifierParameter, 51 | ) where 52 | MT::InnerDigest: Absorb; 53 | 54 | /// Query the oracle using the random oracle. Run the verifier code, and 55 | /// return verifier output that is valid if prover claim is true. 56 | /// Verifier will return an error if prover message is obviously false, 57 | /// or oracle cannot answer the query. 58 | fn query_and_decide>( 59 | namespace: NameSpace, 60 | verifier_parameter: &Self::VerifierParameter, 61 | public_input: &Self::PublicInput, 62 | sponge: &mut S, 63 | transcript_messages: &mut MessagesCollection, 64 | ) -> Result; 65 | } 66 | 67 | /// `IOPVerifierForProver` is an auto-implemented trait. User does not 68 | /// need to derive this trait manually. 69 | /// 70 | /// This trait is an extension for `IOPVerifier`, requiring that the verifier 71 | /// state type and parameter type is consistent with what is expected from the 72 | /// prover implementation. 73 | /// 74 | /// Any IOPVerifier that satisfies this requirement 75 | /// automatically implements this trait. 76 | pub trait IOPVerifierForProver>: 77 | IOPVerifier 78 | where 79 | Self: IOPVerifier< 80 | S, 81 | F, 82 | VerifierParameter = ::VerifierParameter, 83 | PublicInput = P::PublicInput, 84 | >, 85 | { 86 | } 87 | impl, V> 88 | IOPVerifierForProver for V 89 | where 90 | V: IOPVerifier< 91 | S, 92 | F, 93 | VerifierParameter = ::VerifierParameter, 94 | PublicInput = P::PublicInput, 95 | >, 96 | { 97 | } 98 | -------------------------------------------------------------------------------- /ark-bcs/src/ldt/constraints/mod.rs: -------------------------------------------------------------------------------- 1 | /// LDT that runs FRI gadget on a random linear combination. 2 | pub mod rl_ldt; 3 | 4 | use ark_crypto_primitives::merkle_tree::{constraints::ConfigGadget, Config}; 5 | use ark_ff::PrimeField; 6 | use ark_r1cs_std::fields::fp::FpVar; 7 | use ark_relations::r1cs::SynthesisError; 8 | use ark_sponge::{ 9 | constraints::{AbsorbGadget, SpongeWithGadget}, 10 | Absorb, 11 | }; 12 | 13 | use crate::{ 14 | bcs::constraints::transcript::SimulationTranscriptVar, 15 | iop::{ 16 | bookkeeper::NameSpace, constraints::message::MessagesCollectionVar, message::MsgRoundRef, 17 | }, 18 | ldt::{NoLDT, LDT}, 19 | }; 20 | 21 | /// An extension trait of `LDT`. Any implementation of this trait have R1CS 22 | /// gadget for LDT. 23 | pub trait LDTWithGadget: LDT { 24 | /// Simulate interaction with prover in commit phase, reconstruct verifier 25 | /// messages and verifier state using the sponge provided in the 26 | /// simulation transcript. Returns the verifier state for query and decision 27 | /// phase. 28 | /// * `num_codewords_oracles`: sum of number of codeword oracles in each 29 | /// round. 30 | fn register_iop_structure_var( 31 | namespace: NameSpace, 32 | param: &Self::LDTParameters, 33 | num_rs_oracles: usize, 34 | transcript: &mut SimulationTranscriptVar, 35 | ) -> Result<(), SynthesisError> 36 | where 37 | MT: Config, 38 | MTG: ConfigGadget]>, 39 | S: SpongeWithGadget, 40 | MT::InnerDigest: Absorb, 41 | MTG::InnerDigest: AbsorbGadget; 42 | 43 | /// R1CS gadget for `query_and_decide`. 44 | /// 45 | /// Verify `codewords` is low-degree, given the succinct codewords oracle 46 | /// and proof. 47 | fn query_and_decide_var>( 48 | namespace: NameSpace, 49 | param: &Self::LDTParameters, 50 | sponge: &mut S::Var, 51 | codewords: &[MsgRoundRef], 52 | // TODO: add virtual oracle here 53 | transcript_messages: &mut MessagesCollectionVar, 54 | ) -> Result<(), SynthesisError>; 55 | } 56 | 57 | impl LDTWithGadget for NoLDT { 58 | fn register_iop_structure_var( 59 | _namespace: NameSpace, 60 | _param: &Self::LDTParameters, 61 | _num_rs_oracles: usize, 62 | _transcript: &mut SimulationTranscriptVar, 63 | ) -> Result<(), SynthesisError> 64 | where 65 | MT: Config, 66 | MTG: ConfigGadget]>, 67 | S: SpongeWithGadget, 68 | MT::InnerDigest: Absorb, 69 | MTG::InnerDigest: AbsorbGadget, 70 | { 71 | Ok(()) 72 | } 73 | 74 | fn query_and_decide_var>( 75 | _namespace: NameSpace, 76 | _param: &Self::LDTParameters, 77 | _sponge: &mut S::Var, 78 | codewords: &[MsgRoundRef], 79 | // TODO: add virtual oracle here 80 | transcript_messages: &mut MessagesCollectionVar, 81 | ) -> Result<(), SynthesisError> { 82 | // nop, but we need to check that all codewords have no RS codes 83 | let no_rs_code = codewords.iter().all(|round| { 84 | transcript_messages 85 | .get_prover_round_info(*round) 86 | .num_reed_solomon_codes_oracles() 87 | == 0 88 | }); 89 | assert!( 90 | no_rs_code, 91 | "NoLDT enforces that main protocol does not send any RS code." 92 | ); 93 | Ok(()) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /ark-bcs/src/ldt/constraints/rl_ldt.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bcs::constraints::transcript::SimulationTranscriptVar, 3 | iop::{ 4 | bookkeeper::{BookkeeperContainer, NameSpace}, 5 | constraints::message::MessagesCollectionVar, 6 | message::{MsgRoundRef, ProverRoundMessageInfo}, 7 | }, 8 | ldt::{constraints::LDTWithGadget, rl_ldt::LinearCombinationLDT}, 9 | }; 10 | use ark_crypto_primitives::merkle_tree::{constraints::ConfigGadget, Config}; 11 | use ark_ff::PrimeField; 12 | use ark_ldt::{domain::Radix2CosetDomain, fri::constraints::FRIVerifierGadget}; 13 | use ark_r1cs_std::{ 14 | boolean::Boolean, 15 | eq::EqGadget, 16 | fields::{fp::FpVar, FieldVar}, 17 | poly::polynomial::univariate::dense::DensePolynomialVar, 18 | }; 19 | use ark_relations::r1cs::SynthesisError; 20 | use ark_sponge::{ 21 | constraints::{AbsorbGadget, CryptographicSpongeVar, SpongeWithGadget}, 22 | Absorb, 23 | }; 24 | use ark_std::vec::Vec; 25 | 26 | impl LDTWithGadget for LinearCombinationLDT { 27 | fn register_iop_structure_var( 28 | namespace: NameSpace, 29 | param: &Self::LDTParameters, 30 | num_rs_oracles: usize, 31 | transcript: &mut SimulationTranscriptVar, 32 | ) -> Result<(), SynthesisError> 33 | where 34 | MT: Config, 35 | MTG: ConfigGadget]>, 36 | S: SpongeWithGadget, 37 | MT::InnerDigest: Absorb, 38 | MTG::InnerDigest: AbsorbGadget, 39 | { 40 | transcript.squeeze_verifier_field_elements(num_rs_oracles)?; 41 | transcript.submit_verifier_current_round(namespace, iop_trace!()); 42 | 43 | let mut current_domain = param.fri_parameters.domain; 44 | 45 | // receive ldt message oracles 46 | param.fri_parameters.localization_parameters 47 | [0..param.fri_parameters.localization_parameters.len() - 1] 48 | .iter() 49 | .zip(param.fri_parameters.localization_parameters[1..].iter()) 50 | .try_for_each( 51 | |(&localization_curr, &localization_next)| -> Result<_, SynthesisError> { 52 | transcript.squeeze_verifier_field_elements(1)?; 53 | transcript.submit_verifier_current_round(namespace, iop_trace!()); 54 | 55 | let next_domain = current_domain.fold(localization_curr); 56 | transcript.receive_prover_current_round( 57 | namespace, 58 | ProverRoundMessageInfo::new_using_custom_length_and_localization( 59 | next_domain.size(), 60 | localization_next as usize, 61 | ) 62 | .with_num_message_oracles(1) 63 | .build(), 64 | iop_trace!("LDT prover message"), 65 | )?; 66 | 67 | current_domain = next_domain; 68 | Ok(()) 69 | }, 70 | )?; 71 | 72 | // receive final polynomials 73 | 74 | transcript.squeeze_verifier_field_elements(1)?; 75 | transcript.submit_verifier_current_round(namespace, iop_trace!()); 76 | transcript.receive_prover_current_round( 77 | namespace, 78 | ProverRoundMessageInfo::new_using_custom_length_and_localization(0, 0) 79 | .with_num_short_messages(1) 80 | .build(), 81 | iop_trace!("LDT final polynomial"), 82 | )?; 83 | 84 | Ok(()) 85 | } 86 | 87 | fn query_and_decide_var>( 88 | namespace: NameSpace, 89 | param: &Self::LDTParameters, 90 | sponge: &mut S::Var, 91 | codewords: &[MsgRoundRef], 92 | transcript_messages: &mut MessagesCollectionVar, 93 | ) -> Result<(), SynthesisError> { 94 | let span = tracing::span!(tracing::Level::INFO, "LDT QueryVar"); 95 | let _enter = span.enter(); 96 | 97 | let codeword_log_num_cosets = param.fri_parameters.domain.dim() 98 | - param.fri_parameters.localization_parameters[0] as usize; 99 | 100 | let query_indices = (0..param.num_queries) 101 | .map(|_| sponge.squeeze_bits(codeword_log_num_cosets)) 102 | .collect::, _>>()?; 103 | 104 | // restore random coefficients and alphas 105 | let random_coefficients = transcript_messages.verifier_round((namespace, 0))[0] 106 | .clone() 107 | .try_into_field_elements() 108 | .unwrap(); 109 | 110 | let alphas = (1..param.fri_parameters.localization_parameters.len() + 1) 111 | .map(|idx| { 112 | let vm = transcript_messages.verifier_round((namespace, idx)); 113 | assert_eq!(vm.len(), 1); 114 | let vm_curr = vm[0].clone().try_into_field_elements().unwrap(); 115 | assert_eq!(vm_curr.len(), 1); 116 | vm_curr.into_iter().next().unwrap() 117 | }) 118 | .collect::>(); 119 | 120 | query_indices 121 | .into_iter() 122 | .try_for_each(|coset_index| -> Result<(), SynthesisError> { 123 | // prepare query 124 | let (query_cosets, query_indices, domain_final) = 125 | FRIVerifierGadget::prepare_query(coset_index, ¶m.fri_parameters)?; 126 | 127 | // get query responses in codewords oracles 128 | let mut codewords_oracle_responses = (0..query_cosets[0].size()) 129 | .map(|_| FpVar::constant(F::zero())) 130 | .collect::>(); 131 | 132 | codewords 133 | .iter() 134 | .map(|oracle| -> Result<_, SynthesisError> { 135 | let query_responses = transcript_messages 136 | .prover_round(*oracle) 137 | .query_coset( 138 | &[query_indices[0].clone()], 139 | iop_trace!("rl_ldt query codewords"), 140 | )? 141 | .assume_single_coset() 142 | .into_iter() 143 | .map(|round| round.into_iter()); 144 | let degrees = transcript_messages 145 | .get_prover_round_info(*oracle) 146 | .reed_solomon_code_degree_bound; 147 | Ok(query_responses.zip(degrees.into_iter())) 148 | }) 149 | .collect::, _>>()? 150 | .into_iter() 151 | .flatten() 152 | .zip(random_coefficients.iter()) 153 | .try_for_each( 154 | |((msg, degree_bound), coeff)| -> Result<(), SynthesisError> { 155 | assert_eq!(codewords_oracle_responses.len(), msg.len()); 156 | assert!(param.fri_parameters.tested_degree > degree_bound as u64); 157 | let degree_raise_poly_at_coset = degree_raise_poly_query( 158 | param.fri_parameters.domain, 159 | param.fri_parameters.tested_degree - degree_bound as u64, 160 | param.fri_parameters.localization_parameters[0], 161 | &query_indices[0], 162 | )?; 163 | debug_assert_eq!( 164 | codewords_oracle_responses.len(), 165 | degree_raise_poly_at_coset.len() 166 | ); 167 | codewords_oracle_responses 168 | .iter_mut() 169 | .zip(msg.into_iter().zip(degree_raise_poly_at_coset.into_iter())) 170 | .for_each(|(dst, (src_oracle, src_raise))| { 171 | *dst += coeff * &src_oracle * src_raise 172 | }); 173 | Ok(()) 174 | }, 175 | )?; 176 | 177 | // get query responses in ldt prover messages oracles 178 | assert_eq!( 179 | transcript_messages.num_prover_rounds_in_namespace(namespace), 180 | query_indices.len() 181 | ); 182 | let round_oracle_responses = query_indices[1..] 183 | .iter() 184 | .zip( 185 | transcript_messages 186 | .prover_round_refs_in_namespace(namespace) 187 | .clone() 188 | .into_iter(), 189 | ) 190 | .map(|(query_index, msg)| -> Result<_, SynthesisError> { 191 | let mut response = transcript_messages 192 | .prover_round(msg) 193 | .query_coset( 194 | &[query_index.clone()], 195 | iop_trace!("rl_ldt query fri message"), 196 | )? 197 | .assume_single_coset(); // get the first coset position (only one position) 198 | assert_eq!(response.len(), 1); 199 | Ok(response.pop().unwrap()) 200 | }) 201 | .collect::, _>>()?; 202 | 203 | // get final polynomial 204 | let final_polynomial_coeffs = { 205 | let &oracle_ref = transcript_messages 206 | .prover_round_refs_in_namespace(namespace) 207 | .last() 208 | .unwrap(); 209 | transcript_messages 210 | .prover_round(oracle_ref) 211 | .short_message(0, iop_trace!("final poly coefficients")) 212 | }; 213 | let total_shrink_factor = param 214 | .fri_parameters 215 | .localization_parameters 216 | .iter() 217 | .sum::(); 218 | let final_poly_degree_bound = 219 | param.fri_parameters.tested_degree >> total_shrink_factor; 220 | 221 | // make sure final polynomial degree is valid 222 | assert!(final_polynomial_coeffs.len() <= (final_poly_degree_bound + 1) as usize); // we should let prover do `generate_low_degree_coefficients 223 | let final_polynomial = 224 | DensePolynomialVar::from_coefficients_vec(final_polynomial_coeffs); 225 | let result = FRIVerifierGadget::consistency_check( 226 | ¶m.fri_parameters, 227 | &query_indices, 228 | &query_cosets, 229 | &ark_std::iter::once(codewords_oracle_responses) 230 | .chain(round_oracle_responses.into_iter()) 231 | .collect::>(), 232 | &alphas, 233 | &domain_final, 234 | &final_polynomial, 235 | )?; 236 | 237 | result.enforce_equal(&Boolean::TRUE) 238 | }) 239 | } 240 | } 241 | 242 | /// return evaluation of x^{degree_to_raise} at specific location (with coset 243 | /// structure) For now, we assume the offset of codeword domain is constant. 244 | /// TODO: in the future, the offset can also be an variable. 245 | fn degree_raise_poly_query( 246 | domain: Radix2CosetDomain, 247 | degree_to_raise: u64, 248 | log_coset_size: u64, 249 | coset_index: &[Boolean], 250 | ) -> Result>, SynthesisError> { 251 | let mut result = Vec::with_capacity(1 << log_coset_size); 252 | let dist_between_coset_elems = 1 << (domain.dim() - log_coset_size as usize); 253 | 254 | let mut curr = FpVar::constant(domain.offset.pow(&[degree_to_raise])) 255 | * FpVar::constant(domain.gen()) 256 | .pow_le(coset_index)? 257 | .pow_by_constant(&[degree_to_raise])?; 258 | 259 | let step = FpVar::constant( 260 | domain 261 | .gen() 262 | .pow(&[dist_between_coset_elems]) 263 | .pow(&[degree_to_raise]), 264 | ); 265 | 266 | for _ in 0..(1 << log_coset_size) { 267 | result.push(curr.clone()); 268 | curr *= &step; 269 | } 270 | Ok(result) 271 | } 272 | 273 | #[cfg(test)] 274 | mod tests { 275 | use crate::ldt::constraints::rl_ldt::degree_raise_poly_query; 276 | use ark_bls12_381::Fr; 277 | use ark_ldt::domain::Radix2CosetDomain; 278 | use ark_poly::{polynomial::univariate::DensePolynomial, DenseUVPolynomial}; 279 | use ark_r1cs_std::{alloc::AllocVar, boolean::Boolean, R1CSVar}; 280 | use ark_relations::r1cs::ConstraintSystem; 281 | use ark_std::{vec, vec::Vec, One, Zero}; 282 | 283 | #[test] 284 | fn test_degree_raise_poly() { 285 | let domain = Radix2CosetDomain::new_radix2_coset(64, Fr::from(123456u128)); 286 | let cs = ConstraintSystem::new_ref(); 287 | // x^17 288 | let poly = DensePolynomial::from_coefficients_vec( 289 | (0..17) 290 | .map(|_| Fr::zero()) 291 | .chain(ark_std::iter::once(Fr::one())) 292 | .collect(), 293 | ); 294 | 295 | let expected_eval = domain.evaluate(&poly); 296 | 297 | let query_position = vec![ 298 | Boolean::new_witness(cs.clone(), || Ok(true)).unwrap(), 299 | Boolean::new_witness(cs.clone(), || Ok(true)).unwrap(), 300 | ]; // 3 301 | 302 | let (queries, _) = domain.query_position_to_coset(3, 2); 303 | let expected_ans = queries 304 | .iter() 305 | .map(|&i| expected_eval[i]) 306 | .collect::>(); 307 | 308 | let actual_ans = degree_raise_poly_query(domain, 17, 2, &query_position).unwrap(); 309 | 310 | assert_eq!(expected_ans.len(), actual_ans.len()); 311 | expected_ans 312 | .into_iter() 313 | .zip(actual_ans.into_iter()) 314 | .for_each(|(expected, actual)| assert_eq!(expected, actual.value().unwrap())) 315 | } 316 | 317 | // ldt correctness is tested in mock protocol 318 | } 319 | -------------------------------------------------------------------------------- /ark-bcs/src/ldt/mod.rs: -------------------------------------------------------------------------------- 1 | use ark_std::marker::PhantomData; 2 | 3 | use ark_crypto_primitives::merkle_tree::Config as MTConfig; 4 | use ark_ff::PrimeField; 5 | use ark_ldt::domain::Radix2CosetDomain; 6 | use ark_sponge::{Absorb, CryptographicSponge}; 7 | 8 | use crate::{ 9 | bcs::{simulation_transcript::SimulationTranscript, transcript::Transcript}, 10 | iop::{ 11 | bookkeeper::NameSpace, 12 | message::{MessagesCollection, MsgRoundRef}, 13 | oracles::RoundOracle, 14 | }, 15 | Error, 16 | }; 17 | 18 | #[cfg(feature = "r1cs")] 19 | /// R1CS constraints for LDT. 20 | pub mod constraints; 21 | /// LDT that runs FRI on a random linear combination. 22 | pub mod rl_ldt; 23 | 24 | /// Trait for LDT, which is an public coin IOP. 25 | pub trait LDT { 26 | /// Parameters of `Self` 27 | type LDTParameters: Clone; 28 | 29 | /// Return the codeword domain used by this LDT. 30 | fn codeword_domain(param: &Self::LDTParameters) -> Option>; 31 | 32 | /// Return the localization parameter (size of query coset) of codewords 33 | /// used by this LDT. 34 | fn localization_param(param: &Self::LDTParameters) -> Option; 35 | 36 | /// Given the list of message round references along with its degree bound, 37 | /// generate a low degree test proof all reed solomon codes in each 38 | /// reference. 39 | fn prove, S: CryptographicSponge>( 40 | namespace: NameSpace, 41 | param: &Self::LDTParameters, 42 | transcript: &mut Transcript, 43 | codewords: &[MsgRoundRef], 44 | ) -> Result<(), Error> 45 | where 46 | MT::InnerDigest: Absorb; 47 | 48 | /// Simulate interaction with prover in commit phase, reconstruct verifier 49 | /// messages and verifier state using the sponge provided in the 50 | /// simulation transcript. Returns the verifier state for query and decision 51 | /// phase. 52 | fn register_iop_structure, S: CryptographicSponge>( 53 | namespace: NameSpace, 54 | param: &Self::LDTParameters, 55 | num_rs_oracles: usize, 56 | transcript: &mut SimulationTranscript, 57 | ) where 58 | MT::InnerDigest: Absorb; 59 | 60 | /// Verify `codewords` is low-degree, given the succinct codewords oracle 61 | /// and proof. 62 | /// 63 | /// * `codewords`: All codewords references whose reed solomon codes are 64 | /// going to be low degree tested. We can treat it as a specialized 65 | /// version of `oracle_ref`. 66 | /// * `ldt_prover_message_oracles`: LDT Prover messages sent. 67 | fn query_and_decide>( 68 | namespace: NameSpace, 69 | param: &Self::LDTParameters, 70 | sponge: &mut S, 71 | codewords: &[MsgRoundRef], 72 | transcript_messages: &mut MessagesCollection, 73 | ) -> Result<(), Error>; 74 | } 75 | 76 | /// A placeholder LDT, which does nothing. 77 | pub struct NoLDT { 78 | _do_nothing: PhantomData, 79 | } 80 | 81 | impl NoLDT { 82 | /// Specify the evaluation domain and localization_parameter of the 83 | /// codeword, using this dummy LDT. 84 | pub fn parameter( 85 | evaluation_domain: Radix2CosetDomain, 86 | localization_parameter: usize, 87 | ) -> (Radix2CosetDomain, usize) { 88 | (evaluation_domain, localization_parameter) 89 | } 90 | } 91 | 92 | impl LDT for NoLDT { 93 | /// If LDTParameters is None, `ldt_info` will panic, so transcript would not 94 | /// allow low degree oracles to be sent. 95 | type LDTParameters = Option<(Radix2CosetDomain, usize)>; 96 | 97 | /// `prove` for NoLDT is no-op. 98 | fn prove, S: CryptographicSponge>( 99 | _namespace: NameSpace, 100 | _param: &Self::LDTParameters, 101 | _transcript: &mut Transcript, 102 | _codewords: &[MsgRoundRef], 103 | ) -> Result<(), Error> 104 | where 105 | MT::InnerDigest: Absorb, 106 | { 107 | Ok(()) 108 | } 109 | 110 | fn register_iop_structure, S: CryptographicSponge>( 111 | _namespace: NameSpace, 112 | _param: &Self::LDTParameters, 113 | _num_codewords_oracles: usize, 114 | _transcript: &mut SimulationTranscript, 115 | ) where 116 | MT::InnerDigest: Absorb, 117 | { 118 | // do nothing 119 | } 120 | 121 | fn query_and_decide>( 122 | _namespace: NameSpace, 123 | _param: &Self::LDTParameters, 124 | _sponge: &mut S, 125 | codewords: &[MsgRoundRef], 126 | transcript_messages: &mut MessagesCollection, 127 | ) -> Result<(), Error> { 128 | // nop, but we need to check that all codewords have no RS codes 129 | let no_rs_code = codewords.iter().all(|round| { 130 | transcript_messages 131 | .get_prover_round_info(*round) 132 | .num_reed_solomon_codes_oracles() 133 | == 0 134 | }); 135 | assert!( 136 | no_rs_code, 137 | "NoLDT enforces that main protocol does not send any RS code." 138 | ); 139 | Ok(()) 140 | } 141 | 142 | fn codeword_domain(_param: &Self::LDTParameters) -> Option> { 143 | None 144 | } 145 | 146 | fn localization_param(_param: &Self::LDTParameters) -> Option { 147 | None 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /ark-bcs/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | #![doc = include_str!("../../README.md")] 3 | #![deny(unused_import_braces, unused_qualifications, trivial_casts)] 4 | #![deny(trivial_numeric_casts, private_in_public, variant_size_differences)] 5 | #![deny(stable_features, unreachable_pub, non_shorthand_field_patterns)] 6 | #![deny(unused_attributes, unused_mut)] 7 | #![deny(missing_docs)] 8 | #![deny(unused_imports)] 9 | #![deny(renamed_and_removed_lints, stable_features, unused_allocation)] 10 | #![deny(unused_comparisons, bare_trait_objects, unused_must_use)] 11 | #![forbid(unsafe_code)] 12 | #![recursion_limit = "256"] 13 | 14 | #[macro_use] 15 | extern crate derivative; 16 | 17 | /// Defines some helper to debug user's IOP protocol. 18 | #[macro_use] 19 | pub mod tracer; 20 | 21 | /// A public coin interactive oracle proof protocol. 22 | /// Prover sends out messages that can be encoded to field elements. Verifier 23 | /// sample messages using random oracle defined in `ark-sponge`. 24 | pub mod iop; 25 | 26 | /// A compiler to convert any public coin IOP to non-interactive one using BCS 27 | /// transform. Source: [BCS16](https://eprint.iacr.org/2016/116) 28 | pub mod bcs; 29 | /// Low-degree test for RS-IOP. This implementation compiles LDT round function, 30 | /// defined in `ark-ldt` to an IOP. 31 | pub mod ldt; 32 | 33 | /// Some handy imports for users. 34 | pub mod prelude; 35 | #[cfg(test)] 36 | pub(crate) mod test_utils; 37 | 38 | use ark_std::boxed::Box; 39 | 40 | /// Universal Error Type 41 | pub type Error = Box; 42 | -------------------------------------------------------------------------------- /ark-bcs/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // export transcript 2 | pub use crate::bcs::{simulation_transcript::SimulationTranscript, transcript::Transcript}; 3 | 4 | // export queried message 5 | pub use crate::iop::message::{MessagesCollection, MsgRoundRef, ProverRoundMessageInfo}; 6 | -------------------------------------------------------------------------------- /ark-bcs/src/test_utils/mod.rs: -------------------------------------------------------------------------------- 1 | use ark_bls12_381::Fr; 2 | use ark_ff::PrimeField; 3 | use ark_sponge::poseidon::{find_poseidon_ark_and_mds, PoseidonConfig}; 4 | 5 | pub(crate) fn poseidon_parameters() -> PoseidonConfig { 6 | let full_rounds = 8; 7 | let partial_rounds = 31; 8 | let alpha = 5; 9 | let rate = 2; 10 | 11 | let (ark, mds) = find_poseidon_ark_and_mds::( 12 | Fr::MODULUS_BIT_SIZE as u64, 13 | rate, 14 | full_rounds, 15 | partial_rounds, 16 | 0, 17 | ); 18 | 19 | PoseidonConfig::new( 20 | full_rounds as usize, 21 | partial_rounds as usize, 22 | alpha, 23 | mds, 24 | ark, 25 | rate, 26 | 1, 27 | ) 28 | } 29 | 30 | #[cfg(feature = "r1cs")] 31 | mod constraints {} 32 | -------------------------------------------------------------------------------- /ark-bcs/src/tracer.rs: -------------------------------------------------------------------------------- 1 | use ark_std::fmt::{Debug, Display, Formatter}; 2 | 3 | #[derive(Clone, Copy)] 4 | #[allow(unused)] 5 | /// Tracing information for IOP protocol code. 6 | pub struct TraceInfo { 7 | pub(crate) description: Option<&'static str>, 8 | pub(crate) file_name: &'static str, 9 | pub(crate) line: u32, 10 | pub(crate) column: u32, 11 | } 12 | 13 | impl Display for TraceInfo { 14 | fn fmt(&self, f: &mut Formatter<'_>) -> ark_std::fmt::Result { 15 | if let Some(description) = self.description { 16 | f.write_fmt(format_args!( 17 | "[{}] at {}:{}:{}", 18 | description, self.file_name, self.line, self.column 19 | )) 20 | } else { 21 | f.write_fmt(format_args!( 22 | "[anonymous] at {}:{}:{}", 23 | self.file_name, self.line, self.column 24 | )) 25 | } 26 | } 27 | } 28 | 29 | impl Debug for TraceInfo { 30 | fn fmt(&self, f: &mut Formatter<'_>) -> ark_std::fmt::Result { 31 | ::fmt(self, f) 32 | } 33 | } 34 | 35 | impl Default for TraceInfo { 36 | fn default() -> Self { 37 | Self { 38 | description: None, 39 | file_name: "Dummy", 40 | line: 0, 41 | column: 0, 42 | } 43 | } 44 | } 45 | 46 | impl TraceInfo { 47 | /// Returns a new `TraceInfo`. Note that this function should not be 48 | /// directly called. Instead, use `iop_trace!` instead. 49 | pub const fn new( 50 | description: Option<&'static str>, 51 | file_name: &'static str, 52 | line: u32, 53 | column: u32, 54 | ) -> Self { 55 | TraceInfo { 56 | description, 57 | file_name, 58 | line, 59 | column, 60 | } 61 | } 62 | 63 | /// Return a reference to the description of the trace. If None, return "". 64 | pub const fn description(&self) -> &str { 65 | match self.description { 66 | None => "", 67 | Some(s) => s, 68 | } 69 | } 70 | } 71 | 72 | #[macro_export] 73 | /// Returns the tracing information at this point. 74 | macro_rules! iop_trace { 75 | () => {{ 76 | use $crate::tracer::*; 77 | TraceInfo::new(None, file!(), line!(), column!()) 78 | }}; 79 | ($description: expr) => {{ 80 | use $crate::tracer::*; 81 | TraceInfo::new(Some($description), file!(), line!(), column!()) 82 | }}; 83 | } 84 | 85 | #[cfg(test)] 86 | mod compile_tests { 87 | use crate::tracer::TraceInfo; 88 | 89 | #[test] 90 | fn test_it_compiles() { 91 | let _tracer1 = iop_trace!(); 92 | 93 | let _tracer2 = iop_trace!("some message title"); 94 | #[cfg(feature = "std")] 95 | { 96 | eprintln!("tracer1: {}", _tracer1); 97 | eprintln!("tracer2: {}", _tracer2); 98 | } 99 | } 100 | 101 | const TRACE1: TraceInfo = iop_trace!(); 102 | const TRACE2: TraceInfo = iop_trace!("some message title"); 103 | const TRACE2_STR: &str = TRACE2.description(); 104 | 105 | #[test] 106 | fn test_it_compilers_in_const() { 107 | #[cfg(feature = "std")] 108 | { 109 | eprintln!("tracer1: {}", TRACE1); 110 | eprintln!("tracer2: {}", TRACE2); 111 | eprintln!("tracer2 str: {}", TRACE2_STR); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | condense_wildcard_suffixes = true 2 | edition = "2021" 3 | imports_granularity = "Crate" 4 | match_block_trailing_comma = true 5 | normalize_comments = true 6 | reorder_imports = true 7 | use_field_init_shorthand = true 8 | use_try_shorthand = true 9 | wrap_comments = true 10 | max_width = 100 -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /sumcheck/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uni_sumcheck" 3 | version = "0.3.0" 4 | authors = [ 5 | "Tom Shen" 6 | ] 7 | description = "A library for Univariate Sumcheck Protocol" 8 | keywords = ["cryptography"] 9 | categories = ["cryptography"] 10 | include = ["Cargo.toml", "src", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] 11 | license = "MIT/Apache-2.0" 12 | edition = "2018" 13 | 14 | [dependencies] 15 | ark-serialize = { version = "^0.3.0", default-features = false, features = ["derive"] } 16 | ark-ff = { version = "^0.3.0", default-features = false } 17 | ark-poly = { version = "^0.3.0", default-features = false } 18 | ark-sponge = { version = "^0.3.0", default-features = false } 19 | ark-crypto-primitives = { version = "^0.3.0", default-features = false } 20 | ark-std = { version = "^0.3.0", default-features = false } 21 | ark-ldt = { version = "^0.1.0", default-features = false } 22 | ark-relations = { version = "^0.3.0", default-features = false, optional = true } 23 | ark-r1cs-std = { version = "^0.3.1", default-features = false, optional = true } 24 | ark-bcs = { path = "../ark-bcs", default-features = false } 25 | 26 | tracing = { version = "0.1", default-features = false, features = ["attributes"] } 27 | derivative = { version = "2.0", features = ["use_core"] } 28 | hashbrown = "0.11.2" 29 | rayon = { version = "1.5", optional = true } 30 | 31 | [dev-dependencies] 32 | ark-ed-on-bls12-381 = { version = "^0.3.0", default-features = false } 33 | ark-bls12-381 = { version = "^0.3.0", default-features = false, features = ["curve"] } 34 | ark-bls12-377 = { version = "^0.3.0", default-features = false, features = ["curve"] } 35 | 36 | [features] 37 | default = ["std", "parallel"] 38 | std = ["ark-serialize/std", "ark-ff/std", "ark-poly/std", "ark-sponge/std", "ark-crypto-primitives/std", 39 | "ark-std/std", "ark-relations/std", "ark-r1cs-std/std", "ark-ldt/std", "ark-bcs/std"] 40 | r1cs = ["ark-relations", "ark-r1cs-std", "ark-sponge/r1cs", "ark-crypto-primitives/r1cs", "ark-ldt/r1cs", "ark-bcs/r1cs"] 41 | parallel = ["std", "ark-ff/parallel", "ark-poly/parallel", "ark-std/parallel", "rayon"] 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /sumcheck/src/constraints.rs: -------------------------------------------------------------------------------- 1 | use crate::UnivariateSumcheck; 2 | use alloc::{vec, vec::Vec}; 3 | use ark_bcs::{ 4 | bcs::constraints::transcript::SimulationTranscriptVar, 5 | iop::{bookkeeper::NameSpace, constraints::oracles::VirtualOracleVar, message::OracleIndex}, 6 | iop_trace, 7 | prelude::{MsgRoundRef, ProverRoundMessageInfo}, 8 | }; 9 | use ark_crypto_primitives::merkle_tree::{constraints::ConfigGadget, Config}; 10 | use ark_ff::PrimeField; 11 | use ark_ldt::domain::Radix2CosetDomain; 12 | use ark_r1cs_std::{ 13 | fields::fp::FpVar, 14 | poly::domain::{vanishing_poly::VanishingPolynomial, Radix2DomainVar}, 15 | prelude::*, 16 | }; 17 | use ark_relations::r1cs::SynthesisError; 18 | use ark_sponge::{ 19 | constraints::{AbsorbGadget, SpongeWithGadget}, 20 | Absorb, 21 | }; 22 | 23 | #[derive(Debug, Clone)] 24 | pub struct SumcheckPOracleVar { 25 | pub summation_domain: Radix2CosetDomain, 26 | 27 | pub claimed_sum: FpVar, 28 | pub order_h_inv_times_claimed_sum: FpVar, 29 | 30 | pub h_handle: (MsgRoundRef, OracleIndex), 31 | pub f_handle: (MsgRoundRef, OracleIndex), 32 | } 33 | 34 | impl SumcheckPOracleVar { 35 | pub fn new( 36 | summation_domain: Radix2CosetDomain, 37 | claimed_sum: FpVar, 38 | h_handle: (MsgRoundRef, OracleIndex), 39 | f_handle: (MsgRoundRef, OracleIndex), 40 | ) -> Self { 41 | let order_h_inv_times_claimed_sum = 42 | &claimed_sum * F::from(summation_domain.size() as u64).inverse().unwrap(); 43 | Self { 44 | summation_domain, 45 | claimed_sum, 46 | order_h_inv_times_claimed_sum, 47 | h_handle, 48 | f_handle, 49 | } 50 | } 51 | } 52 | 53 | impl VirtualOracleVar for SumcheckPOracleVar { 54 | fn constituent_oracle_handles(&self) -> Vec<(MsgRoundRef, Vec)> { 55 | vec![ 56 | (self.h_handle.0, vec![self.h_handle.1]), 57 | (self.f_handle.0, vec![self.f_handle.1]), 58 | ] 59 | } 60 | 61 | fn evaluate_var( 62 | &self, 63 | coset_domain: Radix2DomainVar, 64 | constituent_oracles: &[Vec>], 65 | ) -> Result>, SynthesisError> { 66 | let h_eval = &constituent_oracles[0]; 67 | let f_eval = &constituent_oracles[1]; 68 | let mut cur_x_inv = coset_domain.offset().inverse().unwrap(); 69 | 70 | let z_h = VanishingPolynomial::new( 71 | self.summation_domain.offset, 72 | self.summation_domain.dim() as u64, 73 | ); 74 | let z_h_eval = coset_domain 75 | .elements() 76 | .into_iter() 77 | .map(|x| z_h.evaluate_constraints(&x)) 78 | .collect::, SynthesisError>>()?; 79 | let gen_inv = coset_domain.gen.inverse().unwrap(); 80 | assert_eq!(h_eval.len(), f_eval.len()); 81 | assert_eq!(h_eval.len(), z_h_eval.len()); 82 | assert_eq!(h_eval.len(), coset_domain.size() as usize); 83 | Ok(f_eval 84 | .iter() 85 | .zip(h_eval) 86 | .zip(z_h_eval) 87 | .map(|((f, h), z_h)| { 88 | let result = (f - &self.order_h_inv_times_claimed_sum - &z_h * h) * &cur_x_inv; 89 | cur_x_inv = &cur_x_inv * gen_inv; 90 | result 91 | }) 92 | .collect()) 93 | } 94 | } 95 | 96 | impl UnivariateSumcheck { 97 | pub fn register_sumcheck_commit_phase_var< 98 | MT: Config, 99 | MTG: ConfigGadget]>, 100 | S: SpongeWithGadget, 101 | >( 102 | &self, 103 | transcript: &mut SimulationTranscriptVar, 104 | ns: NameSpace, 105 | f_handle: (MsgRoundRef, OracleIndex), 106 | claimed_sum: FpVar, 107 | ) -> Result<(), SynthesisError> 108 | where 109 | MTG::InnerDigest: AbsorbGadget, 110 | MT::InnerDigest: Absorb, 111 | { 112 | // receive h with no degree bound 113 | let round_info = ProverRoundMessageInfo::new_using_codeword_domain(transcript) 114 | .with_num_message_oracles(1) 115 | .build(); 116 | let h_handle = 117 | transcript.receive_prover_current_round(ns, round_info, iop_trace!("h oracle"))?; 118 | 119 | // register g as a virtual oracle 120 | let g_oracle = SumcheckPOracleVar::new( 121 | self.summation_domain, 122 | claimed_sum, 123 | (h_handle, (0, false).into()), 124 | f_handle, 125 | ); 126 | let test_bound = self.degree_bound_of_g(); 127 | transcript.register_prover_virtual_round( 128 | ns, 129 | g_oracle, 130 | vec![test_bound], 131 | vec![test_bound], 132 | iop_trace!("g oracle"), 133 | ); 134 | Ok(()) 135 | } 136 | } 137 | 138 | #[cfg(test)] 139 | mod tests { 140 | use crate::{ 141 | protocol::tests::{FieldMTConfig, MockProtocol, MockProverParam, MockVerifierParam}, 142 | test_util::poseidon_parameters, 143 | UnivariateSumcheck, 144 | }; 145 | use ark_bcs::{ 146 | bcs::{ 147 | constraints::{ 148 | proof::BCSProofVar, transcript::SimulationTranscriptVar, 149 | verifier::BCSVerifierGadget, MTHashParametersVar, 150 | }, 151 | prover::BCSProof, 152 | MTHashParameters, 153 | }, 154 | iop::{ 155 | bookkeeper::NameSpace, 156 | constraints::{message::MessagesCollectionVar, IOPVerifierWithGadget}, 157 | message::OracleIndex, 158 | ProverParam, 159 | }, 160 | iop_trace, 161 | ldt::rl_ldt::{LinearCombinationLDT, LinearCombinationLDTParameters}, 162 | prelude::ProverRoundMessageInfo, 163 | }; 164 | use ark_bls12_381::Fr; 165 | use ark_crypto_primitives::{ 166 | crh::poseidon::constraints::CRHParametersVar, 167 | merkle_tree::{constraints::ConfigGadget, Config, IdentityDigestConverter}, 168 | }; 169 | use ark_ldt::domain::Radix2CosetDomain; 170 | use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial}; 171 | use ark_r1cs_std::{ 172 | alloc::{AllocVar, AllocationMode}, 173 | fields::fp::FpVar, 174 | }; 175 | use ark_relations::{ 176 | ns, 177 | r1cs::{ConstraintSystem, ConstraintSystemRef, Namespace, SynthesisError}, 178 | }; 179 | use ark_sponge::{ 180 | constraints::{AbsorbGadget, CryptographicSpongeVar, SpongeWithGadget}, 181 | poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}, 182 | Absorb, CryptographicSponge, 183 | }; 184 | use ark_std::{test_rng, vec}; 185 | use core::borrow::Borrow; 186 | 187 | #[derive(Clone, Debug)] 188 | pub struct MockVerifierParamVar { 189 | pub summation_domain: Radix2CosetDomain, 190 | pub claimed_sum: FpVar, 191 | } 192 | 193 | impl AllocVar for MockVerifierParamVar { 194 | fn new_variable>( 195 | cs: impl Into>, 196 | f: impl FnOnce() -> Result, 197 | mode: AllocationMode, 198 | ) -> Result { 199 | let val = f()?; 200 | let val = val.borrow(); 201 | let var = Self { 202 | summation_domain: val.summation_domain, 203 | claimed_sum: FpVar::new_variable(cs, || Ok(val.claimed_sum), mode)?, 204 | }; 205 | Ok(var) 206 | } 207 | } 208 | 209 | impl> IOPVerifierWithGadget for MockProtocol { 210 | type VerifierParameterVar = MockVerifierParamVar; 211 | 212 | type VerifierOutputVar = (); 213 | type PublicInputVar = (); 214 | 215 | fn register_iop_structure_var]>>( 216 | namespace: NameSpace, 217 | transcript: &mut SimulationTranscriptVar, 218 | verifier_parameter: &Self::VerifierParameterVar, 219 | ) -> Result<(), SynthesisError> 220 | where 221 | MT::InnerDigest: Absorb, 222 | MTG::InnerDigest: AbsorbGadget, 223 | { 224 | let poly_info = ProverRoundMessageInfo::new_using_codeword_domain(transcript) 225 | .with_num_message_oracles(1) 226 | .build(); 227 | let poly_handle = transcript.receive_prover_current_round( 228 | namespace, 229 | poly_info, 230 | iop_trace!("poly to sum"), 231 | )?; 232 | let sumcheck = UnivariateSumcheck { 233 | summation_domain: verifier_parameter.summation_domain, 234 | }; 235 | let sumcheck_ns = transcript.new_namespace(namespace, iop_trace!("sumcheck")); 236 | sumcheck.register_sumcheck_commit_phase_var( 237 | transcript, 238 | sumcheck_ns, 239 | (poly_handle, OracleIndex::new(0, false)), 240 | verifier_parameter.claimed_sum.clone(), 241 | ) 242 | } 243 | 244 | fn query_and_decide_var<'a>( 245 | _cs: ConstraintSystemRef, 246 | _namespace: NameSpace, 247 | _verifier_parameter: &Self::VerifierParameterVar, 248 | _public_input_var: &Self::PublicInputVar, 249 | _sponge: &mut S::Var, 250 | _transcript_messages: &mut MessagesCollectionVar<'a, Fr>, 251 | ) -> Result { 252 | // nothing to do here. LDT is everything. 253 | Ok(()) 254 | } 255 | } 256 | 257 | impl ConfigGadget for FieldMTConfig { 258 | type Leaf = [FpVar]; 259 | type LeafDigest = FpVar; 260 | type LeafInnerConverter = IdentityDigestConverter>; 261 | type InnerDigest = FpVar; 262 | type LeafHash = ark_crypto_primitives::crh::poseidon::constraints::CRHGadget; 263 | type TwoToOneHash = 264 | ark_crypto_primitives::crh::poseidon::constraints::TwoToOneCRHGadget; 265 | } 266 | 267 | #[test] 268 | fn test_constraints_end_to_end() { 269 | let mut rng = test_rng(); 270 | let poseidon_param = poseidon_parameters(); 271 | let sponge = PoseidonSponge::new(&poseidon_param); 272 | 273 | let codeword_domain = Radix2CosetDomain::new_radix2_coset(256, Fr::from(0x12345u128)); 274 | let ldt_param = LinearCombinationLDTParameters::new(128, vec![1, 2, 1], codeword_domain, 5); 275 | let summation_domain = Radix2CosetDomain::new_radix2_coset(32, Fr::from(0x6789u128)); 276 | 277 | let poly = DensePolynomial::rand(100, &mut rng); 278 | 279 | let claimed_sum = summation_domain.evaluate(&poly).into_iter().sum::(); 280 | 281 | let cs = ConstraintSystem::::new_ref(); 282 | 283 | let mt_hash_param = MTHashParameters:: { 284 | leaf_hash_param: poseidon_param.clone(), 285 | inner_hash_param: poseidon_param.clone(), 286 | }; 287 | 288 | let poseidon_param_var = 289 | CRHParametersVar::new_constant(cs.clone(), poseidon_parameters()).unwrap(); 290 | 291 | let mt_hash_param_var = MTHashParametersVar:: { 292 | leaf_params: poseidon_param_var.clone(), 293 | inner_params: poseidon_param_var.clone(), 294 | }; 295 | 296 | let prover_param = MockProverParam { 297 | summation_domain, 298 | poly, 299 | claimed_sum, 300 | }; 301 | 302 | let verifier_param = prover_param.to_verifier_param(); 303 | let verifier_param_var = 304 | MockVerifierParamVar::new_witness(ns!(cs, "verifier_param"), || Ok(verifier_param)) 305 | .unwrap(); 306 | 307 | let proof = BCSProof::generate::, _>( 308 | sponge.clone(), 309 | &(), 310 | &(), 311 | &prover_param, 312 | &ldt_param, 313 | mt_hash_param.clone(), 314 | ) 315 | .unwrap(); 316 | let proof_var = BCSProofVar::new_witness(ns!(cs, "proof"), || Ok(proof)).unwrap(); 317 | 318 | let sponge_var = PoseidonSpongeVar::new(ns!(cs, "sponge").cs(), &poseidon_param); 319 | 320 | BCSVerifierGadget::verify::, PoseidonSponge>( 321 | cs.clone(), 322 | sponge_var, 323 | &proof_var, 324 | &(), 325 | &verifier_param_var, 326 | &ldt_param, 327 | &mt_hash_param_var, 328 | ) 329 | .unwrap(); 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /sumcheck/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | extern crate alloc; 3 | 4 | use ark_ff::PrimeField; 5 | use ark_ldt::domain::Radix2CosetDomain; 6 | use ark_sponge::Absorb; 7 | 8 | #[cfg(feature = "r1cs")] 9 | pub mod constraints; 10 | pub mod protocol; 11 | #[cfg(test)] 12 | pub(crate) mod test_util; 13 | pub mod vp; 14 | 15 | /// Univariate Sumcheck Protocol with a fixed summation domain 16 | pub struct UnivariateSumcheck { 17 | pub summation_domain: Radix2CosetDomain, 18 | } 19 | -------------------------------------------------------------------------------- /sumcheck/src/test_util.rs: -------------------------------------------------------------------------------- 1 | use ark_bls12_381::Fr; 2 | use ark_ff::PrimeField; 3 | use ark_sponge::poseidon::{find_poseidon_ark_and_mds, PoseidonConfig}; 4 | 5 | pub(crate) fn poseidon_parameters() -> PoseidonConfig { 6 | let full_rounds = 8; 7 | let partial_rounds = 31; 8 | let alpha = 5; 9 | let rate = 2; 10 | 11 | let (ark, mds) = find_poseidon_ark_and_mds::( 12 | Fr::MODULUS_BIT_SIZE as u64, 13 | rate, 14 | full_rounds, 15 | partial_rounds, 16 | 0, 17 | ); 18 | 19 | PoseidonConfig::new( 20 | full_rounds as usize, 21 | partial_rounds as usize, 22 | alpha, 23 | mds, 24 | ark, 25 | rate, 26 | 1, 27 | ) 28 | } 29 | 30 | #[cfg(feature = "r1cs")] 31 | mod constraints {} 32 | -------------------------------------------------------------------------------- /sumcheck/src/vp.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::PrimeField; 2 | use ark_ldt::domain::Radix2CosetDomain; 3 | use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; 4 | use ark_std::{ops::Mul, vec, vec::Vec, Zero}; 5 | 6 | /// Vanishing polynomial for a multiplicative coset H where |H| is a power of 2. 7 | /// As H is a coset, every element can be described as b*g^i and therefore 8 | /// has vanishing polynomial Z_H(x) = x^|H| - b^|H| 9 | #[derive(Clone, Debug, Copy)] 10 | pub struct VanishingPoly { 11 | /// `|H|`: the size of the multiplicative coset 12 | degree: usize, 13 | /// `b^|H|` 14 | shift: F, 15 | } 16 | 17 | impl VanishingPoly { 18 | pub fn new(coset: Radix2CosetDomain) -> Self { 19 | VanishingPoly { 20 | degree: coset.size(), 21 | shift: coset.offset.pow(&[coset.size() as u64]), 22 | } 23 | } 24 | 25 | /// point^|H| - b^|H| 26 | pub fn evaluation_at_point(&self, point: F) -> F { 27 | point.pow(&[self.degree as u64]) - self.shift 28 | } 29 | 30 | /// |H|*point^{|H| - 1} 31 | pub fn formal_derivative_at_point(&self, point: F) -> F { 32 | F::from(self.degree as u64) * point.pow(&[self.degree as u64 - 1]) 33 | } 34 | 35 | /// Evaluate `self` (H) over `coset` (S). Returns `s ^ |H| - b^|H|` for all 36 | /// `s` in `S`. Reference: https://github.com/scipr-lab/libiop/blob/a2ed2ec2f3e85f29b6035951553b02cb737c817a/libiop/algebra/polynomials/vanishing_polynomial.tcc#L116 37 | pub fn evaluation_over_coset(&self, coset: &Radix2CosetDomain) -> Vec { 38 | // size of S 39 | let order_s = coset.size() as u64; 40 | // size of H 41 | let order_h = self.degree as u64; 42 | 43 | // Suppose S = hg^{i}, and the expected result is `h^|H| * g^{i|H|} - b^|H|` 44 | 45 | // h^|H| 46 | let shift_to_order_h = coset.offset.pow(&[order_h]); 47 | 48 | if order_h % order_s == 0 { 49 | // we know |S| <= |H| and |H| % |S| = 0. 50 | // so, g^{i|H|} = 1 for all i 51 | // so vp(s) = h^|H| - b^|H| for all s in S 52 | let vps = shift_to_order_h - self.shift; 53 | return vec![vps; coset.size()]; 54 | } 55 | 56 | // g^|H| 57 | let coset_gen_to_order_h = coset.gen().pow(&[order_h]); 58 | 59 | if order_s % order_h == 0 { 60 | // we know |S| >= |H| and |S| % |H| = 0 61 | // therefore, f(X) = X^|H| is a homomorphism from S to a subgroup of order 62 | // |S|/|H| since P = X^|H| - shift, and shift is independent of X, 63 | // we can see there are only |S|/|H| distinct evaluations 64 | // and these repeat 65 | let num_distinct_eval = order_s / order_h; 66 | let evaluation_repetitions = order_h as usize; 67 | let mut cur = shift_to_order_h; 68 | (0..num_distinct_eval) 69 | .map(|_| { 70 | let result = cur - self.shift; 71 | cur *= coset_gen_to_order_h; 72 | result 73 | }) 74 | .collect::>() 75 | .repeat(evaluation_repetitions) 76 | } else { 77 | let mut cur = shift_to_order_h; 78 | (0..order_s) 79 | .map(|_| { 80 | let result = cur - self.shift; 81 | cur *= coset_gen_to_order_h; 82 | result 83 | }) 84 | .collect() 85 | } 86 | } 87 | 88 | pub fn degree(&self) -> usize { 89 | self.degree 90 | } 91 | 92 | pub fn constant_coefficient(&self) -> F { 93 | self.shift 94 | } 95 | 96 | pub fn dense_poly(&self) -> DensePolynomial { 97 | let mut coeffs = vec![F::zero(); self.degree + 1]; 98 | coeffs[0] = -self.shift; 99 | coeffs[self.degree] = F::one(); 100 | DensePolynomial::from_coefficients_vec(coeffs) 101 | } 102 | } 103 | 104 | impl Mul<&DensePolynomial> for VanishingPoly { 105 | type Output = DensePolynomial; 106 | 107 | fn mul(self, rhs: &DensePolynomial) -> DensePolynomial { 108 | // (x^|H| - b^|H|) * f(x) = x^|H| * f(x) - b^|H| * f(x) 109 | let mut result_coeffs = vec![F::zero(); rhs.degree() + self.degree + 1]; 110 | 111 | // result += x^|H| * f(x) 112 | result_coeffs[self.degree..self.degree + rhs.coeffs.len()].copy_from_slice(&rhs.coeffs); 113 | result_coeffs[0..rhs.coeffs.len()] 114 | .iter_mut() 115 | .zip(rhs.coeffs.iter()) 116 | .for_each(|(x, &a)| *x -= a * self.shift); 117 | DensePolynomial::from_coefficients_vec(result_coeffs) 118 | } 119 | } 120 | 121 | pub trait DivVanishingPoly: Sized { 122 | /// divide `self` by `vp`. Return quotient and remainder. 123 | /// 124 | /// This function is different from `ark-poly::divide_by_vanishing_poly` in 125 | /// that this function supports vanishing polynomial for coset, which is 126 | /// more general. 127 | #[must_use] 128 | fn divide_by_vp(&self, vp: VanishingPoly) -> (Self, Self); 129 | } 130 | 131 | impl DivVanishingPoly for DensePolynomial { 132 | fn divide_by_vp(&self, vp: VanishingPoly) -> (Self, Self) { 133 | // inverse of the leading term 134 | if self.degree() < vp.degree() { 135 | // `self` is remainder 136 | return (DensePolynomial::zero(), self.clone()); 137 | } 138 | 139 | // suppose self = \sum_{i=0}^{k|H| + b} a_i x^i; vp = x^|H| - S 140 | // then, the quotient is \sum_{i=1}^{k|H| + b} a_i x^{i- |H|} + a_i * S * x^{i - 141 | // 2|H|} + a_i * S^2 * x^{i - 3|H|} + ...+ a_i * S^{k-1} * x^{i - k|H|} 142 | // where k is the maximum int such that i - k|H| >= 0 143 | let mut quotient_vec = self.coeffs[vp.degree..].to_vec(); 144 | let mut s_pow = vp.shift; 145 | for r in 1..(self.len() / vp.degree) { 146 | // add a_i * S^{r + 1 -1} * x^{i - (r+1)|H|} 147 | quotient_vec 148 | .iter_mut() 149 | .zip(&self.coeffs[vp.degree * (r + 1)..]) 150 | .for_each(|(s, &c)| *s += c * s_pow); 151 | s_pow *= vp.shift; 152 | } 153 | 154 | // remainder = self - quotient * vp 155 | // = self - quotient * (x^|H| - S) 156 | // = self.first_H_terms + self.other_terms - quotient * (x^|H| - S) 157 | // we know self.other_terms - quotient * x^|H| = 0 because remainder has degree 158 | // |H| so, remainder = self.first_H_terms - quotient * (-S) = 159 | // self.first_H_terms + quotient * s 160 | let mut remainder_vec = self.coeffs[..vp.degree].to_vec(); 161 | remainder_vec 162 | .iter_mut() 163 | .zip(quotient_vec.iter()) 164 | .for_each(|(a, &b)| *a += b * vp.shift); 165 | 166 | ( 167 | DensePolynomial::from_coefficients_vec(quotient_vec), 168 | DensePolynomial::from_coefficients_vec(remainder_vec), 169 | ) 170 | } 171 | } 172 | 173 | #[cfg(test)] 174 | mod tests { 175 | use super::*; 176 | use ark_ff::Field; 177 | use ark_poly::{univariate::DenseOrSparsePolynomial, Polynomial}; 178 | use ark_std::{test_rng, UniformRand}; 179 | 180 | type F = ark_bls12_381::Fr; 181 | 182 | fn test_coset_evaluate_template( 183 | vp_coset: Radix2CosetDomain, 184 | coset: Radix2CosetDomain, 185 | ) -> Vec { 186 | let expected_eval = (0..coset.size()) 187 | .map(|i| { 188 | coset.element(i).pow(&[vp_coset.size() as u64]) 189 | - vp_coset.offset.pow(&[vp_coset.size() as u64]) 190 | }) 191 | .collect::>(); 192 | let actual_eval = VanishingPoly::new(vp_coset).evaluation_over_coset(&coset); 193 | assert_eq!(actual_eval, expected_eval); 194 | actual_eval 195 | } 196 | 197 | #[test] 198 | fn test_coset_template() { 199 | let mut rng = test_rng(); 200 | let vp_domain = Radix2CosetDomain::new_radix2_coset(128, F::rand(&mut rng)); 201 | // case 1: |S| <= |H| and |H| % |S| = 0. 202 | let coset = Radix2CosetDomain::new_radix2_coset(32, F::rand(&mut rng)); 203 | test_coset_evaluate_template(vp_domain, coset); 204 | 205 | // case 1.2: |S| = |H| 206 | let coset = Radix2CosetDomain::new_radix2_coset(128, F::rand(&mut rng)); 207 | test_coset_evaluate_template(vp_domain, coset); 208 | 209 | // case 2: |S| >= |H| and |S| % |H| = 0. 210 | let coset = Radix2CosetDomain::new_radix2_coset(256, F::rand(&mut rng)); 211 | test_coset_evaluate_template(vp_domain, coset); 212 | 213 | // for now, it's not possible to have |S| % |H| != 0 or |H| % |S| != 0, 214 | // because they are both power of 2. 215 | 216 | // more complex case 217 | let coset = Radix2CosetDomain::new_radix2_coset(256, F::rand(&mut rng)); 218 | let eval = test_coset_evaluate_template(vp_domain, coset); 219 | (0..(256usize >> 2)).for_each(|i| { 220 | let (pos, coset) = coset.query_position_to_coset(i, 2); 221 | let coset_eval = test_coset_evaluate_template(vp_domain, coset); 222 | let expected = pos.into_iter().map(|p| eval[p]).collect::>(); 223 | assert_eq!(coset_eval, expected); 224 | }) 225 | } 226 | 227 | #[test] 228 | fn test_dense_poly() { 229 | let mut rng = test_rng(); 230 | let vp_domain = Radix2CosetDomain::new_radix2_coset(256, F::rand(&mut rng)); 231 | let vp = VanishingPoly::new(vp_domain); 232 | 233 | let point = F::rand(&mut rng); 234 | let expected = vp.evaluation_at_point(point); 235 | let actual = vp.dense_poly().evaluate(&point); 236 | assert_eq!(actual, expected); 237 | } 238 | 239 | #[test] 240 | fn test_mul() { 241 | let mut rng = test_rng(); 242 | let vp_domain = Radix2CosetDomain::new_radix2_coset(256, F::rand(&mut rng)); 243 | let vp = VanishingPoly::new(vp_domain); 244 | 245 | let poly = DensePolynomial::::rand(17, &mut rng); 246 | 247 | let point = F::rand(&mut rng); 248 | 249 | let expected = vp.evaluation_at_point(point) * poly.evaluate(&point); 250 | let actual = (vp * &poly).evaluate(&point); 251 | 252 | assert_eq!(actual, expected); 253 | } 254 | 255 | #[test] 256 | fn test_div() { 257 | let mut rng = test_rng(); 258 | let vp_domain = Radix2CosetDomain::new_radix2_coset(128, F::rand(&mut rng)); 259 | let vp = VanishingPoly::new(vp_domain); 260 | for d in 1..999 { 261 | let poly = DensePolynomial::rand(d, &mut rng); 262 | 263 | let (expected_q, expected_r) = DenseOrSparsePolynomial::from(poly.clone()) 264 | .divide_with_q_and_r(&DenseOrSparsePolynomial::from(vp.dense_poly())) 265 | .unwrap(); 266 | let (actual_q, actual_r) = poly.divide_by_vp(vp); 267 | 268 | assert_eq!(actual_q, expected_q); 269 | assert_eq!(actual_r, expected_r); 270 | } 271 | } 272 | } 273 | --------------------------------------------------------------------------------