├── SECURITY.md ├── .gitignore ├── .github ├── workflows │ ├── linkify_changelog.yml │ └── ci.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── src ├── lib.rs ├── direct │ ├── constraints.rs │ └── mod.rs ├── fri │ ├── mod.rs │ ├── prover.rs │ ├── verifier.rs │ └── constraints │ │ └── mod.rs └── domain │ └── mod.rs ├── LICENSE-MIT ├── scripts └── linkify_changelog.py ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── CONTRIBUTING.md └── LICENSE-APACHE /SECURITY.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /.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 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | .idea -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | //! A crate for low-degree tests. 4 | #![deny( 5 | future_incompatible, 6 | missing_docs, 7 | non_shorthand_field_patterns, 8 | renamed_and_removed_lints, 9 | rust_2018_idioms, 10 | stable_features, 11 | trivial_casts, 12 | trivial_numeric_casts, 13 | unused, 14 | variant_size_differences, 15 | warnings 16 | )] 17 | #![forbid(unsafe_code)] 18 | 19 | /// Direct low-degree tests 20 | pub mod direct; 21 | 22 | /// Domain represented as coset. 23 | pub mod domain; 24 | /// Implementations for FRI Protocol 25 | pub mod fri; 26 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Sample changelog for maintainers 2 | 3 | ## Pending 4 | 5 | ### Breaking changes 6 | - #75 Move bigint from `algebra` to `algebra/utils`. To do this upgrade, do the following find-replace: 7 | - find `algebra/bigint`, replace with `algebra/utils/bigint` 8 | 9 | ### Features 10 | - #58 Add a defined API for every field type, and ensure all fields implement it (Thanks @alexander-zw) 11 | - #71 Add BLS12-381 (Thanks @yelhousni) 12 | 13 | ### Improvements 14 | - #xx Speedup sqrt by removing unnecessary exponentiation 15 | 16 | ### Bug fixes 17 | - #75 get rid of warning for unused constant PI, in complex field 18 | - #78 Reduce prints when inhibit_profiling_info is set. 19 | 20 | ## v1.1.0 21 | 22 | _Special thanks to all downstream projects upstreaming their patches!_ 23 | 24 | ### Breaking Changes 25 | - None! 26 | 27 | ### Features 28 | - #20 Improve operator+ speed for alt_bn, correct the corresponding docs, and reduce code duplication. 29 | - #50 Add mul_by_cofactor to elliptic curve groups 30 | - #50 Add sage scripts for altbn and mnt curves, to verify cofactors and generators 31 | - #52 Change default procps build flags to work with Mac OS 32 | 33 | ### Bug fixes 34 | - #19 Fix is_little_endian always returning true 35 | - #20 Fix operator+ not contributing to alt_bn_128 profiling opcount 36 | - #26 Remove unused warnings in release build 37 | - #39 Update Travis Config for newer Ubuntu mirror defaults 38 | - #50 Fix incorrect mnt4 g2 generator 39 | - #54 Fix is_power_of_two for n > 2^32 40 | - #55 Throw informative error for division by zero in div_ceil 41 | -------------------------------------------------------------------------------- /src/direct/constraints.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::PrimeField; 2 | use ark_r1cs_std::boolean::Boolean; 3 | use ark_r1cs_std::eq::EqGadget; 4 | use ark_r1cs_std::fields::fp::FpVar; 5 | use ark_r1cs_std::poly::polynomial::univariate::dense::DensePolynomialVar; 6 | use ark_relations::r1cs::SynthesisError; 7 | use ark_std::marker::PhantomData; 8 | 9 | /// Constraints for direct ldt. 10 | pub struct DirectLDTGadget { 11 | _marker: PhantomData, 12 | } 13 | 14 | impl DirectLDTGadget { 15 | /// ### Verifier Side 16 | /// 17 | /// The Direct LDT Verify function tests that given a list of coefficients `a_0, a_1, ..., a_{d-1}` 18 | /// an evaluation point `x`, and claimed evaluation `y`, that `y = \sum_{i =0}^{d} a_i x^i`. 19 | /// This proves that the provided coefficients of a degree `d` polynomial agree with the claimed 20 | /// `(evaluation_point, claimed_evaluation)` pair. 21 | /// This is used to construct a low degree test for an oracle to a claimed polynomials evaluations over a domain. 22 | /// By sampling enough (domain_element, claimed_evaluation) pairs from the oracle, and testing them 23 | /// via this method, you become convinced w.h.p. that the oracle is sufficiently close to the claimed coefficients list. 24 | pub fn verify( 25 | evaluation_point: FpVar, 26 | claimed_evaluation: FpVar, 27 | coefficients: &DensePolynomialVar, 28 | degree_bound: usize, 29 | ) -> Result, SynthesisError> { 30 | // make sure the degree is within degree_bound. No need to include degree_bound check 31 | // in constraints because the verifier can just verify the size of circuit. 32 | assert!( 33 | coefficients.coeffs.len() <= degree_bound + 1, 34 | "polynomial degree out of bound" 35 | ); 36 | coefficients 37 | .evaluate(&evaluation_point)? 38 | .is_eq(&claimed_evaluation) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/fri/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::Radix2CosetDomain; 2 | use ark_ff::PrimeField; 3 | use ark_std::marker::PhantomData; 4 | use ark_std::vec::Vec; 5 | /// R1CS constraints for FRI Verifier. 6 | #[cfg(feature = "r1cs")] 7 | pub mod constraints; 8 | /// Prover used by FRI protocol. 9 | pub mod prover; 10 | /// Verifier used by FRI protocol. 11 | pub mod verifier; 12 | 13 | /// Some parameters used by FRI verifiers. 14 | #[derive(Clone)] 15 | pub struct FRIParameters { 16 | /// The degree 17 | pub tested_degree: u64, 18 | /// At each round `i`, domain size will shrink to `last_round_domain_size` / `localization_parameters[i]`^2 19 | pub localization_parameters: Vec, 20 | /// Evaluation domain, which is represented as a coset. 21 | pub domain: Radix2CosetDomain, 22 | /// coset sizes in each round (first round is input coset) 23 | log_round_coset_sizes: Vec, 24 | } 25 | 26 | impl FRIParameters { 27 | /// Check parameter validity and returns new `FRIParameters`. 28 | pub fn new( 29 | tested_degree: u64, 30 | localization_parameters: Vec, 31 | domain: Radix2CosetDomain, 32 | ) -> Self { 33 | assert!( 34 | domain.size() >= tested_degree as usize + 1, 35 | "Evaluations is not low degree!\ 36 | Domain size needs to be >= tested_degree + 1" 37 | ); 38 | let mut log_round_coset_sizes = Vec::new(); 39 | log_round_coset_sizes.push(domain.dim()); 40 | for i in 0..localization_parameters.len() { 41 | log_round_coset_sizes 42 | .push(log_round_coset_sizes[i] - localization_parameters[i] as usize) 43 | } 44 | FRIParameters { 45 | tested_degree, 46 | localization_parameters, 47 | domain, 48 | log_round_coset_sizes, 49 | } 50 | } 51 | } 52 | 53 | /// Fast Reed-Solomon Interactive Oracle Proof of Proximity 54 | pub struct FRI { 55 | _protocol: PhantomData, 56 | } 57 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ark-ldt" 3 | version = "0.1.0" 4 | authors = ["arkworks contributors"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | ark-ff = { version = "^0.3.0", default-features = false } 11 | ark-std = { version = "^0.3.0", default-features = false } 12 | ark-r1cs-std = { version = "^0.3.0", default-features = false} 13 | ark-sponge = { version = "^0.3.0", default-features = false } 14 | ark-poly = { version = "0.3.0", default-features = false } 15 | ark-relations = { version = "^0.3.0", default-features = false, optional = true} 16 | tracing = { version = "0.1", default-features = false, features = [ "attributes" ], optional = true} 17 | 18 | [dev-dependencies] 19 | ark-test-curves = { version = "^0.3.0", default-features = false, features = ["bls12_381_scalar_field", "mnt4_753_scalar_field"] } 20 | 21 | [patch.crates-io] 22 | ark-sponge = {git = "https://github.com/arkworks-rs/sponge"} 23 | ark-r1cs-std = { git = "https://github.com/arkworks-rs/r1cs-std" } 24 | ark-ec = { git = "https://github.com/arkworks-rs/algebra" } 25 | ark-ff = { git = "https://github.com/arkworks-rs/algebra" } 26 | ark-poly = { git = "https://github.com/arkworks-rs/algebra" } 27 | ark-serialize = { git = "https://github.com/arkworks-rs/algebra" } 28 | ark-std = { git = "https://github.com/arkworks-rs/std" } 29 | ark-test-curves = { git = "https://github.com/arkworks-rs/algebra" } 30 | 31 | [profile.release] 32 | opt-level = 3 33 | lto = "thin" 34 | incremental = true 35 | panic = 'abort' 36 | 37 | [profile.bench] 38 | opt-level = 3 39 | debug = false 40 | rpath = false 41 | lto = "thin" 42 | incremental = true 43 | debug-assertions = false 44 | 45 | [profile.dev] 46 | opt-level = 0 47 | panic = 'abort' 48 | 49 | [profile.test] 50 | opt-level = 3 51 | lto = "thin" 52 | incremental = true 53 | debug-assertions = true 54 | debug = true 55 | 56 | [features] 57 | default = ["std"] 58 | std = ["ark-ff/std", "ark-std/std", "ark-relations/std", "ark-r1cs-std/std", "ark-sponge/std", "ark-poly/std"] 59 | r1cs = ["ark-sponge/r1cs", "tracing", "ark-relations"] 60 | 61 | -------------------------------------------------------------------------------- /.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@v1 18 | - name: Install Rust 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: stable 23 | override: true 24 | components: rustfmt 25 | 26 | - name: cargo fmt --check 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: fmt 30 | args: --all -- --check 31 | 32 | test: 33 | name: Test 34 | runs-on: ubuntu-latest 35 | env: 36 | RUSTFLAGS: -Dwarnings 37 | strategy: 38 | matrix: 39 | rust: 40 | - stable 41 | - nightly 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@v2 45 | 46 | - name: Install Rust (${{ matrix.rust }}) 47 | uses: actions-rs/toolchain@v1 48 | with: 49 | profile: minimal 50 | toolchain: ${{ matrix.rust }} 51 | override: true 52 | 53 | - name: Check examples 54 | uses: actions-rs/cargo@v1 55 | with: 56 | command: check 57 | args: --examples --all 58 | 59 | - name: Check examples with all features on stable 60 | uses: actions-rs/cargo@v1 61 | with: 62 | command: check 63 | args: --examples --all-features --all 64 | if: matrix.rust == 'stable' 65 | 66 | - name: Check benchmarks on nightly 67 | uses: actions-rs/cargo@v1 68 | with: 69 | command: check 70 | args: --all-features --examples --all --benches 71 | if: matrix.rust == 'nightly' 72 | 73 | - name: Test 74 | uses: actions-rs/cargo@v1 75 | with: 76 | command: test 77 | args: --release 78 | 79 | check_no_std: 80 | name: Check no_std 81 | runs-on: ubuntu-latest 82 | steps: 83 | - name: Checkout 84 | uses: actions/checkout@v2 85 | 86 | - name: Install Rust (${{ matrix.rust }}) 87 | uses: actions-rs/toolchain@v1 88 | with: 89 | toolchain: stable 90 | target: thumbv6m-none-eabi 91 | override: true 92 | 93 | - name: Install Rust ARM64 (${{ matrix.rust }}) 94 | uses: actions-rs/toolchain@v1 95 | with: 96 | toolchain: stable 97 | target: aarch64-unknown-none 98 | override: true 99 | 100 | - uses: actions/cache@v2 101 | with: 102 | path: | 103 | ~/.cargo/registry 104 | ~/.cargo/git 105 | target 106 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 107 | 108 | - name: ldt 109 | run: | 110 | cargo build --no-default-features --target aarch64-unknown-none 111 | cargo check --examples --no-default-features --target aarch64-unknown-none 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

arkworks::ldt

2 | 3 |

4 | 5 | 6 | 7 | 8 |

9 | 10 | `ark-ldt` is a Rust library that provides implementations of low-degree tests (LDTs). This library is released under the MIT License 11 | and the Apache v2 License (see [License](#license)). 12 | 13 | **WARNING:** This is an academic prototype, and in particular has not received careful code review. 14 | This implementation is NOT ready for production use. 15 | 16 | ## Overview 17 | 18 | A (univariate) low-degree test is an IOP that checks that a given function is close to a (univariate) polynomial of low degree. This library provides two LDTs: the **direct low-degree test** and the **FRI Protocol**. The library also comes with R1CS constraints for the LDT verifiers. Enable `r1cs` feature to use those constraints. 19 | 20 | ## Build Guide 21 | 22 | The library compiles on the `stable` toolchain of the Rust compiler. To install the latest version 23 | of Rust, first install `rustup` by following the instructions [here](https://rustup.rs/), or via 24 | your platform's package manager. Once `rustup` is installed, install the Rust toolchain by invoking: 25 | ```bash 26 | rustup install stable 27 | ``` 28 | 29 | After that, use `cargo` (the standard Rust build tool) to build the library: 30 | ```bash 31 | git clone https://github.com/arkworks-rs/ldt.git 32 | cd ldt 33 | cargo build --release 34 | ``` 35 | 36 | This library comes with some unit and integration tests. Run these tests with: 37 | ```bash 38 | cargo test 39 | ``` 40 | 41 | To use this library, you need to add the following to your `Cargo.toml`. Note that this configuration will bump `ark-sponge` and `ark-r1cs-std` to `master`/`main` instead of stable version on `crates.io`. 42 | ```toml 43 | [dependencies] 44 | ark-ldt = {git = "https://github.com/arkworks-rs/ldt", branch="main", default-features = false} 45 | 46 | [patch.crates-io] 47 | ark-sponge = {git = "https://github.com/arkworks-rs/sponge"} 48 | ark-r1cs-std = {git = "https://github.com/arkworks-rs/r1cs-std", branch = "master"} 49 | ``` 50 | 51 | ## License 52 | 53 | This library is licensed under either of the following licenses, at your discretion. 54 | 55 | * [Apache License Version 2.0](LICENSE-APACHE) 56 | * [MIT License](LICENSE-MIT) 57 | 58 | Unless you explicitly state otherwise, any contribution that you submit to this library shall be 59 | dual licensed as above (as defined in the Apache v2 License), without any additional terms or 60 | conditions. 61 | 62 | ## Reference papers 63 | 64 | [Fractal: Post-Quantum and Transparent Recursive Proofs from Holography][cos20]
65 | Alessandro Chiesa, Dev Ojha, Nicholas Spooner 66 | 67 | [Fast Reed-Solomon Interactive Oracle Proofs of Proximity][bbhr17]
68 | Eli Ben-Sasson, Iddo Bentov, Ynon Horesh, Michael Riabzev 69 | 70 | [cos20]: https://eprint.iacr.org/2019/1076 71 | [bbhr17]: https://eccc.weizmann.ac.il/report/2017/134/ 72 | -------------------------------------------------------------------------------- /src/direct/mod.rs: -------------------------------------------------------------------------------- 1 | /// R1CS constraints for DirectLDT 2 | #[cfg(feature = "r1cs")] 3 | pub mod constraints; 4 | 5 | use crate::domain::Radix2CosetDomain; 6 | use ark_ff::PrimeField; 7 | use ark_poly::univariate::DensePolynomial; 8 | use ark_poly::Polynomial; 9 | use ark_std::marker::PhantomData; 10 | use ark_std::vec::Vec; 11 | /// Direct LDT by interpolating evaluations and truncating coefficients to low degree. 12 | /// /// This requires communication linear in the degree bound; use FRI for better communication complexity. 13 | pub struct DirectLDT { 14 | marker: PhantomData, 15 | } 16 | 17 | /// A linear-communication protocol for testing if a function is a polynomial of certain degree. 18 | /// Method is described in Aurora appendix C.1. 19 | /// 20 | /// For now, the domain of the function needs to support IFFT. 21 | impl DirectLDT { 22 | /// ### Prover Side 23 | /// 24 | /// Generate the coefficient of the low-degree polynomial obtained by interpolating the domain evaluations. 25 | /// The polynomial is trimmed to `degree_bound` when necessary. 26 | pub fn generate_low_degree_coefficients( 27 | domain: Radix2CosetDomain, 28 | codewords: Vec, 29 | degree_bound: usize, 30 | ) -> DensePolynomial { 31 | let mut poly = domain.interpolate(codewords); 32 | // trim higher degree: if poly is higher degree, then the soundness should fail 33 | poly.coeffs.truncate(degree_bound + 1); 34 | poly 35 | } 36 | 37 | /// ### Verifier Side 38 | /// 39 | /// The Direct LDT Verify function tests that given a list of coefficients `a_0, a_1, ..., a_{d-1}` 40 | /// an evaluation point `x`, and claimed evaluation `y`, that `y = \sum_{i =0}^{d} a_i x^i`. 41 | /// This proves that the provided coefficients of a degree `d` polynomial agree with the claimed 42 | /// `(evaluation_point, claimed_evaluation)` pair. 43 | /// This is used to construct a low degree test for an oracle to a claimed polynomials evaluations over a domain. 44 | /// By sampling enough (domain_element, claimed_evaluation) pairs from the oracle, and testing them 45 | /// via this method, you become convinced w.h.p. that the oracle is sufficiently close to the claimed coefficients list. 46 | pub fn verify( 47 | evaluation_point: F, 48 | claimed_evaluation: F, 49 | bounded_coefficients: &DensePolynomial, 50 | ) -> bool { 51 | return bounded_coefficients.evaluate(&evaluation_point) == claimed_evaluation; 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use crate::direct::{DirectLDT, Radix2CosetDomain}; 58 | use ark_ff::UniformRand; 59 | use ark_poly::univariate::DensePolynomial; 60 | use ark_poly::DenseUVPolynomial; 61 | use ark_std::test_rng; 62 | use ark_test_curves::bls12_381::Fr; 63 | 64 | #[test] 65 | fn test_direct_ldt() { 66 | let degree = 51; 67 | 68 | let mut rng = test_rng(); 69 | let poly = DensePolynomial::::rand(degree, &mut rng); 70 | let domain_coset = Radix2CosetDomain::new_radix2_coset(52, Fr::rand(&mut rng)); 71 | let evaluations = domain_coset.evaluate(&poly); 72 | 73 | let low_degree_poly = DirectLDT::generate_low_degree_coefficients( 74 | domain_coset.clone(), 75 | evaluations.to_vec(), 76 | degree, 77 | ); 78 | 79 | let sampled_element = domain_coset.element(15); 80 | let sampled_evaluation = evaluations[15]; 81 | 82 | assert!(DirectLDT::verify( 83 | sampled_element, 84 | sampled_evaluation, 85 | &low_degree_poly 86 | )) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /src/fri/prover.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::Radix2CosetDomain; 2 | use ark_ff::{batch_inversion_and_mul, PrimeField}; 3 | use ark_r1cs_std::poly::evaluations::univariate::lagrange_interpolator::LagrangeInterpolator; 4 | use ark_std::marker::PhantomData; 5 | use ark_std::vec::Vec; 6 | /// FRI Prover 7 | pub struct FRIProver { 8 | _prover: PhantomData, 9 | } 10 | 11 | impl FRIProver { 12 | /// Single round prover in commit phase. Returns the evaluation oracles for next round. 13 | /// 14 | /// Returns domain for next round polynomial and evaluations over the domain. 15 | pub fn interactive_phase_single_round_naive( 16 | domain: Radix2CosetDomain, 17 | evaluation_oracles_over_domain: Vec, 18 | localization_param: u64, 19 | alpha: F, 20 | ) -> (Radix2CosetDomain, Vec) { 21 | let coset_size = 1 << localization_param; 22 | let domain_size = domain.base_domain.size; 23 | let dist_between_coset_elems = domain_size / coset_size; 24 | let mut new_evals = Vec::with_capacity(dist_between_coset_elems as usize); 25 | let coset_generator = domain 26 | .gen() 27 | .pow(&[1 << (domain.dim() as u64 - localization_param)]); 28 | let mut cur_coset_offset = domain.offset; 29 | 30 | for coset_index in 0..dist_between_coset_elems { 31 | let mut poly_evals = Vec::new(); 32 | for intra_coset_index in 0..coset_size { 33 | poly_evals.push( 34 | evaluation_oracles_over_domain 35 | [(coset_index + intra_coset_index * dist_between_coset_elems) as usize], 36 | ); 37 | } 38 | 39 | let interpolator = LagrangeInterpolator::new( 40 | cur_coset_offset, 41 | coset_generator, 42 | localization_param, 43 | poly_evals, 44 | ); 45 | new_evals.push(interpolator.interpolate(alpha)); 46 | cur_coset_offset *= domain.gen(); 47 | } 48 | 49 | let c = Radix2CosetDomain::new_radix2_coset(new_evals.len(), domain.offset); 50 | // c.base_domain.group_gen = coset_generator; 51 | // c.base_domain.group_gen_inv = coset_generator.inverse().unwrap(); 52 | debug_assert_eq!(coset_generator.pow(&[new_evals.len() as u64]), F::one()); 53 | debug_assert_eq!(c.size(), new_evals.len()); 54 | (c, new_evals) 55 | } 56 | 57 | /// Single round prover in commit phase. Returns the polynomial for next round 58 | /// represented by evaluations over domain in next round. 59 | /// 60 | /// Returns domain for next round polynomial and evaluations over the domain. 61 | pub fn interactive_phase_single_round( 62 | domain: Radix2CosetDomain, 63 | evals_over_domain: Vec, 64 | localization_param: u64, 65 | alpha: F, 66 | ) -> (Radix2CosetDomain, Vec) { 67 | let coset_size = 1 << localization_param; 68 | let num_cosets = domain.size() / coset_size; 69 | let mut next_f_i = Vec::with_capacity(num_cosets); // new_evals 70 | 71 | let h_inc = domain.gen(); 72 | let h_inc_to_coset_inv_plus_one = 73 | h_inc.pow(&[coset_size as u64]).inverse().unwrap() * h_inc; 74 | 75 | let shiftless_coset = Radix2CosetDomain::new_radix2_coset(coset_size, F::one()); 76 | let g = shiftless_coset.gen(); 77 | let g_inv = g.inverse().unwrap(); 78 | let x_to_order_coset = alpha.pow(&[coset_size as u64]); 79 | 80 | // x * g^{-k} 81 | let mut shifted_x_elements = Vec::with_capacity(coset_size); 82 | shifted_x_elements.push(alpha); 83 | for i in 1..coset_size { 84 | shifted_x_elements.push(shifted_x_elements[i - 1] * g_inv); 85 | } 86 | 87 | let mut cur_h = domain.offset; 88 | let first_h_to_coset_inv_plus_one = 89 | cur_h.pow(&[coset_size as u64]).inverse().unwrap() * cur_h; 90 | let mut cur_coset_constant_plus_h = x_to_order_coset * first_h_to_coset_inv_plus_one; 91 | 92 | /* x * g^{-k} - h, for all combinations of k, h. */ 93 | let mut elements_to_invert = Vec::with_capacity(evals_over_domain.len()); 94 | 95 | /* constant for each coset, equal to 96 | * vp_coset(x) / h^{|coset| - 1} = x^{|coset|} h^{-|coset| + 1} - h */ 97 | let mut constant_for_each_coset = Vec::with_capacity(num_cosets); 98 | 99 | let constant_for_all_cosets = F::from(coset_size as u128).inverse().unwrap(); 100 | let mut x_ever_in_domain = false; 101 | let mut x_coset_index = 0; 102 | let mut x_index_in_domain = 0; 103 | 104 | /* First we create all the constants for each coset, 105 | and the entire vector of elements to invert, xg^{-k} - h. 106 | */ 107 | 108 | for j in 0..num_cosets { 109 | /* coset constant = x^|coset| * h^{1 - |coset|} - h */ 110 | let coset_constant: F = cur_coset_constant_plus_h - cur_h; 111 | constant_for_each_coset.push(coset_constant); 112 | /* coset_constant = vp_coset(x) * h^{-|coset| + 1}, 113 | since h is non-zero, coset_constant is zero iff vp_coset(x) is zero. 114 | If vp_coset(x) is zero, then x is in the coset. */ 115 | let x_in_coset = coset_constant.is_zero(); 116 | /* if x is in the coset, we mark which position x is within f_i_domain, 117 | and we pad elements to invert to simplify inversion later. */ 118 | if x_in_coset { 119 | x_ever_in_domain = true; 120 | x_coset_index = j; 121 | // find which element in the coset x belongs to. 122 | // also pad elements_to_invert to simplify indexing 123 | let mut cur_elem = cur_h; 124 | for k in 0..coset_size { 125 | if cur_elem == alpha { 126 | x_index_in_domain = k * num_cosets + j; 127 | } 128 | cur_elem *= g; 129 | elements_to_invert.push(F::one()); 130 | } 131 | continue; 132 | } 133 | 134 | /* Append all elements to invert, (xg^{-k} - h) */ 135 | for k in 0..coset_size { 136 | elements_to_invert.push(shifted_x_elements[k] - cur_h); 137 | } 138 | 139 | cur_h *= h_inc; 140 | /* coset constant = x^|coset| * h^{1 - |coset|} - h 141 | So we can efficiently increment x^|coset| * h^{1 - |coset|} */ 142 | cur_coset_constant_plus_h *= h_inc_to_coset_inv_plus_one; 143 | } 144 | /* Technically not lagrange coefficients, its missing the constant for each coset */ 145 | batch_inversion_and_mul(&mut elements_to_invert, &constant_for_all_cosets); 146 | let lagrange_coefficients = elements_to_invert; 147 | for j in 0..num_cosets { 148 | let mut interpolation = F::zero(); 149 | for k in 0..coset_size { 150 | interpolation += evals_over_domain[k * num_cosets + j] 151 | * lagrange_coefficients[j * coset_size + k]; 152 | } 153 | /* Multiply the constant for each coset, to get the correct interpolation */ 154 | interpolation *= constant_for_each_coset[j]; 155 | next_f_i.push(interpolation); 156 | } 157 | 158 | /* if x ever in domain, correct that evaluation. */ 159 | if x_ever_in_domain { 160 | next_f_i[x_coset_index] = evals_over_domain[x_index_in_domain]; 161 | } 162 | 163 | // domain definition 164 | let c = domain.fold(localization_param); 165 | 166 | (c, next_f_i) 167 | } 168 | } 169 | 170 | #[cfg(test)] 171 | pub mod tests { 172 | use crate::direct::DirectLDT; 173 | use crate::domain::Radix2CosetDomain; 174 | use crate::fri::prover::FRIProver; 175 | use ark_poly::univariate::DensePolynomial; 176 | use ark_poly::DenseUVPolynomial; 177 | use ark_std::{test_rng, UniformRand}; 178 | use ark_test_curves::bls12_381::Fr; 179 | 180 | #[test] 181 | fn efficient_prover_consistency_test() { 182 | let degree = 32; 183 | 184 | let mut rng = test_rng(); 185 | let poly = DensePolynomial::::rand(degree, &mut rng); 186 | let domain_coset = Radix2CosetDomain::new_radix2_coset(64, Fr::rand(&mut rng)); 187 | let evaluations = domain_coset.evaluate(&poly); 188 | 189 | // fri prover should reduce its degree 190 | let alpha = Fr::rand(&mut rng); 191 | let localization = 2; 192 | let (expected_domain_next_round, expected_eval_next_round) = 193 | FRIProver::interactive_phase_single_round_naive( 194 | domain_coset, 195 | evaluations.to_vec(), 196 | localization, 197 | alpha, 198 | ); 199 | 200 | let (actual_domain_next_round, actual_eval_next_round) = 201 | FRIProver::interactive_phase_single_round( 202 | domain_coset, 203 | evaluations.to_vec(), 204 | localization, 205 | alpha, 206 | ); 207 | 208 | assert_eq!(actual_domain_next_round, expected_domain_next_round); 209 | assert_eq!(actual_eval_next_round, expected_eval_next_round); 210 | } 211 | 212 | #[test] 213 | fn degree_reduction_test() { 214 | let degree = 32; 215 | 216 | let mut rng = test_rng(); 217 | let poly = DensePolynomial::::rand(degree, &mut rng); 218 | let domain_coset = Radix2CosetDomain::new_radix2_coset(64, Fr::rand(&mut rng)); 219 | let evaluations = domain_coset.evaluate(&poly); 220 | 221 | // fri prover should reduce its degree 222 | let alpha = Fr::rand(&mut rng); 223 | let localization = 2; 224 | let (domain_next_round, eval_next_round) = FRIProver::interactive_phase_single_round( 225 | domain_coset.clone(), 226 | evaluations.to_vec(), 227 | localization, 228 | alpha, 229 | ); 230 | 231 | let low_degree_poly = DirectLDT::generate_low_degree_coefficients( 232 | domain_next_round.clone(), 233 | eval_next_round.to_vec(), 234 | degree / (1 << localization), 235 | ); 236 | 237 | let sampled_element = domain_next_round.element(15); 238 | let sampled_evaluation = eval_next_round[15]; 239 | 240 | assert!(DirectLDT::verify( 241 | sampled_element, 242 | sampled_evaluation, 243 | &low_degree_poly 244 | )); 245 | 246 | // test `fold_domain` 247 | let fold_domain = domain_coset.fold(localization); 248 | assert_eq!(fold_domain, domain_next_round); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/domain/mod.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::PrimeField; 2 | use ark_poly::polynomial::univariate::DensePolynomial; 3 | use ark_poly::{ 4 | DenseUVPolynomial, EvaluationDomain, Evaluations, Polynomial, Radix2EvaluationDomain, 5 | }; 6 | #[cfg(feature = "r1cs")] 7 | use ark_r1cs_std::bits::boolean::Boolean; 8 | #[cfg(feature = "r1cs")] 9 | use ark_r1cs_std::fields::fp::FpVar; 10 | #[cfg(feature = "r1cs")] 11 | use ark_r1cs_std::fields::FieldVar; 12 | #[cfg(feature = "r1cs")] 13 | use ark_relations::r1cs::SynthesisError; 14 | use ark_std::vec::Vec; 15 | 16 | /// Given domain as ``, `CosetOfDomain` represents `h` 17 | /// 18 | /// Constraint equivalent is in `r1cs_std::poly::domain`. 19 | #[derive(Clone, Copy, Eq, PartialEq, Debug)] 20 | pub struct Radix2CosetDomain { 21 | /// A non-coset radix 2 domain: `` 22 | pub base_domain: Radix2EvaluationDomain, 23 | /// offset `h` 24 | pub offset: F, 25 | } 26 | 27 | // TODO: Move this to algebra, per https://github.com/arkworks-rs/algebra/issues/88#issuecomment-734963835 28 | impl Radix2CosetDomain { 29 | /// Returns a new coset domain. 30 | pub fn new(base_domain: Radix2EvaluationDomain, offset: F) -> Self { 31 | Radix2CosetDomain { 32 | base_domain, 33 | offset, 34 | } 35 | } 36 | 37 | /// Returns a coset of size of power of two. 38 | pub fn new_radix2_coset(coset_size: usize, offset: F) -> Self { 39 | Self::new(Radix2EvaluationDomain::new(coset_size).unwrap(), offset) 40 | } 41 | 42 | /// Converts a query position to the elements of the unique coset of size `log_coset_size` 43 | /// within this domain that the query lies in. 44 | /// `query_position` is an index within this domain. 45 | /// Returns the positions of coset elements in `self`, 46 | /// and the coset represented as a Radix2CosetDomain. 47 | pub fn query_position_to_coset( 48 | &self, 49 | query_position: usize, 50 | log_coset_size: usize, 51 | ) -> (Vec, Self) { 52 | // make sure coset position is not out of range 53 | assert!( 54 | log_coset_size < self.base_domain.log_size_of_group as usize, 55 | "query coset size too large" 56 | ); 57 | assert!( 58 | query_position < (1 << (self.base_domain.log_size_of_group - log_coset_size as u32)), 59 | "coset position out of range" 60 | ); 61 | 62 | let dist_between_coset_elems = 63 | 1 << (self.base_domain.log_size_of_group as usize - log_coset_size); 64 | 65 | // generate coset 66 | let c = Self::new_radix2_coset( 67 | 1 << log_coset_size, 68 | self.offset * self.gen().pow(&[query_position as u64]), 69 | ); 70 | // c.base_domain.group_gen = self.gen().pow(&[1 << (self.dim() - log_coset_size)]); 71 | // c.base_domain.group_gen_inv = c.base_domain.group_gen.inverse().unwrap(); // not necessary 72 | 73 | // generate positions 74 | let mut indices = Vec::with_capacity(1 << log_coset_size); 75 | for i in 0..(1 << log_coset_size) { 76 | indices.push(query_position + i * dist_between_coset_elems) 77 | } 78 | 79 | (indices, c) 80 | } 81 | 82 | /// returns the size of the domain 83 | pub fn size(&self) -> usize { 84 | self.base_domain.size() 85 | } 86 | 87 | /// return the log 2 size of domain 88 | pub fn dim(&self) -> usize { 89 | self.base_domain.log_size_of_group as usize 90 | } 91 | 92 | /// returns generator of the coset 93 | pub fn gen(&self) -> F { 94 | self.base_domain.group_gen 95 | } 96 | 97 | /// Given f(x) = \sum a_i x^i. Returns g(x) = \sum a_i h^i x^i 98 | /// 99 | /// Note that g(x) = f(hx) 100 | fn add_offset_to_coeffs(&self, poly: &DensePolynomial) -> DensePolynomial { 101 | let mut r = F::one(); 102 | let mut transformed_coeff = Vec::with_capacity(poly.coeffs.len()); 103 | for &coeff in poly.coeffs.iter() { 104 | transformed_coeff.push(coeff * r); 105 | r *= self.offset 106 | } 107 | DensePolynomial::from_coefficients_vec(transformed_coeff) 108 | } 109 | 110 | /// Given g(x) = \sum a_i h^i x^i. Returns f(x) = \sum a_i x^i 111 | /// 112 | /// Note that g(x) = f(hx) 113 | fn remove_offset_from_coeffs(&self, poly: &DensePolynomial) -> DensePolynomial { 114 | let mut r = F::one(); 115 | let h_inv = self.offset.inverse().unwrap(); 116 | let mut transformed_coeff = Vec::with_capacity(poly.coeffs.len()); 117 | for &coeff in poly.coeffs.iter() { 118 | transformed_coeff.push(coeff * r); 119 | r *= h_inv 120 | } 121 | DensePolynomial::from_coefficients_vec(transformed_coeff) 122 | } 123 | 124 | /// Evaluate polynomial on this coset 125 | pub fn evaluate(&self, poly: &DensePolynomial) -> Vec { 126 | if self.size() < poly.degree() + 1 { 127 | // we use naive method for evaluating a polynomial larger than the domain size. 128 | // TODO: use a more efficient method using the fact that: 129 | // (hg)^{|base_domain|} = h^{|base_domain|}, 130 | // so we can efficiently fold the polynomial's coefficients on itself, 131 | // into a single polynomial of degree `self.size() - 1` 132 | return self 133 | .base_domain 134 | .elements() 135 | .map(|g| poly.evaluate(&(self.offset * g))) 136 | .collect(); 137 | } 138 | // g(x) = f(hx). So, f(coset) = g(base_domain) 139 | let gx = self.add_offset_to_coeffs(poly); 140 | gx.evaluate_over_domain(self.base_domain.clone()).evals 141 | } 142 | 143 | /// given evaluation over this coset. Interpolate and returns coefficients. 144 | pub fn interpolate(&self, evaluations: Vec) -> DensePolynomial { 145 | assert_eq!(evaluations.len(), self.base_domain.size()); 146 | // first get g(x) 147 | let gx = Evaluations::from_vec_and_domain(evaluations, self.base_domain).interpolate(); 148 | // g(x) = f(hx). Let g(x) = \sum a_i h^i x^i. Then f(x) = \sum a_i x^i 149 | let fx = self.remove_offset_from_coeffs(&gx); 150 | fx 151 | } 152 | 153 | /// Returns an element of the coset 154 | pub fn element(&self, i: usize) -> F { 155 | self.base_domain.element(i) * self.offset 156 | } 157 | 158 | #[cfg(feature = "r1cs")] 159 | /// Returns an element fo the coset, given the index as a variable. 160 | pub fn element_var(&self, index: &[Boolean]) -> Result, SynthesisError> { 161 | Ok(FpVar::constant(self.offset) * FpVar::constant(self.gen()).pow_le(index)?) 162 | } 163 | 164 | /// Shrink the domain size such that new domain size = `self.size() / (1 << log_shrink_factor)` 165 | /// and has same offset. 166 | pub fn fold(&self, log_shrink_factor: u64) -> Radix2CosetDomain { 167 | let coset_size = 1 << log_shrink_factor; 168 | let domain_size = self.base_domain.size; 169 | let dist_between_coset_elems = domain_size / coset_size; 170 | Radix2CosetDomain::new_radix2_coset(dist_between_coset_elems as usize, self.offset) 171 | } 172 | } 173 | 174 | #[cfg(test)] 175 | mod tests { 176 | use ark_poly::univariate::DensePolynomial; 177 | use ark_poly::{DenseUVPolynomial, Polynomial}; 178 | use ark_std::{test_rng, UniformRand}; 179 | use ark_test_curves::bls12_381::Fr; 180 | 181 | use crate::domain::Radix2CosetDomain; 182 | 183 | #[cfg(feature = "r1cs")] 184 | mod consistency_with_constraints { 185 | use ark_poly::univariate::DensePolynomial; 186 | use ark_poly::Radix2EvaluationDomain; 187 | use ark_poly::{DenseUVPolynomial, EvaluationDomain, Polynomial}; 188 | use ark_r1cs_std::alloc::AllocVar; 189 | use ark_r1cs_std::fields::fp::FpVar; 190 | use ark_r1cs_std::fields::FieldVar; 191 | use ark_r1cs_std::poly::domain::Radix2DomainVar; 192 | use ark_r1cs_std::poly::evaluations::univariate::EvaluationsVar; 193 | use ark_r1cs_std::R1CSVar; 194 | use ark_relations::r1cs::ConstraintSystem; 195 | use ark_std::{test_rng, UniformRand}; 196 | use ark_test_curves::bls12_381::Fr; 197 | 198 | use crate::domain::Radix2CosetDomain; 199 | 200 | #[test] 201 | fn test_consistency_with_coset_constraints() { 202 | let mut rng = test_rng(); 203 | let degree = 51; 204 | let poly = DensePolynomial::::rand(degree, &mut rng); 205 | let base_domain = Radix2EvaluationDomain::new(degree + 1).unwrap(); 206 | let offset = Fr::rand(&mut rng); 207 | let coset = Radix2CosetDomain::new(base_domain, offset); 208 | 209 | // test evaluation 210 | let expected_eval: Vec<_> = coset 211 | .base_domain 212 | .elements() 213 | .map(|x| poly.evaluate(&(offset * x))) 214 | .collect(); 215 | let actual_eval = coset.evaluate(&poly); 216 | assert_eq!(actual_eval, expected_eval); 217 | 218 | // test interpolation 219 | let interpolated_poly = coset.interpolate(expected_eval.to_vec()); 220 | assert_eq!(interpolated_poly, poly); 221 | 222 | // test consistency with r1cs-std 223 | let cs = ConstraintSystem::new_ref(); 224 | let eval_var: Vec<_> = expected_eval 225 | .iter() 226 | .map(|x| FpVar::new_witness(ark_relations::ns!(cs, "eval_var"), || Ok(*x)).unwrap()) 227 | .collect(); 228 | 229 | let r1cs_coset = Radix2DomainVar::new( 230 | base_domain.group_gen, 231 | ark_std::log2(degree.next_power_of_two()) as u64, 232 | FpVar::constant(offset), 233 | ) 234 | .unwrap(); 235 | let eval_var = EvaluationsVar::from_vec_and_domain(eval_var, r1cs_coset, true); 236 | 237 | let pt = Fr::rand(&mut rng); 238 | let pt_var = 239 | FpVar::new_witness(ark_relations::ns!(cs, "random point"), || Ok(pt)).unwrap(); 240 | 241 | let expected = poly.evaluate(&pt); 242 | let actual = eval_var.interpolate_and_evaluate(&pt_var).unwrap(); 243 | 244 | assert_eq!(actual.value().unwrap(), expected); 245 | assert!(cs.is_satisfied().unwrap()); 246 | } 247 | } 248 | 249 | #[test] 250 | fn query_coset_test() { 251 | let mut rng = test_rng(); 252 | let poly = DensePolynomial::rand(4, &mut rng); 253 | 254 | let offset = Fr::rand(&mut rng); 255 | let domain_coset = Radix2CosetDomain::new_radix2_coset(15, offset); 256 | 257 | let evals_on_domain_coset = domain_coset.evaluate(&poly); 258 | assert_eq!( 259 | poly.evaluate(&domain_coset.element(2)), 260 | evals_on_domain_coset[2] 261 | ); 262 | 263 | let (query_coset_pos, query_coset) = domain_coset.query_position_to_coset(2, 2); 264 | 265 | assert_eq!(query_coset_pos, vec![2, 6, 10, 14]); 266 | 267 | assert_eq!(query_coset.element(0), domain_coset.element(2)); 268 | assert_eq!(query_coset.element(1), domain_coset.element(6)); 269 | assert_eq!(query_coset.element(2), domain_coset.element(10)); 270 | assert_eq!(query_coset.element(3), domain_coset.element(14)); 271 | 272 | assert_eq!( 273 | query_coset.evaluate(&poly), 274 | vec![ 275 | evals_on_domain_coset[2], 276 | evals_on_domain_coset[6], 277 | evals_on_domain_coset[10], 278 | evals_on_domain_coset[14] 279 | ] 280 | ) 281 | } 282 | 283 | #[test] 284 | #[cfg(feature = "r1cs")] 285 | fn element_var_test() { 286 | use ark_r1cs_std::alloc::AllocVar; 287 | use ark_r1cs_std::uint64::UInt64; 288 | use ark_r1cs_std::R1CSVar; 289 | use ark_relations::r1cs::ConstraintSystem; 290 | use ark_relations::*; 291 | 292 | let mut rng = test_rng(); 293 | let offset = Fr::rand(&mut rng); 294 | let domain_coset = Radix2CosetDomain::new_radix2_coset(15, offset); 295 | 296 | let cs = ConstraintSystem::new_ref(); 297 | let index = 11; 298 | let index_var = UInt64::new_witness(ns!(cs, "index"), || Ok(index)) 299 | .unwrap() 300 | .to_bits_le(); 301 | 302 | let expected = domain_coset.element(index as usize); 303 | let actual = domain_coset 304 | .element_var(&index_var) 305 | .unwrap() 306 | .value() 307 | .unwrap(); 308 | 309 | assert_eq!(expected, actual); 310 | assert!(cs.is_satisfied().unwrap()) 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /src/fri/verifier.rs: -------------------------------------------------------------------------------- 1 | use ark_std::marker::PhantomData; 2 | 3 | use crate::direct::DirectLDT; 4 | use crate::domain::Radix2CosetDomain; 5 | use crate::fri::FRIParameters; 6 | use ark_ff::PrimeField; 7 | use ark_poly::polynomial::univariate::DensePolynomial; 8 | use ark_poly::Polynomial; 9 | use ark_sponge::FieldBasedCryptographicSponge; 10 | use ark_std::vec::Vec; 11 | 12 | /// Implements FRI verifier. 13 | pub struct FRIVerifier { 14 | _verifier: PhantomData, 15 | } 16 | 17 | impl FRIVerifier { 18 | /// ## Step 1: Interative Phase 19 | /// Sample alpha in interactive phase. 20 | pub fn interactive_phase_single_round>( 21 | sponge: &mut S, 22 | ) -> F { 23 | sponge.squeeze_native_field_elements(1)[0] 24 | } 25 | 26 | /// ## Step 2: Sample Queried Coset 27 | /// Sample the coset to be queried. 28 | pub fn sample_coset_index>( 29 | sponge: &mut S, 30 | fri_parameters: &FRIParameters, 31 | ) -> usize { 32 | let log_num_cosets = 33 | fri_parameters.domain.dim() - fri_parameters.localization_parameters[0] as usize; 34 | // we use the fact that number of cosets is always power of two 35 | let rand_coset_index = le_bits_array_to_usize(&sponge.squeeze_bits(log_num_cosets)); 36 | rand_coset_index 37 | } 38 | 39 | /// ## Step 2: Query Phase (Prepare Query) 40 | /// Prepare one query given the random coset index. The returned value `queries[i]` is the coset query 41 | /// of the `ith` round polynomial (including codeword but does not include final polynomial). 42 | /// Final polynomial is not queried. Instead, verifier will get 43 | /// the whole final polynomial in evaluation form, and do direct LDT. 44 | /// 45 | /// Returns the all query domains, and query coset index, final polynomial domain 46 | pub fn prepare_query( 47 | rand_coset_index: usize, 48 | fri_parameters: &FRIParameters, 49 | ) -> (Vec>, Vec, Radix2CosetDomain) { 50 | let num_fri_rounds = fri_parameters.localization_parameters.len(); 51 | let mut coset_indices = Vec::new(); 52 | let mut curr_coset_index = rand_coset_index; 53 | let mut queries = Vec::with_capacity(num_fri_rounds); 54 | let mut curr_round_domain = fri_parameters.domain; 55 | // sample a coset index 56 | for i in 0..num_fri_rounds { 57 | // current coset index = last coset index % (distance between coset at current round) 58 | // edge case: at first round, this still applies 59 | 60 | let dist_between_coset_elems = 61 | curr_round_domain.size() / (1 << fri_parameters.localization_parameters[i]); 62 | curr_coset_index = curr_coset_index % dist_between_coset_elems; 63 | 64 | coset_indices.push(curr_coset_index); 65 | 66 | let (_, query_coset) = curr_round_domain.query_position_to_coset( 67 | curr_coset_index, 68 | fri_parameters.localization_parameters[i] as usize, 69 | ); 70 | 71 | queries.push(query_coset); 72 | 73 | // get next round coset size, and next round domain 74 | curr_round_domain = curr_round_domain.fold(fri_parameters.localization_parameters[i]); 75 | } 76 | 77 | (queries, coset_indices, curr_round_domain) 78 | } 79 | 80 | /// ## Step 2: Query Phase (Prepare Query) 81 | /// Prepare all queries given the sampled random coset indices. 82 | /// 83 | /// The first returned value `queries[i][j]` is the coset query 84 | /// of the `j`th round polynomial (including codeword but does not include final polynomial) for `i`th query. 85 | /// 86 | /// The second returned value `indices[i][j]` is the coset index 87 | /// of the `j`th round polynomial (including codeword but does not include final polynomial) for `i`th query. 88 | /// 89 | /// The last returned value `final[i]` is the final polynomial domain at round `i`. 90 | pub fn batch_prepare_queries( 91 | rand_coset_indices: &[usize], 92 | fri_parameters: &FRIParameters, 93 | ) -> ( 94 | Vec>>, 95 | Vec>, 96 | Vec>, 97 | ) { 98 | let mut queries = Vec::with_capacity(rand_coset_indices.len()); 99 | let mut indices = Vec::with_capacity(rand_coset_indices.len()); 100 | let mut finals = Vec::with_capacity(rand_coset_indices.len()); 101 | 102 | rand_coset_indices 103 | .iter() 104 | .map(|&i| Self::prepare_query(i, fri_parameters)) 105 | .for_each(|(query, index, fp)| { 106 | queries.push(query); 107 | indices.push(index); 108 | finals.push(fp); 109 | }); 110 | 111 | (queries, indices, finals) 112 | } 113 | 114 | /// ## Step 3: Decision Phase (Check query) 115 | /// After preparing the query, verifier get the evaluations of corresponding query. Those evaluations needs 116 | /// to be checked by merkle tree. Then verifier calls this method to check if polynomial sent in each round 117 | /// is consistent with each other, and the final polynomial is low-degree. 118 | /// 119 | /// `queries[i]` is the coset query of the `ith` round polynomial, including the codeword polynomial. 120 | /// `queried_evaluations` stores the result of corresponding query. 121 | pub fn consistency_check( 122 | fri_parameters: &FRIParameters, 123 | queried_coset_indices: &[usize], 124 | queries: &[Radix2CosetDomain], 125 | queried_evaluations: &[Vec], 126 | alphas: &[F], 127 | final_polynomial_domain: &Radix2CosetDomain, 128 | final_polynomial: &DensePolynomial, 129 | ) -> bool { 130 | let mut expected_next_round_eval = F::zero(); 131 | 132 | debug_assert_eq!(fri_parameters.localization_parameters.len(), queries.len()); 133 | for i in 0..queries.len() { 134 | expected_next_round_eval = FRIVerifier::expected_evaluation( 135 | &queries[i], 136 | queried_evaluations[i].clone(), 137 | alphas[i], 138 | ); 139 | 140 | // check if current round result is consistent with next round polynomial (if next round is not final) 141 | if i < queries.len() - 1 { 142 | let next_localization_param = 143 | fri_parameters.localization_parameters[i + 1] as usize; 144 | let log_next_dist_between_coset_elems = 145 | fri_parameters.log_round_coset_sizes[i + 1] - next_localization_param; 146 | let next_intra_coset_index = 147 | queried_coset_indices[i] >> log_next_dist_between_coset_elems; 148 | 149 | let actual = queried_evaluations[i + 1][next_intra_coset_index]; 150 | if expected_next_round_eval != actual { 151 | return false; 152 | } 153 | } 154 | } 155 | 156 | // check final polynomial (low degree & consistency check) 157 | // We assume degree_bound is power of 2. 158 | assert!(fri_parameters.tested_degree.is_power_of_two()); 159 | let total_shrink_factor: u64 = fri_parameters.localization_parameters.iter().sum(); 160 | let final_poly_degree_bound = fri_parameters.tested_degree >> total_shrink_factor; 161 | 162 | let final_element_index = *queried_coset_indices.last().unwrap(); 163 | 164 | assert!( 165 | final_polynomial.degree() <= final_poly_degree_bound as usize, 166 | "final polynomial degree is too large!" 167 | ); 168 | DirectLDT::verify( 169 | final_polynomial_domain.element(final_element_index), 170 | expected_next_round_eval, 171 | &final_polynomial, 172 | ) 173 | } 174 | 175 | /// ## Step 3: Decision Phase (Check query) 176 | /// After preparing all queries, verifier gets the evaluations of corresponding query. Those evaluations needs 177 | /// to be checked by merkle tree. Then verifier calls this method to check if polynomial sent in each round 178 | /// is consistent with each other, and the final polynomial is low-degree. 179 | /// 180 | /// * `all_queried_coset_indices[i][j]` is the `j`th round query coset index of `i`th query 181 | /// * `all_queries_domains[i][j]` is the `j`th round query coset of `i`th query 182 | /// * `all_queried_evaluations[i][j]` is a vector storing corresponding evaluations at `all_queries_domains[i][j]` 183 | /// * `alphas[i]` is the randomness used by the polynomial 184 | /// * `all_final_polynomial_domain[i]` is the final polynomial domain for `i`th query 185 | /// * `all_final_polynomials` is the final polynomial for `i`th query 186 | pub fn batch_consistency_check( 187 | fri_parameters: &FRIParameters, 188 | all_queried_coset_indices: &[Vec], 189 | all_queries_domains: &[Vec>], 190 | all_queried_evaluations: &[Vec>], 191 | alphas: &[F], 192 | all_final_polynomial_domain: &[Radix2CosetDomain], 193 | all_final_polynomials: &[DensePolynomial], 194 | ) -> bool { 195 | for i in 0..all_queried_coset_indices.len() { 196 | let result = Self::consistency_check( 197 | fri_parameters, 198 | &all_queried_coset_indices[i], 199 | &all_queries_domains[i], 200 | &all_queried_evaluations[i], 201 | alphas, 202 | &all_final_polynomial_domain[i], 203 | &all_final_polynomials[i], 204 | ); 205 | if !result { 206 | return false; 207 | } 208 | } 209 | true 210 | } 211 | 212 | /// Map coset in current round to a single point in next round. 213 | /// 214 | /// Essentially, this function interpolates the polynomial and evaluate on `alpha`. 215 | #[inline] 216 | fn expected_evaluation( 217 | coset: &Radix2CosetDomain, 218 | queried_evaluations: Vec, 219 | alpha: F, 220 | ) -> F { 221 | let poly = coset.interpolate(queried_evaluations); 222 | poly.evaluate(&alpha) 223 | } 224 | } 225 | 226 | fn le_bits_array_to_usize(bits: &[bool]) -> usize { 227 | let mut result = 0; 228 | for &bit in bits { 229 | result += bit as usize; 230 | result *= 2; 231 | } 232 | result 233 | } 234 | 235 | #[cfg(test)] 236 | mod tests { 237 | use ark_ff::UniformRand; 238 | use ark_poly::univariate::DensePolynomial; 239 | use ark_poly::{DenseUVPolynomial, Polynomial}; 240 | use ark_std::test_rng; 241 | use ark_test_curves::bls12_381::Fr; 242 | 243 | use crate::direct::DirectLDT; 244 | use crate::domain::Radix2CosetDomain; 245 | use crate::fri::prover::FRIProver; 246 | use crate::fri::verifier::FRIVerifier; 247 | use crate::fri::FRIParameters; 248 | 249 | #[test] 250 | fn two_rounds_fri_test() { 251 | // First, generate a low degree polynomial, and its evaluations. 252 | let mut rng = test_rng(); 253 | let poly = DensePolynomial::rand(32, &mut rng); 254 | let offset = Fr::rand(&mut rng); 255 | let domain_input = Radix2CosetDomain::new_radix2_coset(128, offset); 256 | let evaluations_input = domain_input.evaluate(&poly); 257 | 258 | // Set up verifier parameter 259 | let fri_parameters = FRIParameters::new(32, vec![1, 2, 1], domain_input); 260 | let alphas: Vec<_> = (0..3).map(|_| Fr::rand(&mut rng)).collect(); 261 | 262 | // prover commits all round polynomial 263 | let (domain_round_0, evaluations_round_0) = FRIProver::interactive_phase_single_round( 264 | domain_input, 265 | evaluations_input.clone(), 266 | fri_parameters.localization_parameters[0], 267 | alphas[0], 268 | ); 269 | 270 | let (domain_round_1, evaluations_round_1) = FRIProver::interactive_phase_single_round( 271 | domain_round_0, 272 | evaluations_round_0.clone(), 273 | fri_parameters.localization_parameters[1], 274 | alphas[1], 275 | ); 276 | 277 | let (expected_domain_final, evaluations_final) = FRIProver::interactive_phase_single_round( 278 | domain_round_1, 279 | evaluations_round_1.clone(), 280 | fri_parameters.localization_parameters[2], 281 | alphas[2], 282 | ); 283 | 284 | // verifier prepare queries 285 | let rand_coset_index = 31; 286 | let (query_cosets, query_indices, domain_final) = 287 | FRIVerifier::prepare_query(rand_coset_index, &fri_parameters); 288 | assert_eq!(query_indices.len(), 3); 289 | assert_eq!(domain_final, expected_domain_final); 290 | 291 | // prover generate answers to queries 292 | let (indices, qi) = domain_input.query_position_to_coset( 293 | query_indices[0], 294 | fri_parameters.localization_parameters[0] as usize, 295 | ); 296 | let answer_input: Vec<_> = indices.iter().map(|&i| evaluations_input[i]).collect(); 297 | assert_eq!(qi, query_cosets[0]); 298 | 299 | let (indices, q0) = domain_round_0.query_position_to_coset( 300 | query_indices[1], 301 | fri_parameters.localization_parameters[1] as usize, 302 | ); 303 | let answer_round_0: Vec<_> = indices.iter().map(|&i| evaluations_round_0[i]).collect(); 304 | assert_eq!(q0, query_cosets[1]); 305 | 306 | let (indices, q1) = domain_round_1.query_position_to_coset( 307 | query_indices[2], 308 | fri_parameters.localization_parameters[2] as usize, 309 | ); 310 | let answer_round_1: Vec<_> = indices.iter().map(|&i| evaluations_round_1[i]).collect(); 311 | assert_eq!(q1, query_cosets[2]); 312 | 313 | // sanity check: answer_round_i interpolate version contained in answer_round_i+1 314 | assert!(answer_round_0.contains(&qi.interpolate(answer_input.clone()).evaluate(&alphas[0]))); 315 | assert!( 316 | answer_round_1.contains(&q0.interpolate(answer_round_0.clone()).evaluate(&alphas[1])) 317 | ); 318 | assert!(evaluations_final 319 | .contains(&q1.interpolate(answer_round_1.clone()).evaluate(&alphas[2]))); 320 | 321 | let total_shrink_factor: u64 = fri_parameters.localization_parameters.iter().sum(); 322 | let final_poly_degree_bound = fri_parameters.tested_degree >> total_shrink_factor; 323 | let final_polynomial = DirectLDT::generate_low_degree_coefficients( 324 | domain_final, 325 | evaluations_final, 326 | final_poly_degree_bound as usize, 327 | ); 328 | 329 | // verifier verifies consistency 330 | let result = FRIVerifier::consistency_check( 331 | &fri_parameters, 332 | &query_indices, 333 | &query_cosets, 334 | &vec![answer_input, answer_round_0, answer_round_1], 335 | &alphas, 336 | &domain_final, 337 | &final_polynomial, 338 | ); 339 | 340 | assert!(result) 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /src/fri/constraints/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] // temporary 2 | use crate::direct::constraints::DirectLDTGadget; 3 | use crate::domain::Radix2CosetDomain; 4 | use crate::fri::FRIParameters; 5 | use ark_ff::PrimeField; 6 | use ark_r1cs_std::bits::boolean::Boolean; 7 | use ark_r1cs_std::eq::EqGadget; 8 | use ark_r1cs_std::fields::fp::FpVar; 9 | use ark_r1cs_std::fields::FieldVar; 10 | use ark_r1cs_std::poly::domain::Radix2DomainVar; 11 | use ark_r1cs_std::poly::evaluations::univariate::EvaluationsVar; 12 | use ark_r1cs_std::poly::polynomial::univariate::dense::DensePolynomialVar; 13 | use ark_r1cs_std::prelude::CondSelectGadget; 14 | use ark_relations::r1cs::SynthesisError; 15 | use ark_sponge::constraints::CryptographicSpongeVar; 16 | use ark_sponge::FieldBasedCryptographicSponge; 17 | use ark_std::marker::PhantomData; 18 | use ark_std::vec::Vec; 19 | 20 | /// Constraints for FRI verifier. 21 | pub struct FRIVerifierGadget { 22 | _marker: PhantomData, 23 | } 24 | 25 | impl FRIVerifierGadget { 26 | /// ## Step 1: Interative Phase 27 | /// Sample alpha in interactive phase. 28 | pub fn interactive_phase_single_round< 29 | S: FieldBasedCryptographicSponge, 30 | SV: CryptographicSpongeVar, 31 | >( 32 | sponge_var: &mut SV, 33 | ) -> Result, SynthesisError> { 34 | Ok(sponge_var 35 | .squeeze_field_elements(1)? 36 | .first() 37 | .unwrap() 38 | .clone()) 39 | } 40 | 41 | /// ## Step 2: Sample Queried Coset 42 | /// Sample the coset to be queried. 43 | pub fn sample_coset_index< 44 | S: FieldBasedCryptographicSponge, 45 | SV: CryptographicSpongeVar, 46 | >( 47 | sponge_var: &mut SV, 48 | fri_parameters: &FRIParameters, 49 | ) -> Result>, SynthesisError> { 50 | let log_num_cosets = 51 | fri_parameters.domain.dim() - fri_parameters.localization_parameters[0] as usize; 52 | sponge_var.squeeze_bits(log_num_cosets) 53 | } 54 | 55 | /// ## Step 2: Query Phase (Prepare Query) 56 | /// Prepare one query given the random coset index. The returned value `queries[i]` is the coset query 57 | /// of the `ith` round polynomial (including codeword but does not include final polynomial). 58 | /// Final polynomial is not queried. Instead, verifier will get 59 | /// the whole final polynomial in evaluation form, and do direct LDT. 60 | /// 61 | /// Returns the all query domains, and query coset index, final polynomial domain 62 | pub fn prepare_query( 63 | rand_coset_index: Vec>, 64 | fri_parameters: &FRIParameters, 65 | ) -> Result< 66 | ( 67 | Vec>, 68 | Vec>>, 69 | Radix2CosetDomain, 70 | ), 71 | SynthesisError, 72 | > { 73 | let num_fri_rounds = fri_parameters.localization_parameters.len(); 74 | let mut coset_indices = Vec::new(); 75 | let mut curr_coset_index = rand_coset_index; 76 | let mut queries = Vec::with_capacity(num_fri_rounds); 77 | let mut curr_round_domain = fri_parameters.domain; 78 | 79 | // sample coset index 80 | for i in 0..num_fri_rounds { 81 | let log_dist_between_coset_elems = 82 | curr_round_domain.dim() - fri_parameters.localization_parameters[i] as usize; 83 | curr_coset_index = curr_coset_index[..log_dist_between_coset_elems].to_vec(); 84 | 85 | coset_indices.push(curr_coset_index.clone()); 86 | 87 | // get the query coset from coset index 88 | let query_gen = fri_parameters.domain.gen().pow(&[1 89 | << (fri_parameters.domain.dim() 90 | - fri_parameters.localization_parameters[i] as usize)]); 91 | debug_assert_eq!( 92 | query_gen.pow(&[1 << fri_parameters.localization_parameters[i]]), 93 | F::one() 94 | ); 95 | 96 | let query_offset = &FpVar::constant(curr_round_domain.offset) 97 | * &(FpVar::constant(curr_round_domain.gen()).pow_le(&curr_coset_index)?); 98 | 99 | let query_coset = Radix2DomainVar::new( 100 | query_gen, 101 | fri_parameters.localization_parameters[i], 102 | query_offset, 103 | )?; 104 | 105 | queries.push(query_coset); 106 | 107 | curr_round_domain = curr_round_domain.fold(fri_parameters.localization_parameters[i]) 108 | } 109 | 110 | Ok((queries, coset_indices, curr_round_domain)) 111 | } 112 | 113 | /// Map coset in current round to a single point in next round. 114 | /// 115 | /// Essentially, this function interpolates the polynomial and evaluate on `alpha`. 116 | fn expected_evaluation( 117 | coset: &Radix2DomainVar, 118 | queried_evaluations: Vec>, 119 | alpha: FpVar, 120 | ) -> Result, SynthesisError> { 121 | let evaluations = 122 | EvaluationsVar::from_vec_and_domain(queried_evaluations, coset.clone(), true); 123 | evaluations.interpolate_and_evaluate(&alpha) 124 | } 125 | 126 | /// ## Step 3: Decision Phase (Check query) 127 | /// After preparing the query, verifier get the evaluations of corresponding query. Those evaluations needs 128 | /// to be checked by merkle tree. Then verifier calls this method to check if polynomial sent in each round 129 | /// is consistent with each other, and the final polynomial is low-degree. 130 | /// 131 | /// `queries[i]` is the coset query of the `ith` round polynomial, including the codeword polynomial. 132 | /// `queried_evaluations` stores the result of corresponding query. 133 | pub fn consistency_check( 134 | fri_parameters: &FRIParameters, 135 | queried_coset_indices: &[Vec>], 136 | queries: &[Radix2DomainVar], 137 | queried_evaluations: &[Vec>], 138 | alphas: &[FpVar], 139 | final_polynomial_domain: &Radix2CosetDomain, 140 | final_polynomial: &DensePolynomialVar, 141 | ) -> Result, SynthesisError> { 142 | let mut expected_next_round_eval = FpVar::zero(); 143 | 144 | debug_assert_eq!(fri_parameters.localization_parameters.len(), queries.len()); 145 | let mut check_result = Boolean::constant(true); 146 | for i in 0..queries.len() { 147 | expected_next_round_eval = FRIVerifierGadget::expected_evaluation( 148 | &queries[i], 149 | queried_evaluations[i].clone(), 150 | alphas[i].clone(), 151 | )?; 152 | 153 | // check if current round result is consistent with next round polynomial (if next round is not final) 154 | if i < queries.len() - 1 { 155 | let next_localization_param = 156 | fri_parameters.localization_parameters[i + 1] as usize; 157 | let log_next_dist_between_coset_elems = 158 | fri_parameters.log_round_coset_sizes[i + 1] - next_localization_param; 159 | // native code: queried_coset_indices[i] >> log_next_dist_between_coset_elems 160 | let next_intra_coset_index = 161 | &queried_coset_indices[i][log_next_dist_between_coset_elems..]; 162 | 163 | let actual = FpVar::::conditionally_select_power_of_two_vector( 164 | next_intra_coset_index, 165 | &queried_evaluations[i + 1], 166 | )?; 167 | 168 | check_result = check_result.and(&expected_next_round_eval.is_eq(&actual)?)?; 169 | } 170 | } 171 | 172 | // check final polynomial (low degree & consistency check) 173 | // We assume degree_bound is power of 2. 174 | assert!(fri_parameters.tested_degree.is_power_of_two()); 175 | let total_shrink_factor: u64 = fri_parameters.localization_parameters.iter().sum(); 176 | let final_poly_degree_bound = fri_parameters.tested_degree >> total_shrink_factor; 177 | 178 | let final_element_index = queried_coset_indices.last().unwrap(); 179 | 180 | DirectLDTGadget::verify( 181 | final_polynomial_domain.element_var(final_element_index)?, 182 | expected_next_round_eval, 183 | final_polynomial, 184 | final_poly_degree_bound as usize, 185 | ) 186 | } 187 | } 188 | 189 | #[cfg(test)] 190 | mod tests { 191 | use crate::direct::DirectLDT; 192 | use crate::domain::Radix2CosetDomain; 193 | use crate::fri::constraints::FRIVerifierGadget; 194 | use crate::fri::prover::FRIProver; 195 | use crate::fri::verifier::FRIVerifier; 196 | use crate::fri::FRIParameters; 197 | use ark_poly::polynomial::univariate::DensePolynomial; 198 | use ark_poly::DenseUVPolynomial; 199 | use ark_r1cs_std::alloc::AllocVar; 200 | use ark_r1cs_std::bits::uint64::UInt64; 201 | use ark_r1cs_std::fields::fp::FpVar; 202 | use ark_r1cs_std::poly::polynomial::univariate::dense::DensePolynomialVar; 203 | use ark_r1cs_std::R1CSVar; 204 | use ark_relations::r1cs::ConstraintSystem; 205 | use ark_relations::*; 206 | use ark_std::{test_rng, UniformRand}; 207 | use ark_test_curves::bls12_381::Fr; 208 | 209 | #[test] 210 | fn test_prepare_query() { 211 | let mut rng = test_rng(); 212 | let offset = Fr::rand(&mut rng); 213 | let domain_input = Radix2CosetDomain::new_radix2_coset(1 << 7, offset); 214 | 215 | let fri_parameters = FRIParameters::new(32, vec![1, 2, 1], domain_input); 216 | 217 | let rand_coset_index = 31usize; 218 | let cs = ConstraintSystem::new_ref(); 219 | let rand_coset_index_var = 220 | UInt64::new_witness(ns!(cs, "rand_coset_index"), || Ok(rand_coset_index as u64)) 221 | .unwrap(); 222 | let rand_coset_index_var_arr = rand_coset_index_var.to_bits_le()[..(1 << 6)].to_vec(); 223 | 224 | let rand_coset_index = 31; 225 | let (query_cosets, query_indices, domain_final) = 226 | FRIVerifier::prepare_query(rand_coset_index, &fri_parameters); 227 | let (query_cosets_actual, query_indices_actual, domain_final_actual) = 228 | FRIVerifierGadget::prepare_query(rand_coset_index_var_arr, &fri_parameters).unwrap(); 229 | 230 | for i in 0..query_cosets.len() { 231 | assert_eq!( 232 | query_cosets_actual[i].offset().value().unwrap(), 233 | query_cosets[i].offset 234 | ); 235 | assert_eq!(query_cosets_actual[i].gen, query_cosets[i].gen()); 236 | assert_eq!(query_cosets_actual[i].dim as usize, query_cosets[i].dim()); 237 | } 238 | 239 | assert_eq!(domain_final, domain_final_actual) 240 | } 241 | 242 | #[test] 243 | fn two_rounds_fri_test() { 244 | let cs = ConstraintSystem::new_ref(); 245 | 246 | let mut rng = test_rng(); 247 | let poly = DensePolynomial::rand(64, &mut rng); 248 | let offset = Fr::rand(&mut rng); 249 | let domain_input = Radix2CosetDomain::new_radix2_coset(128, offset); 250 | let evaluations_input = domain_input.evaluate(&poly); 251 | 252 | // set up verifier parameters 253 | let fri_parameters = FRIParameters::new(64, vec![1, 2, 2], domain_input); 254 | let alphas: Vec<_> = (0..3).map(|_| Fr::rand(&mut rng)).collect(); 255 | let alphas_var: Vec<_> = alphas 256 | .iter() 257 | .map(|x| FpVar::new_witness(ns!(cs, "alphas"), || Ok(x)).unwrap()) 258 | .collect(); 259 | 260 | // prover commits all round polynomial 261 | let (domain_round_0, evaluations_round_0) = FRIProver::interactive_phase_single_round( 262 | domain_input, 263 | evaluations_input.clone(), 264 | fri_parameters.localization_parameters[0], 265 | alphas[0], 266 | ); 267 | 268 | let (domain_round_1, evaluations_round_1) = FRIProver::interactive_phase_single_round( 269 | domain_round_0, 270 | evaluations_round_0.clone(), 271 | fri_parameters.localization_parameters[1], 272 | alphas[1], 273 | ); 274 | 275 | let (expected_domain_final, evaluations_final) = FRIProver::interactive_phase_single_round( 276 | domain_round_1, 277 | evaluations_round_1.clone(), 278 | fri_parameters.localization_parameters[2], 279 | alphas[2], 280 | ); 281 | 282 | let rand_coset_index = 31; 283 | let rand_coset_index_var = 284 | UInt64::new_witness(ns!(cs, "rand_coset_index"), || Ok(rand_coset_index)) 285 | .unwrap() 286 | .to_bits_le(); 287 | 288 | let (query_cosets, query_indices, domain_final) = 289 | FRIVerifierGadget::prepare_query(rand_coset_index_var, &fri_parameters).unwrap(); 290 | let (_, query_indices_native, _) = 291 | FRIVerifier::prepare_query(rand_coset_index as usize, &fri_parameters); 292 | 293 | assert_eq!(query_indices.len(), 3); 294 | assert_eq!(domain_final, expected_domain_final); 295 | 296 | let (indices, qi) = domain_input.query_position_to_coset( 297 | query_indices_native[0], 298 | fri_parameters.localization_parameters[0] as usize, 299 | ); 300 | assert_eq!(qi.offset, query_cosets[0].offset().value().unwrap()); 301 | let answer_input: Vec<_> = indices 302 | .iter() 303 | .map(|&i| { 304 | FpVar::new_witness(ns!(cs, "answer_input"), || Ok(evaluations_input[i])).unwrap() 305 | }) 306 | .collect(); 307 | 308 | let (indices, q0) = domain_round_0.query_position_to_coset( 309 | query_indices_native[1], 310 | fri_parameters.localization_parameters[1] as usize, 311 | ); 312 | assert_eq!(q0.offset, query_cosets[1].offset().value().unwrap()); 313 | let answer_round_0: Vec<_> = indices 314 | .iter() 315 | .map(|&i| { 316 | FpVar::new_witness( 317 | ns!(cs, "evaluations_round_0"), 318 | || Ok(evaluations_round_0[i]), 319 | ) 320 | .unwrap() 321 | }) 322 | .collect(); 323 | 324 | let (indices, q1) = domain_round_1.query_position_to_coset( 325 | query_indices_native[2], 326 | fri_parameters.localization_parameters[2] as usize, 327 | ); 328 | let answer_round_1: Vec<_> = indices 329 | .iter() 330 | .map(|&i| { 331 | FpVar::new_witness( 332 | ns!(cs, "evaluations_round_1"), 333 | || Ok(evaluations_round_1[i]), 334 | ) 335 | .unwrap() 336 | }) 337 | .collect(); 338 | assert_eq!(q1.offset, query_cosets[2].offset().value().unwrap()); 339 | 340 | let total_shrink_factor: u64 = fri_parameters.localization_parameters.iter().sum(); 341 | let final_poly_degree_bound = fri_parameters.tested_degree >> total_shrink_factor; 342 | let final_polynomial = DirectLDT::generate_low_degree_coefficients( 343 | domain_final, 344 | evaluations_final, 345 | final_poly_degree_bound as usize, 346 | ); 347 | let final_polynomial_coeffs: Vec<_> = final_polynomial 348 | .coeffs() 349 | .iter() 350 | .map(|x| FpVar::new_witness(ns!(cs, "final_poly_coeff"), || Ok(*x)).unwrap()) 351 | .collect(); 352 | let final_polynomial_var = 353 | DensePolynomialVar::from_coefficients_slice(&final_polynomial_coeffs); 354 | 355 | let result = FRIVerifierGadget::consistency_check( 356 | &fri_parameters, 357 | &query_indices, 358 | &query_cosets, 359 | &vec![answer_input, answer_round_0, answer_round_1], 360 | &alphas_var, 361 | &domain_final, 362 | &final_polynomial_var, 363 | ) 364 | .unwrap(); 365 | 366 | assert!(result.value().unwrap()); 367 | assert!(cs.is_satisfied().unwrap()); 368 | } 369 | } 370 | --------------------------------------------------------------------------------