├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── ci.yml │ └── linkify_changelog.yml ├── .gitignore ├── .hooks └── pre-commit ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── bench.rs ├── rustfmt.toml ├── scripts ├── install-hook.sh ├── linkify_changelog.py └── migrate.sh ├── src ├── alloc.rs ├── boolean │ ├── allocated.rs │ ├── and.rs │ ├── cmp.rs │ ├── convert.rs │ ├── eq.rs │ ├── mod.rs │ ├── not.rs │ ├── or.rs │ ├── select.rs │ ├── test_utils.rs │ └── xor.rs ├── cmp.rs ├── convert.rs ├── eq.rs ├── fields │ ├── cubic_extension.rs │ ├── emulated_fp │ │ ├── allocated_field_var.rs │ │ ├── allocated_mul_result.rs │ │ ├── field_var.rs │ │ ├── mod.rs │ │ ├── mul_result.rs │ │ ├── params.rs │ │ └── reduce.rs │ ├── fp │ │ ├── cmp.rs │ │ └── mod.rs │ ├── fp12.rs │ ├── fp2.rs │ ├── fp3.rs │ ├── fp4.rs │ ├── fp6_2over3.rs │ ├── fp6_3over2.rs │ ├── mod.rs │ └── quadratic_extension.rs ├── gr1cs_var.rs ├── groups │ ├── curves │ │ ├── mod.rs │ │ ├── short_weierstrass │ │ │ ├── bls12 │ │ │ │ └── mod.rs │ │ │ ├── mnt4 │ │ │ │ └── mod.rs │ │ │ ├── mnt6 │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ └── non_zero_affine.rs │ │ └── twisted_edwards │ │ │ └── mod.rs │ └── mod.rs ├── lib.rs ├── macros.rs ├── pairing │ ├── bls12 │ │ └── mod.rs │ ├── mnt4 │ │ └── mod.rs │ ├── mnt6 │ │ └── mod.rs │ └── mod.rs ├── poly │ ├── domain │ │ ├── mod.rs │ │ └── vanishing_poly.rs │ ├── evaluations │ │ ├── mod.rs │ │ └── univariate │ │ │ ├── lagrange_interpolator.rs │ │ │ └── mod.rs │ ├── mod.rs │ └── polynomial │ │ ├── mod.rs │ │ └── univariate │ │ ├── dense.rs │ │ └── mod.rs ├── select.rs ├── test_utils.rs ├── uint │ ├── add │ │ ├── mod.rs │ │ ├── saturating.rs │ │ └── wrapping.rs │ ├── and.rs │ ├── cmp.rs │ ├── convert.rs │ ├── eq.rs │ ├── mod.rs │ ├── not.rs │ ├── or.rs │ ├── prim_uint.rs │ ├── rotate.rs │ ├── select.rs │ ├── shl.rs │ ├── shr.rs │ ├── test_utils.rs │ └── xor.rs └── uint8.rs └── tests ├── arithmetic_tests.rs ├── from_test.rs └── to_constraint_field_test.rs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @arkworks-rs/maintainers 2 | -------------------------------------------------------------------------------- /.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/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: rand_xorshift 10 | versions: 11 | - 0.3.0 12 | - dependency-name: rand 13 | versions: 14 | - 0.8.0 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | merge_group: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | env: 9 | RUST_BACKTRACE: 1 10 | 11 | jobs: 12 | style: 13 | name: Check Style 14 | runs-on: ubuntu-latest 15 | steps: 16 | 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | 20 | - name: Install Rust 21 | uses: dtolnay/rust-toolchain@stable 22 | with: 23 | components: rustfmt 24 | 25 | - name: cargo fmt --check 26 | uses: actions-rs/cargo@v1 27 | with: 28 | command: fmt 29 | args: --all -- --check 30 | 31 | test: 32 | name: Test 33 | runs-on: ubuntu-latest 34 | env: 35 | RUSTFLAGS: -Dwarnings --cfg ci 36 | strategy: 37 | matrix: 38 | rust: 39 | - stable 40 | - nightly 41 | steps: 42 | - name: Checkout 43 | uses: actions/checkout@v3 44 | 45 | - name: Install Rust (${{ matrix.rust }}) 46 | uses: dtolnay/rust-toolchain@master 47 | with: 48 | toolchain: ${{ matrix.rust }} 49 | id: toolchain 50 | - run: rustup override set ${{steps.toolchain.outputs.name}} 51 | 52 | - uses: actions/cache@v4 53 | with: 54 | path: | 55 | ~/.cargo/registry 56 | ~/.cargo/git 57 | target 58 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 59 | 60 | - name: Check examples 61 | uses: actions-rs/cargo@v1 62 | with: 63 | command: check 64 | args: --examples --all 65 | 66 | - name: Check examples with all features on stable 67 | uses: actions-rs/cargo@v1 68 | with: 69 | command: check 70 | args: --examples --all-features --all 71 | if: matrix.rust == 'stable' 72 | 73 | - name: Check benchmarks on nightly 74 | uses: actions-rs/cargo@v1 75 | with: 76 | command: check 77 | args: --all-features --examples --all --benches 78 | if: matrix.rust == 'nightly' 79 | 80 | - name: Test 81 | uses: actions-rs/cargo@v1 82 | with: 83 | command: test 84 | args: "--all \ 85 | --all-features" 86 | 87 | docs: 88 | name: Check Documentation 89 | runs-on: ubuntu-latest 90 | steps: 91 | 92 | - name: Checkout 93 | uses: actions/checkout@v3 94 | - name: Install Rust 95 | uses: dtolnay/rust-toolchain@stable 96 | with: 97 | components: rustfmt 98 | 99 | - name: cargo doc --all --no-deps --document-private-items --all-features 100 | uses: actions-rs/cargo@v1 101 | with: 102 | command: doc 103 | args: --all --no-deps --document-private-items --all-features 104 | 105 | check_no_std: 106 | name: Check no_std 107 | runs-on: ubuntu-latest 108 | steps: 109 | - name: Checkout 110 | uses: actions/checkout@v3 111 | 112 | - name: Install Rust ARM64 113 | uses: dtolnay/rust-toolchain@stable 114 | id: toolchain-aarch64 115 | with: 116 | target: aarch64-unknown-none 117 | - run: rustup override set ${{steps.toolchain-aarch64.outputs.name}} 118 | 119 | - uses: actions/cache@v4 120 | with: 121 | path: | 122 | ~/.cargo/registry 123 | ~/.cargo/git 124 | target 125 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 126 | 127 | - name: r1cs-std 128 | run: | 129 | cargo build --no-default-features --target aarch64-unknown-none 130 | cargo check --examples --no-default-features --target aarch64-unknown-none 131 | 132 | test_against_curves: 133 | name: Test against curves 134 | runs-on: ubuntu-latest 135 | env: 136 | RUSTFLAGS: -Dwarnings 137 | strategy: 138 | matrix: 139 | curve: 140 | - bls12_377 141 | - pallas 142 | - vesta 143 | - mnt4_298 144 | - mnt6_298 145 | - ed_on_bls12_381 146 | steps: 147 | - name: Checkout curves 148 | uses: actions/checkout@v4 149 | with: 150 | repository: arkworks-rs/algebra 151 | 152 | - name: Checkout r1cs-std 153 | uses: actions/checkout@v4 154 | with: 155 | path: r1cs-std 156 | 157 | - name: Install Rust 158 | uses: dtolnay/rust-toolchain@stable 159 | 160 | - name: Patch cargo.toml 161 | run: | 162 | cd curves 163 | if grep -q "\[patch.crates-io\]" Cargo.toml ; then 164 | MATCH=$(awk '/\[patch.crates-io\]/{ print NR; exit }' Cargo.toml); 165 | sed -i "$MATCH,\$d" Cargo.toml 166 | fi 167 | { 168 | echo "[patch.crates-io]" 169 | echo "ark-std = { git = 'https://github.com/arkworks-rs/std' }" 170 | echo "ark-ec = { path = '../ec' }" 171 | echo "ark-ff = { path = '../ff' }" 172 | echo "ark-poly = { path = '../poly' }" 173 | echo "ark-relations = { git = 'https://github.com/arkworks-rs/snark' }" 174 | echo "ark-serialize = { path = '../serialize' }" 175 | echo "ark-algebra-bench-templates = { path = '../bench-templates' }" 176 | echo "ark-algebra-test-templates = { path = '../test-templates' }" 177 | echo "ark-r1cs-std = { path = '../r1cs-std' }" 178 | } >> Cargo.toml 179 | cd ${{ matrix.curve }} && cargo test --features 'r1cs' -------------------------------------------------------------------------------- /.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 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .DS_Store 4 | .idea 5 | *.iml 6 | *.ipynb_checkpoints 7 | *.pyc 8 | *.sage.py 9 | params 10 | *.swp 11 | *.swo 12 | -------------------------------------------------------------------------------- /.hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rustfmt --version &>/dev/null 4 | if [ $? != 0 ]; then 5 | printf "[pre_commit] \033[0;31merror\033[0m: \"rustfmt\" not available. \n" 6 | printf "[pre_commit] \033[0;31merror\033[0m: rustfmt can be installed via - \n" 7 | printf "[pre_commit] $ rustup component add rustfmt \n" 8 | exit 1 9 | fi 10 | 11 | problem_files=() 12 | 13 | # collect ill-formatted files 14 | for file in $(git diff --name-only --cached); do 15 | if [ ${file: -3} == ".rs" ]; then 16 | rustfmt +stable --check $file &>/dev/null 17 | if [ $? != 0 ]; then 18 | problem_files+=($file) 19 | fi 20 | fi 21 | done 22 | 23 | if [ ${#problem_files[@]} == 0 ]; then 24 | # done 25 | printf "[pre_commit] rustfmt \033[0;32mok\033[0m \n" 26 | else 27 | # reformat the files that need it and re-stage them. 28 | printf "[pre_commit] the following files were rustfmt'd before commit: \n" 29 | for file in ${problem_files[@]}; do 30 | rustfmt +stable $file 31 | git add $file 32 | printf "\033[0;32m $file\033[0m \n" 33 | done 34 | fi 35 | 36 | exit 0 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## Pending 4 | 5 | ### Breaking changes 6 | 7 | ### Features 8 | 9 | ### Improvements 10 | 11 | ### Bugfixes 12 | 13 | - [\#156](https://github.com/arkworks-rs/r1cs-std/pull/156) Fix panic in `impl Sum for FpVar` 14 | 15 | ## v0.5.0 16 | 17 | ### Breaking changes 18 | 19 | - [\#121](https://github.com/arkworks-rs/r1cs-std/pull/121) 20 | - Refactor `UInt{8,16,64,128}` into one struct `UInt`. 21 | - Remove `bits` module. 22 | - Use `std::ops` traits for `UInt` and `Boolean`. 23 | - [\#134](https://github.com/arkworks-rs/r1cs-std/pull/134) Add `Mul` bounds and impls for `CurveVar`. 24 | - [\#135](https://github.com/arkworks-rs/r1cs-std/pull/135) 25 | - Rename `NonNativeFieldVar` to `EmulatedFpVar`. 26 | - Rename `fields::nonnative` to `fields::emulated_fp`. 27 | - Rename `fields::nonnative::{Allocated}NonNativeMulResultVar` to `fields::emulated_fp::{Allocated}MulResultVar`. 28 | - [\#136](https://github.com/arkworks-rs/r1cs-std/pull/136) 29 | - Rename `ToBytesGadget::to_{non_unique_}bytes` → `ToBytesGadget::to_{non_unique_}bytes_in_le`. 30 | 31 | ### Features 32 | 33 | - [\#136](https://github.com/arkworks-rs/r1cs-std/pull/136) 34 | - Add `{BitAnd,BitOr,BitXor,BitAndAssign,BitOrAssign,BitXorAssign} for UInt`. 35 | - Add `UInt::rotate_{left,right}_in_place`. 36 | - Add `{Boolean,UInt}::not_in_place`. 37 | - Add `UInt::{from_bytes_le, from_bytes_be, to_bytes_be}`. 38 | - [\#143](https://github.com/arkworks-rs/r1cs-std/pull/143) Add `AllocVar::new_variable_with_inferred_mode`. 39 | - [\#144](https://github.com/arkworks-rs/r1cs-std/pull/144) Add `ToConstraintFieldGadget` bounds to `CurveVar` and `FieldVar` 40 | 41 | ### Improvements 42 | 43 | ### Bug Fixes 44 | 45 | - [\#145](https://github.com/arkworks-rs/r1cs-std/pull/145) 46 | - Avoid deeply nested `LinearCombinations` in `EvaluationsVar::interpolate_and_evaluate` to fix the stack overflow issue when calling `.value()` on the evaluation result. 47 | - [\#148](https://github.com/arkworks-rs/r1cs-std/pull/148) 48 | - Fix panic issues during in-circuit polynomial interpolation. 49 | 50 | ## 0.4.0 51 | 52 | - [\#117](https://github.com/arkworks-rs/r1cs-std/pull/117) Fix result of `precomputed_base_scalar_mul_le` to not discard previous value. 53 | - [\#124](https://github.com/arkworks-rs/r1cs-std/pull/124) Fix `scalar_mul_le` constraints unsatisfiability when short Weierstrass point is zero. 54 | - [\#127](https://github.com/arkworks-rs/r1cs-std/pull/127) Convert `NonNativeFieldVar` constants to little-endian bytes instead of big-endian (`ToBytesGadget`). 55 | - [\#133](https://github.com/arkworks-rs/r1cs-std/pull/133) Save 1 constraint in `FpVar::{is_eq, is_neq}` by removing the unnecessary booleanity check. 56 | 57 | ### Breaking changes 58 | 59 | - [\#86](https://github.com/arkworks-rs/r1cs-std/pull/86) Change the API for domains for coset. 60 | 61 | ### Features 62 | 63 | - [\#84](https://github.com/arkworks-rs/r1cs-std/pull/84) Expose `short_weierstrass::non_zero_affine` module 64 | and implement `EqGadget` for `NonZeroAffineVar`. 65 | - [\#79](https://github.com/arkworks-rs/r1cs-std/pull/79) Move `NonNativeFieldVar` from `ark-nonnative` to `ark-r1cs-std`. 66 | - [\#76](https://github.com/arkworks-rs/r1cs-std/pull/76) Implement `ToBytesGadget` for `Vec`. 67 | - [nonnative/\#45](https://github.com/arkworks-rs/nonnative/pull/45) Add `new_witness_with_le_bits` which returns the bits used during variable allocation. 68 | 69 | ### Improvements 70 | 71 | ### Bug Fixes 72 | 73 | - [\#101](https://github.com/arkworks-rs/r1cs-std/pull/101) Fix `is_zero` for twisted Edwards curves. 74 | - [\#86](https://github.com/arkworks-rs/r1cs-std/pull/86) Make result of `query_position_to_coset` consistent with `ark-ldt`. 75 | - [\#77](https://github.com/arkworks-rs/r1cs-std/pull/77) Fix BLS12 `G2PreparedGadget`'s `AllocVar` when G2 uses a divisive twist. 76 | 77 | ## v0.3.1 78 | 79 | ### Features 80 | 81 | - [\#71](https://github.com/arkworks-rs/r1cs-std/pull/71) Implement the `Sum` trait for `FpVar`. 82 | - [\#75](https://github.com/arkworks-rs/r1cs-std/pull/71) Introduce `mul_by_inverse_unchecked` for `FieldVar`. This accompanies the bug fix in [\#70](https://github.com/arkworks-rs/r1cs-std/pull/70). 83 | 84 | ### Bug Fixes 85 | 86 | - [\#70](https://github.com/arkworks-rs/r1cs-std/pull/70) Fix soundness issues of `mul_by_inverse` for field gadgets. 87 | 88 | ## v0.3.0 89 | 90 | ### Breaking changes 91 | 92 | - [\#60](https://github.com/arkworks-rs/r1cs-std/pull/60) Rename `AllocatedBit` to `AllocatedBool` for consistency with the `Boolean` variable. 93 | You can update downstream usage with `grep -rl 'AllocatedBit' . | xargs env LANG=C env LC_CTYPE=C sed -i '' 's/AllocatedBit/AllocatedBool/g'`. 94 | - [\#65](https://github.com/arkworks-rs/r1cs-std/pull/65) Rename `Radix2Domain` in `r1cs-std` to `Radix2DomainVar`. 95 | - [nonnative/\#43](https://github.com/arkworks-rs/nonnative/pull/43) Add padding to allocated nonnative element's `to_bytes`. 96 | 97 | ### Features 98 | 99 | - [\#53](https://github.com/arkworks-rs/r1cs-std/pull/53) Add univariate evaluation domain and Lagrange interpolation. 100 | 101 | ### Improvements 102 | 103 | - [\#65](https://github.com/arkworks-rs/r1cs-std/pull/65) Add support for non-constant coset offset in `Radix2DomainVar`. 104 | 105 | ### Bug Fixes 106 | 107 | ## v0.2.0 108 | 109 | ### Breaking changes 110 | 111 | - [\#12](https://github.com/arkworks-rs/r1cs-std/pull/12) Make the output of the `ToBitsGadget` impl for `FpVar` fixed-size 112 | - [\#48](https://github.com/arkworks-rs/r1cs-std/pull/48) Add `Clone` trait bound to `CondSelectGadget`. 113 | 114 | ### Features 115 | 116 | - [\#21](https://github.com/arkworks-rs/r1cs-std/pull/21) Add `UInt128` 117 | - [\#50](https://github.com/arkworks-rs/r1cs-std/pull/50) Add `DensePolynomialVar` 118 | 119 | ### Improvements 120 | 121 | - [\#5](https://github.com/arkworks-rs/r1cs-std/pull/5) Speedup BLS-12 pairing 122 | - [\#13](https://github.com/arkworks-rs/r1cs-std/pull/13) Add `ToConstraintFieldGadget` to `ProjectiveVar` 123 | - [\#15](https://github.com/arkworks-rs/r1cs-std/pull/15), #16 Allow `cs` to be `None` when converting a Montgomery point into a Twisted Edwards point 124 | - [\#20](https://github.com/arkworks-rs/r1cs-std/pull/20) Add `CondSelectGadget` impl for `UInt`s 125 | - [\#22](https://github.com/arkworks-rs/r1cs-std/pull/22) Reduce density of `three_bit_cond_neg_lookup` 126 | - [\#23](https://github.com/arkworks-rs/r1cs-std/pull/23) Reduce allocations in `UInt`s 127 | - [\#33](https://github.com/arkworks-rs/r1cs-std/pull/33) Speedup scalar multiplication by a constant 128 | - [\#35](https://github.com/arkworks-rs/r1cs-std/pull/35) Construct a `FpVar` from bits 129 | - [\#36](https://github.com/arkworks-rs/r1cs-std/pull/36) Implement `ToConstraintFieldGadget` for `Vec` 130 | - [\#40](https://github.com/arkworks-rs/r1cs-std/pull/40), #43 Faster scalar multiplication for Short Weierstrass curves by relying on affine formulae 131 | - [\#46](https://github.com/arkworks-rs/r1cs-std/pull/46) Add mux gadget as an auto-impl in `CondSelectGadget` to support random access of an array 132 | 133 | ### Bug fixes 134 | 135 | - [\#8](https://github.com/arkworks-rs/r1cs-std/pull/8) Fix bug in `three_bit_cond_neg_lookup` when using a constant lookup bit 136 | - [\#9](https://github.com/arkworks-rs/r1cs-std/pull/9) Fix bug in `short_weierstrass::ProjectiveVar::to_affine` 137 | - [\#29](https://github.com/arkworks-rs/r1cs-std/pull/29) Fix `to_non_unique_bytes` for `BLS12::G1Prepared` 138 | - [\#34](https://github.com/arkworks-rs/r1cs-std/pull/34) Fix `mul_by_inverse` for constants 139 | - [\#42](https://github.com/arkworks-rs/r1cs-std/pull/42) Fix regression in `mul_by_inverse` constraint count 140 | - [\#47](https://github.com/arkworks-rs/r1cs-std/pull/47) Compile with `panic='abort'` in release mode, for safety of the library across FFI boundaries 141 | - [\#57](https://github.com/arkworks-rs/r1cs-std/pull/57) Clean up `UInt` docs 142 | 143 | ## v0.1.0 144 | 145 | Initial release 146 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for considering making contributions to `arkworks-rs/r1cs-std`! 4 | 5 | Contributing to this repo can be done in several forms, such as participating in discussion or proposing code changes. 6 | To ensure a smooth workflow for all contributors, the following general procedure for contributing has been established: 7 | 8 | 1) Either open or find an issue you'd like to help with 9 | 2) Participate in thoughtful discussion on that issue 10 | 3) If you would like to contribute: 11 | * If the issue is a feature proposal, ensure that the proposal has been accepted 12 | * Ensure that nobody else has already begun working on this issue. 13 | If they have, please try to contact them to collaborate 14 | * 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) 15 | * We suggest using standard Github best practices for contributing: fork the repo, branch from the HEAD of `main`, make some commits on your branch, and submit a PR from the branch to `main`. 16 | More detail on this is below 17 | * Be sure to include a relevant change log entry in the Pending section of CHANGELOG.md (see file for log format) 18 | * If the change is breaking, we may add migration instructions. 19 | 20 | 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. 21 | 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. 22 | 23 | Looking for a good place to start contributing? How about checking out some good first issues 24 | 25 | ## Branch Structure 26 | 27 | `r1cs-std` has its default branch as `main`, which is where PRs are merged into. Releases will be periodically made, on no set schedule. 28 | All other branches should be assumed to be miscellaneous feature development branches. 29 | 30 | All downstream users of the library should be using tagged versions of the library pulled from cargo. 31 | 32 | ## How to work on a fork 33 | Please skip this section if you're familiar with contributing to opensource github projects. 34 | 35 | First fork the repo from the github UI, and clone it locally. 36 | Then in the repo, you want to add the repo you forked from as a new remote. You do this as: 37 | ```bash 38 | git remote add upstream git@github.com:arkworks-rs/r1cs-std.git 39 | ``` 40 | 41 | Then the way you make code contributions is to first think of a branch name that describes your change. 42 | Then do the following: 43 | ```bash 44 | git checkout main 45 | git pull upstream main 46 | git checkout -b $NEW_BRANCH_NAME 47 | ``` 48 | and then work as normal on that branch, and pull request to upstream master when you're done =) 49 | 50 | ## Updating documentation 51 | 52 | All PRs should aim to leave the code more documented than it started with. 53 | Please don't assume that its easy to infer what the code is doing, 54 | as that is almost always not the case for these complex protocols. 55 | (Even when you understand the paper!) 56 | 57 | Its often very useful to describe what is the high level view of what a code block is doing, 58 | 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. 59 | 60 | ## Performance improvements 61 | 62 | All performance improvements should be accompanied with benchmarks improving, or otherwise have it be clear that things have improved. 63 | For some areas of the codebase, performance roughly follows the number of field multiplications, but there are also many areas where 64 | hard to predict low level system effects such as cache locality and superscalar operations become important for performance. 65 | Thus performance can often become very non-intuitive / diverge from minimizing the number of arithmetic operations. -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ark-r1cs-std" 3 | version = "0.5.0" 4 | authors = ["arkworks contributors"] 5 | description = "A standard library for constraint system gadgets" 6 | homepage = "https://arkworks.rs" 7 | repository = "https://github.com/arkworks-rs/r1cs-std" 8 | documentation = "https://docs.rs/ark-r1cs-std/" 9 | keywords = ["zero-knowledge", "cryptography", "zkSNARK", "SNARK", "r1cs"] 10 | categories = ["cryptography"] 11 | include = ["Cargo.toml", "src", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] 12 | license = "MIT/Apache-2.0" 13 | edition = "2021" 14 | resolver = "2" 15 | 16 | [dependencies] 17 | ark-ff = { version = "0.5.0", default-features = false } 18 | ark-ec = { version = "0.5.0", default-features = false } 19 | ark-std = { version = "0.5.0", default-features = false } 20 | ark-relations = { version = "0.5.0", default-features = false } 21 | 22 | educe = "0.6.0" 23 | tracing = { version = "^0.1.0", default-features = false, features = ["attributes"] } 24 | itertools = { version = "0.14.0", default-features = false, features = [ "use_alloc" ] } 25 | num-bigint = { version = "0.4", default-features = false } 26 | num-traits = { version = "0.2", default-features = false } 27 | num-integer = { version = "0.1.44", default-features = false } 28 | 29 | [dev-dependencies] 30 | ark-test-curves = { version = "0.5.0", default-features = false, features = [ 31 | "bls12_381_scalar_field", 32 | "bls12_381_curve", 33 | "mnt4_753_scalar_field", 34 | ] } 35 | ark-poly = { version = "0.5.0", default-features = false } 36 | paste = "1.0" 37 | ark-bls12-377 = { version = "0.5.0", features = ["curve"], default-features = false } 38 | ark-bls12-381 = { version = "0.5.0", features = ["curve"], default-features = false } 39 | ark-mnt4-298 = { version = "0.5.0", features = ["curve"], default-features = false } 40 | ark-mnt4-753 = { version = "0.5.0", features = ["curve"], default-features = false } 41 | ark-mnt6-298 = { version = "0.5.0", default-features = false } 42 | ark-mnt6-753 = { version = "0.5.0", default-features = false } 43 | ark-pallas = { version = "0.5.0", features = ["curve"], default-features = false } 44 | ark-bn254 = { version = "0.5.0", features = ["curve"], default-features = false } 45 | tracing-subscriber = { version = "0.3", default-features = true } 46 | 47 | [features] 48 | default = ["std"] 49 | std = ["ark-ff/std", "ark-relations/std", "ark-std/std", "num-bigint/std", "itertools/use_std" ] 50 | parallel = ["std", "ark-ff/parallel", "ark-std/parallel"] 51 | 52 | [[bench]] 53 | name = "emulated-bench" 54 | path = "benches/bench.rs" 55 | harness = false 56 | 57 | [profile.release] 58 | opt-level = 3 59 | lto = "thin" 60 | incremental = true 61 | panic = 'abort' 62 | 63 | [profile.bench] 64 | opt-level = 3 65 | debug = false 66 | rpath = false 67 | lto = "thin" 68 | incremental = true 69 | debug-assertions = false 70 | 71 | [profile.dev] 72 | opt-level = 0 73 | panic = 'abort' 74 | 75 | [profile.test] 76 | opt-level = 3 77 | lto = "thin" 78 | incremental = true 79 | debug-assertions = true 80 | debug = true 81 | 82 | [lints.rust] 83 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ci)'] } 84 | 85 | 86 | # patch 87 | [patch.crates-io] 88 | ark-relations = { git = "https://github.com/arkworks-rs/snark.git", default-features = true } 89 | -------------------------------------------------------------------------------- /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 |

ark-r1cs-std

2 | 3 |

4 | 5 | 6 | 7 | 8 |

9 | 10 | The arkworks ecosystem consist of Rust libraries for designing and working with __zero knowledge succinct non-interactive arguments (zkSNARKs)__. This repository contains efficient implementations of constraint "gadgets" that enable checking common computations inside SNARKs, such as bit operations, finite field arithmetic, elliptic curve arithmetic, and pairings. 11 | 12 | This library is released under the MIT License and the Apache v2 License (see [License](#license)). 13 | 14 | **WARNING:** This is an academic proof-of-concept prototype, and in particular has not received careful code review. This implementation is NOT ready for production use. 15 | 16 | ## Build guide 17 | 18 | The library compiles on the `stable` toolchain of the Rust compiler. To install the latest version of Rust, first install `rustup` by following the instructions [here](https://rustup.rs/), or via your platform's package manager. Once `rustup` is installed, install the Rust toolchain by invoking: 19 | ```bash 20 | rustup install stable 21 | ``` 22 | 23 | After that, use `cargo`, the standard Rust build tool, to build the library: 24 | ```bash 25 | git clone https://github.com/arkworks-rs/r1cs-std.git 26 | cargo build --release 27 | ``` 28 | 29 | This library comes with unit tests for each of the provided crates. Run the tests with: 30 | ```bash 31 | cargo test 32 | ``` 33 | 34 | ## License 35 | 36 | This library is licensed under either of the following licenses, at your discretion. 37 | 38 | * Apache License Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 39 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 40 | 41 | Unless you explicitly state otherwise, any contribution submitted for inclusion in this library by you shall be dual licensed as above (as defined in the Apache v2 License), without any additional terms or conditions. 42 | 43 | ## Acknowledgements 44 | 45 | This work was supported by: 46 | a Google Faculty Award; 47 | the National Science Foundation; 48 | the UC Berkeley Center for Long-Term Cybersecurity; 49 | and donations from the Ethereum Foundation, the Interchain Foundation, and Qtum. 50 | 51 | An earlier version of this library was developed as part of the paper *"[ZEXE: Enabling Decentralized Private Computation][zexe]"*. 52 | 53 | [zexe]: https://ia.cr/2018/962 -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | wrap_comments = true 3 | normalize_comments = true 4 | use_try_shorthand = true 5 | match_block_trailing_comma = true 6 | use_field_init_shorthand = true 7 | edition = "2018" 8 | condense_wildcard_suffixes = true 9 | merge_imports = true 10 | -------------------------------------------------------------------------------- /scripts/install-hook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/env bash 2 | # This script will install the provided directory ../.hooks as the hook 3 | # directory for the present repo. See there for hooks, including a pre-commit 4 | # hook that runs rustfmt on files before a commit. 5 | 6 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 7 | HOOKS_DIR="${DIR}/../.hooks" 8 | 9 | git config core.hooksPath "$HOOKS_DIR" 10 | -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /scripts/migrate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # ----------------------------------------------------------------------------- 4 | # rename_r1cs.sh (Regex Version) 5 | # 6 | # Recursively: 7 | # 1. Renames .rs filenames: R1CS->GR1CS, r1cs->gr1cs (except those with r1cs_std). 8 | # 2. In .rs file contents, replaces R1CS->GR1CS, r1cs->gr1cs (except lines with r1cs_std), 9 | # ensuring words already in GR1CS/gr1cs are not changed again. 10 | # 3. Finally replaces enforce_constraint->enforce_r1cs_constraint (except lines with r1cs_std), 11 | # ensuring enforce_r1cs_constraint is not changed again. 12 | # ----------------------------------------------------------------------------- 13 | 14 | # --- Detect which 'sed' in-place flag to use (GNU vs BSD/macOS) --- 15 | if sed --version 2>/dev/null | grep -q "GNU"; then 16 | SED_INPLACE=(-i) 17 | else 18 | SED_INPLACE=(-i '') 19 | fi 20 | 21 | echo "###############################################" 22 | echo "# Make sure you have a backup before running. #" 23 | echo "###############################################" 24 | echo 25 | read -p "Are you sure you want to continue? (yes/no) " choice 26 | 27 | if [[ "$choice" != "yes" ]]; then 28 | echo "Aborting script execution." 29 | exit 1 30 | fi 31 | 32 | # 1) Rename files that contain 'r1cs' or 'R1CS' in their name, skipping 'r1cs_std' and existing 'gr1cs'. 33 | echo "Renaming files that contain 'r1cs' or 'R1CS' (but not 'r1cs_std' and already converted ones)..." 34 | export LC_ALL=C # Ensure consistent behavior for case conversions 35 | 36 | while IFS= read -r -d '' file; do 37 | if [[ "$file" =~ r1cs_std || "$file" =~ gr1cs ]]; then 38 | continue 39 | fi 40 | newfile="$(echo "$file" | sed -E 's/(^|[^g])r1cs/\1gr1cs/g; s/(^|[^G])R1CS/\1GR1CS/g')" 41 | if [[ "$newfile" != "$file" ]]; then 42 | echo " -> Renaming: $file -> $newfile" 43 | mv "$file" "$newfile" 44 | fi 45 | done < <(find . -type f -name "*.rs" -print0) 46 | 47 | # 2) Replace R1CS->GR1CS and r1cs->gr1cs in file contents, 48 | # skipping lines that have 'r1cs_std' and ensuring already transformed words are not changed again. 49 | echo "Replacing (R1CS->GR1CS, r1cs->gr1cs) in .rs file contents..." 50 | while IFS= read -r -d '' file; do 51 | sed "${SED_INPLACE[@]}" -E '/r1cs_std|enforce_r1cs_constraint/! s/(^|[^g])r1cs/\1gr1cs/g' "$file" 52 | sed "${SED_INPLACE[@]}" -E '/r1cs_std|enforce_r1cs_constraint/! s/(^|[^G])R1CS/\1GR1CS/g' "$file" 53 | done < <(find . -type f -name "*.rs" ! -name "*r1cs_std*" -print0) 54 | 55 | # 3) Replace 'enforce_constraint' -> 'enforce_r1cs_constraint', 56 | # skipping lines that have 'r1cs_std' and ensuring enforce_r1cs_constraint is not changed again. 57 | echo "Replacing enforce_constraint->enforce_r1cs_constraint..." 58 | while IFS= read -r -d '' file; do 59 | sed "${SED_INPLACE[@]}" '/r1cs_std|enforce_r1cs_constraint/! s/enforce_constraint/enforce_r1cs_constraint/g' "$file" 60 | done < <(find . -type f -name "*.rs" ! -name "*r1cs_std*" -print0) 61 | 62 | echo "Done!" 63 | -------------------------------------------------------------------------------- /src/alloc.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::Field; 2 | use ark_relations::gr1cs::{Namespace, SynthesisError}; 3 | use ark_std::vec::Vec; 4 | use core::borrow::Borrow; 5 | 6 | /// Describes the mode that a variable should be allocated in within 7 | /// a `ConstraintSystem`. 8 | #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Copy, Clone)] 9 | pub enum AllocationMode { 10 | /// Indicate to the `ConstraintSystem` that the high-level variable should 11 | /// be allocated as a constant. That is, no `Variable`s should be 12 | /// generated. 13 | Constant = 0, 14 | 15 | /// Indicate to the `ConstraintSystem` that the high-level variable should 16 | /// be allocated as a public input to the `ConstraintSystem`. 17 | Input = 1, 18 | 19 | /// Indicate to the `ConstraintSystem` that the high-level variable should 20 | /// be allocated as a private witness to the `ConstraintSystem`. 21 | Witness = 2, 22 | } 23 | 24 | impl AllocationMode { 25 | /// Outputs the maximum according to the relation `Constant < Input < 26 | /// Witness`. 27 | pub fn max(&self, other: Self) -> Self { 28 | use AllocationMode::*; 29 | match (self, other) { 30 | (Constant, _) => other, 31 | (Input, Constant) => *self, 32 | (Input, _) => other, 33 | (Witness, _) => *self, 34 | } 35 | } 36 | } 37 | 38 | /// Specifies how variables of type `Self` should be allocated in a 39 | /// `ConstraintSystem`. 40 | pub trait AllocVar: Sized { 41 | /// Allocates a new variable of type `Self` in the `ConstraintSystem` `cs`. 42 | /// The mode of allocation is decided by `mode`. 43 | fn new_variable>( 44 | cs: impl Into>, 45 | f: impl FnOnce() -> Result, 46 | mode: AllocationMode, 47 | ) -> Result; 48 | 49 | /// Allocates a new constant of type `Self` in the `ConstraintSystem` `cs`. 50 | /// 51 | /// This should *not* allocate any new variables or constraints in `cs`. 52 | #[tracing::instrument(target = "gr1cs", skip(cs, t))] 53 | fn new_constant( 54 | cs: impl Into>, 55 | t: impl Borrow, 56 | ) -> Result { 57 | Self::new_variable(cs, || Ok(t), AllocationMode::Constant) 58 | } 59 | 60 | /// Allocates a new public input of type `Self` in the `ConstraintSystem` 61 | /// `cs`. 62 | #[tracing::instrument(target = "gr1cs", skip(cs, f))] 63 | fn new_input>( 64 | cs: impl Into>, 65 | f: impl FnOnce() -> Result, 66 | ) -> Result { 67 | Self::new_variable(cs, f, AllocationMode::Input) 68 | } 69 | 70 | /// Allocates a new private witness of type `Self` in the `ConstraintSystem` 71 | /// `cs`. 72 | #[tracing::instrument(target = "gr1cs", skip(cs, f))] 73 | fn new_witness>( 74 | cs: impl Into>, 75 | f: impl FnOnce() -> Result, 76 | ) -> Result { 77 | Self::new_variable(cs, f, AllocationMode::Witness) 78 | } 79 | 80 | /// Allocates a new constant or private witness of type `Self` in the 81 | /// `ConstraintSystem` `cs` with the allocation mode inferred from `cs`. 82 | /// A constant is allocated if `cs` is `None`, and a private witness is 83 | /// allocated otherwise. 84 | /// 85 | /// A common use case is the creation of non-deterministic advice (a.k.a. 86 | /// hints) in the circuit, where this method can avoid boilerplate code 87 | /// while allowing optimization on circuit size. 88 | /// 89 | /// For example, to compute `x_var / y_var` where `y_var` is a non-zero 90 | /// variable, one can write: 91 | /// ``` 92 | /// use ark_ff::PrimeField; 93 | /// use ark_r1cs_std::{alloc::AllocVar, fields::{fp::FpVar, FieldVar}, GR1CSVar}; 94 | /// use ark_relations::gr1cs::SynthesisError; 95 | /// 96 | /// fn div(x_var: &FpVar, y_var: &FpVar) -> Result, SynthesisError> { 97 | /// let cs = x_var.cs().or(y_var.cs()); 98 | /// let z_var = FpVar::new_variable_with_inferred_mode(cs, || Ok(x_var.value()? / y_var.value()?))?; 99 | /// z_var.mul_equals(y_var, x_var)?; 100 | /// Ok(z_var) 101 | /// } 102 | /// ``` 103 | /// In this example, if either `x_var` or `y_var` is a witness variable, 104 | /// then `z_var` is also a witness variable. On the other hand, `z_var` 105 | /// is a constant if both `x_var` and `y_var` are constants (i.e., `cs` 106 | /// is `None`), and future operations on `z_var` do not generate any 107 | /// constraints. 108 | /// 109 | /// (Note that we use division as an example for simplicity. You may 110 | /// call `x_var.mul_by_inverse(y_var)?` directly, which internally works 111 | /// similarly to the above code.) 112 | #[tracing::instrument(target = "r1cs", skip(cs, f))] 113 | fn new_variable_with_inferred_mode>( 114 | cs: impl Into>, 115 | f: impl FnOnce() -> Result, 116 | ) -> Result { 117 | let ns: Namespace = cs.into(); 118 | let cs = ns.cs(); 119 | let mode = if cs.is_none() { 120 | AllocationMode::Constant 121 | } else { 122 | AllocationMode::Witness 123 | }; 124 | Self::new_variable(cs, f, mode) 125 | } 126 | } 127 | 128 | /// This blanket implementation just allocates variables in `Self` 129 | /// element by element. 130 | impl> AllocVar<[I], F> for Vec { 131 | fn new_variable>( 132 | cs: impl Into>, 133 | f: impl FnOnce() -> Result, 134 | mode: AllocationMode, 135 | ) -> Result { 136 | let ns = cs.into(); 137 | let cs = ns.cs(); 138 | f().and_then(|v| { 139 | v.borrow() 140 | .iter() 141 | .map(|e| A::new_variable(cs.clone(), || Ok(e), mode)) 142 | .collect() 143 | }) 144 | } 145 | } 146 | 147 | /// Dummy impl for `()`. 148 | impl AllocVar<(), F> for () { 149 | fn new_variable>( 150 | _cs: impl Into>, 151 | _f: impl FnOnce() -> Result, 152 | _mode: AllocationMode, 153 | ) -> Result { 154 | Ok(()) 155 | } 156 | } 157 | 158 | /// This blanket implementation just allocates variables in `Self` 159 | /// element by element. 160 | impl, const N: usize> AllocVar<[I; N], F> for [A; N] { 161 | fn new_variable>( 162 | cs: impl Into>, 163 | f: impl FnOnce() -> Result, 164 | mode: AllocationMode, 165 | ) -> Result { 166 | let ns = cs.into(); 167 | let cs = ns.cs(); 168 | f().map(|v| { 169 | let v = v.borrow(); 170 | core::array::from_fn(|i| A::new_variable(cs.clone(), || Ok(&v[i]), mode).unwrap()) 171 | }) 172 | } 173 | } 174 | 175 | /// This blanket implementation just allocates variables in `Self` 176 | /// element by element. 177 | impl, const N: usize> AllocVar<[I], F> for [A; N] { 178 | fn new_variable>( 179 | cs: impl Into>, 180 | f: impl FnOnce() -> Result, 181 | mode: AllocationMode, 182 | ) -> Result { 183 | let ns = cs.into(); 184 | let cs = ns.cs(); 185 | f().map(|v| { 186 | let v = v.borrow(); 187 | core::array::from_fn(|i| A::new_variable(cs.clone(), || Ok(&v[i]), mode).unwrap()) 188 | }) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/boolean/cmp.rs: -------------------------------------------------------------------------------- 1 | use crate::cmp::CmpGadget; 2 | 3 | use super::*; 4 | 5 | impl CmpGadget for Boolean { 6 | fn is_ge(&self, other: &Self) -> Result, SynthesisError> { 7 | // a | b | (a | !b) | a >= b 8 | // --|---|--------|-------- 9 | // 0 | 0 | 1 | 1 10 | // 1 | 0 | 1 | 1 11 | // 0 | 1 | 0 | 0 12 | // 1 | 1 | 1 | 1 13 | Ok(self | &(!other)) 14 | } 15 | } 16 | 17 | impl Boolean { 18 | /// Enforces that `bits`, when interpreted as a integer, is less than 19 | /// `F::characteristic()`, That is, interpret bits as a little-endian 20 | /// integer, and enforce that this integer is "in the field Z_p", where 21 | /// `p = F::characteristic()` . 22 | #[tracing::instrument(target = "gr1cs")] 23 | pub fn enforce_in_field_le(bits: &[Self]) -> Result<(), SynthesisError> { 24 | // `bits` < F::characteristic() <==> `bits` <= F::characteristic() -1 25 | let mut b = F::characteristic().to_vec(); 26 | assert_eq!(b[0] % 2, 1); 27 | b[0] -= 1; // This works, because the LSB is one, so there's no borrows. 28 | let run = Self::enforce_smaller_or_equal_than_le(bits, b)?; 29 | 30 | // We should always end in a "run" of zeros, because 31 | // the characteristic is an odd prime. So, this should 32 | // be empty. 33 | assert!(run.is_empty()); 34 | 35 | Ok(()) 36 | } 37 | 38 | /// Enforces that `bits` is less than or equal to `element`, 39 | /// when both are interpreted as (little-endian) integers. 40 | #[tracing::instrument(target = "gr1cs", skip(element))] 41 | pub fn enforce_smaller_or_equal_than_le( 42 | bits: &[Self], 43 | element: impl AsRef<[u64]>, 44 | ) -> Result, SynthesisError> { 45 | let b: &[u64] = element.as_ref(); 46 | 47 | let mut bits_iter = bits.iter().rev(); // Iterate in big-endian 48 | 49 | // Runs of ones in r 50 | let mut last_run = Boolean::TRUE; 51 | let mut current_run = vec![]; 52 | 53 | let mut element_num_bits = 0; 54 | for _ in BitIteratorBE::without_leading_zeros(b) { 55 | element_num_bits += 1; 56 | } 57 | 58 | if bits.len() > element_num_bits { 59 | let mut or_result = Boolean::FALSE; 60 | for should_be_zero in &bits[element_num_bits..] { 61 | or_result |= should_be_zero; 62 | let _ = bits_iter.next().unwrap(); 63 | } 64 | or_result.enforce_equal(&Boolean::FALSE)?; 65 | } 66 | 67 | for (b, a) in BitIteratorBE::without_leading_zeros(b).zip(bits_iter.by_ref()) { 68 | if b { 69 | // This is part of a run of ones. 70 | current_run.push(a.clone()); 71 | } else { 72 | if !current_run.is_empty() { 73 | // This is the start of a run of zeros, but we need 74 | // to k-ary AND against `last_run` first. 75 | 76 | current_run.push(last_run.clone()); 77 | last_run = Self::kary_and(¤t_run)?; 78 | current_run.truncate(0); 79 | } 80 | 81 | // If `last_run` is true, `a` must be false, or it would 82 | // not be in the field. 83 | // 84 | // If `last_run` is false, `a` can be true or false. 85 | // 86 | // Ergo, at least one of `last_run` and `a` must be false. 87 | Self::enforce_kary_nand(&[last_run.clone(), a.clone()])?; 88 | } 89 | } 90 | assert!(bits_iter.next().is_none()); 91 | 92 | Ok(current_run) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/boolean/convert.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::convert::ToConstraintFieldGadget; 3 | 4 | impl ToBytesGadget for Boolean { 5 | /// Outputs `1u8` if `self` is true, and `0u8` otherwise. 6 | #[tracing::instrument(target = "gr1cs")] 7 | fn to_bytes_le(&self) -> Result>, SynthesisError> { 8 | let value = self.value().map(u8::from).ok(); 9 | let mut bits = [Boolean::FALSE; 8]; 10 | bits[0] = self.clone(); 11 | Ok(vec![UInt8 { bits, value }]) 12 | } 13 | } 14 | 15 | impl ToConstraintFieldGadget for Boolean { 16 | #[tracing::instrument(target = "gr1cs")] 17 | fn to_constraint_field(&self) -> Result>, SynthesisError> { 18 | let var = From::from(self.clone()); 19 | Ok(vec![var]) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/boolean/not.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::Field; 2 | use ark_relations::gr1cs::SynthesisError; 3 | use ark_std::ops::Not; 4 | 5 | use super::Boolean; 6 | 7 | impl Boolean { 8 | fn _not(&self) -> Result { 9 | let mut result = self.clone(); 10 | result.not_in_place()?; 11 | Ok(result) 12 | } 13 | 14 | /// Negates `self` in place. 15 | pub fn not_in_place(&mut self) -> Result<(), SynthesisError> { 16 | match *self { 17 | Boolean::Constant(ref mut c) => *c = !*c, 18 | Boolean::Var(ref mut v) => *v = v.not()?, 19 | } 20 | Ok(()) 21 | } 22 | } 23 | 24 | impl<'a, F: Field> Not for &'a Boolean { 25 | type Output = Boolean; 26 | /// Negates `self`. 27 | /// 28 | /// This *does not* create any new variables or constraints. 29 | /// ``` 30 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 31 | /// // We'll use the BLS12-381 scalar field for our constraints. 32 | /// use ark_test_curves::bls12_381::Fr; 33 | /// use ark_relations::gr1cs::*; 34 | /// use ark_r1cs_std::prelude::*; 35 | /// 36 | /// let cs = ConstraintSystem::::new_ref(); 37 | /// 38 | /// let a = Boolean::new_witness(cs.clone(), || Ok(true))?; 39 | /// let b = Boolean::new_witness(cs.clone(), || Ok(false))?; 40 | /// 41 | /// (!&a).enforce_equal(&b)?; 42 | /// (!&b).enforce_equal(&a)?; 43 | /// 44 | /// (!&a).enforce_equal(&Boolean::FALSE)?; 45 | /// (!&b).enforce_equal(&Boolean::TRUE)?; 46 | /// 47 | /// assert!(cs.is_satisfied().unwrap()); 48 | /// # Ok(()) 49 | /// # } 50 | /// ``` 51 | #[tracing::instrument(target = "gr1cs", skip(self))] 52 | fn not(self) -> Self::Output { 53 | self._not().unwrap() 54 | } 55 | } 56 | 57 | impl<'a, F: Field> Not for &'a mut Boolean { 58 | type Output = Boolean; 59 | 60 | #[tracing::instrument(target = "gr1cs", skip(self))] 61 | fn not(self) -> Self::Output { 62 | self._not().unwrap() 63 | } 64 | } 65 | 66 | impl Not for Boolean { 67 | type Output = Boolean; 68 | 69 | #[tracing::instrument(target = "gr1cs", skip(self))] 70 | fn not(self) -> Self::Output { 71 | self._not().unwrap() 72 | } 73 | } 74 | 75 | #[cfg(test)] 76 | mod tests { 77 | use super::*; 78 | use crate::{ 79 | alloc::{AllocVar, AllocationMode}, 80 | boolean::test_utils::run_unary_exhaustive, 81 | prelude::EqGadget, 82 | GR1CSVar, 83 | }; 84 | use ark_test_curves::bls12_381::Fr; 85 | 86 | #[test] 87 | fn not() { 88 | run_unary_exhaustive::(|a| { 89 | let cs = a.cs(); 90 | let computed = !&a; 91 | let expected_mode = if a.is_constant() { 92 | AllocationMode::Constant 93 | } else { 94 | AllocationMode::Witness 95 | }; 96 | let expected = Boolean::new_variable(cs.clone(), || Ok(!a.value()?), expected_mode)?; 97 | assert_eq!(expected.value(), computed.value()); 98 | expected.enforce_equal(&computed)?; 99 | if !a.is_constant() { 100 | assert!(cs.is_satisfied().unwrap()); 101 | } 102 | Ok(()) 103 | }) 104 | .unwrap() 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/boolean/or.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::PrimeField; 2 | use ark_relations::gr1cs::SynthesisError; 3 | use ark_std::ops::{BitOr, BitOrAssign}; 4 | 5 | use crate::{ 6 | eq::EqGadget, 7 | fields::{fp::FpVar, FieldVar}, 8 | }; 9 | 10 | use super::Boolean; 11 | 12 | impl Boolean { 13 | fn _or(&self, other: &Self) -> Result { 14 | use Boolean::*; 15 | match (self, other) { 16 | (&Constant(false), x) | (x, &Constant(false)) => Ok(x.clone()), 17 | (&Constant(true), _) | (_, &Constant(true)) => Ok(Constant(true)), 18 | (Var(ref x), Var(ref y)) => Ok(Var(x.or(y)?)), 19 | } 20 | } 21 | 22 | /// Outputs `bits[0] | bits[1] | ... | bits.last().unwrap()`. 23 | /// 24 | /// ``` 25 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 26 | /// // We'll use the BLS12-381 scalar field for our constraints. 27 | /// use ark_test_curves::bls12_381::Fr; 28 | /// use ark_relations::gr1cs::*; 29 | /// use ark_r1cs_std::prelude::*; 30 | /// 31 | /// let cs = ConstraintSystem::::new_ref(); 32 | /// 33 | /// let a = Boolean::new_witness(cs.clone(), || Ok(true))?; 34 | /// let b = Boolean::new_witness(cs.clone(), || Ok(false))?; 35 | /// let c = Boolean::new_witness(cs.clone(), || Ok(false))?; 36 | /// 37 | /// Boolean::kary_or(&[a.clone(), b.clone(), c.clone()])?.enforce_equal(&Boolean::TRUE)?; 38 | /// Boolean::kary_or(&[a.clone(), c.clone()])?.enforce_equal(&Boolean::TRUE)?; 39 | /// Boolean::kary_or(&[b.clone(), c.clone()])?.enforce_equal(&Boolean::FALSE)?; 40 | /// 41 | /// assert!(cs.is_satisfied().unwrap()); 42 | /// # Ok(()) 43 | /// # } 44 | /// ``` 45 | #[tracing::instrument(target = "gr1cs")] 46 | pub fn kary_or(bits: &[Self]) -> Result { 47 | assert!(!bits.is_empty()); 48 | if bits.len() <= 3 { 49 | let mut cur: Option = None; 50 | for next in bits { 51 | cur = if let Some(b) = cur { 52 | Some(b | next) 53 | } else { 54 | Some(next.clone()) 55 | }; 56 | } 57 | 58 | Ok(cur.expect("should not be 0")) 59 | } else { 60 | // b0 | b1 | ... | bN == 1 if and only if not all of b0, b1, ..., bN are 0. 61 | // We can enforce this by requiring that the sum of b0, b1, ..., bN is not 0. 62 | let sum_bits: FpVar<_> = bits.iter().map(|b| FpVar::from(b.clone())).sum(); 63 | sum_bits.is_neq(&FpVar::zero()) 64 | } 65 | } 66 | } 67 | 68 | impl<'a, F: PrimeField> BitOr for &'a Boolean { 69 | type Output = Boolean; 70 | 71 | /// Outputs `self | other`. 72 | /// 73 | /// If at least one of `self` and `other` are constants, then this method 74 | /// *does not* create any constraints or variables. 75 | /// 76 | /// ``` 77 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 78 | /// // We'll use the BLS12-381 scalar field for our constraints. 79 | /// use ark_test_curves::bls12_381::Fr; 80 | /// use ark_relations::gr1cs::*; 81 | /// use ark_r1cs_std::prelude::*; 82 | /// 83 | /// let cs = ConstraintSystem::::new_ref(); 84 | /// 85 | /// let a = Boolean::new_witness(cs.clone(), || Ok(true))?; 86 | /// let b = Boolean::new_witness(cs.clone(), || Ok(false))?; 87 | /// 88 | /// (&a | &b).enforce_equal(&Boolean::TRUE)?; 89 | /// (&b | &a).enforce_equal(&Boolean::TRUE)?; 90 | /// 91 | /// (&a | &a).enforce_equal(&Boolean::TRUE)?; 92 | /// (&b | &b).enforce_equal(&Boolean::FALSE)?; 93 | /// 94 | /// assert!(cs.is_satisfied().unwrap()); 95 | /// # Ok(()) 96 | /// # } 97 | /// ``` 98 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 99 | fn bitor(self, other: Self) -> Self::Output { 100 | self._or(other).unwrap() 101 | } 102 | } 103 | 104 | impl<'a, F: PrimeField> BitOr<&'a Self> for Boolean { 105 | type Output = Boolean; 106 | 107 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 108 | fn bitor(self, other: &Self) -> Self::Output { 109 | self._or(&other).unwrap() 110 | } 111 | } 112 | 113 | impl<'a, F: PrimeField> BitOr> for &'a Boolean { 114 | type Output = Boolean; 115 | 116 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 117 | fn bitor(self, other: Boolean) -> Self::Output { 118 | self._or(&other).unwrap() 119 | } 120 | } 121 | 122 | impl BitOr for Boolean { 123 | type Output = Self; 124 | 125 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 126 | fn bitor(self, other: Self) -> Self::Output { 127 | self._or(&other).unwrap() 128 | } 129 | } 130 | 131 | impl BitOrAssign for Boolean { 132 | /// Sets `self = self | other`. 133 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 134 | fn bitor_assign(&mut self, other: Self) { 135 | let result = self._or(&other).unwrap(); 136 | *self = result; 137 | } 138 | } 139 | 140 | impl<'a, F: PrimeField> BitOrAssign<&'a Self> for Boolean { 141 | /// Sets `self = self | other`. 142 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 143 | fn bitor_assign(&mut self, other: &'a Self) { 144 | let result = self._or(other).unwrap(); 145 | *self = result; 146 | } 147 | } 148 | 149 | #[cfg(test)] 150 | mod tests { 151 | use super::*; 152 | use crate::{ 153 | alloc::{AllocVar, AllocationMode}, 154 | boolean::test_utils::run_binary_exhaustive, 155 | prelude::EqGadget, 156 | GR1CSVar, 157 | }; 158 | use ark_test_curves::bls12_381::Fr; 159 | 160 | #[test] 161 | fn or() { 162 | run_binary_exhaustive::(|a, b| { 163 | let cs = a.cs().or(b.cs()); 164 | let both_constant = a.is_constant() && b.is_constant(); 165 | let computed = &a | &b; 166 | let expected_mode = if both_constant { 167 | AllocationMode::Constant 168 | } else { 169 | AllocationMode::Witness 170 | }; 171 | let expected = 172 | Boolean::new_variable(cs.clone(), || Ok(a.value()? | b.value()?), expected_mode)?; 173 | assert_eq!(expected.value(), computed.value()); 174 | expected.enforce_equal(&computed)?; 175 | if !both_constant { 176 | assert!(cs.is_satisfied().unwrap()); 177 | } 178 | Ok(()) 179 | }) 180 | .unwrap() 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/boolean/select.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | impl Boolean { 4 | /// Conditionally selects one of `first` and `second` based on the value of 5 | /// `self`: 6 | /// 7 | /// If `self.is_eq(&Boolean::TRUE)`, this outputs `first`; else, it outputs 8 | /// `second`. 9 | /// ``` 10 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 11 | /// // We'll use the BLS12-381 scalar field for our constraints. 12 | /// use ark_test_curves::bls12_381::Fr; 13 | /// use ark_relations::gr1cs::*; 14 | /// use ark_r1cs_std::prelude::*; 15 | /// 16 | /// let cs = ConstraintSystem::::new_ref(); 17 | /// 18 | /// let a = Boolean::new_witness(cs.clone(), || Ok(true))?; 19 | /// let b = Boolean::new_witness(cs.clone(), || Ok(false))?; 20 | /// 21 | /// let cond = Boolean::new_witness(cs.clone(), || Ok(true))?; 22 | /// 23 | /// cond.select(&a, &b)?.enforce_equal(&Boolean::TRUE)?; 24 | /// cond.select(&b, &a)?.enforce_equal(&Boolean::FALSE)?; 25 | /// 26 | /// assert!(cs.is_satisfied().unwrap()); 27 | /// # Ok(()) 28 | /// # } 29 | /// ``` 30 | #[tracing::instrument(target = "gr1cs", skip(first, second))] 31 | pub fn select>( 32 | &self, 33 | first: &T, 34 | second: &T, 35 | ) -> Result { 36 | T::conditionally_select(&self, first, second) 37 | } 38 | } 39 | impl CondSelectGadget for Boolean { 40 | #[tracing::instrument(target = "gr1cs")] 41 | fn conditionally_select( 42 | cond: &Boolean, 43 | true_val: &Self, 44 | false_val: &Self, 45 | ) -> Result { 46 | use Boolean::*; 47 | match cond { 48 | Constant(true) => Ok(true_val.clone()), 49 | Constant(false) => Ok(false_val.clone()), 50 | cond @ Var(_) => match (true_val, false_val) { 51 | (x, &Constant(false)) => Ok(cond & x), 52 | (&Constant(false), x) => Ok((!cond) & x), 53 | (&Constant(true), x) => Ok(cond | x), 54 | (x, &Constant(true)) => Ok((!cond) | x), 55 | (a, b) => { 56 | let cs = cond.cs(); 57 | let result: Boolean = 58 | AllocatedBool::new_witness_without_booleanity_check(cs.clone(), || { 59 | let cond = cond.value()?; 60 | Ok(if cond { a.value()? } else { b.value()? }) 61 | })? 62 | .into(); 63 | // a = self; b = other; c = cond; 64 | // 65 | // r = c * a + (1 - c) * b 66 | // r = b + c * (a - b) 67 | // c * (a - b) = r - b 68 | // 69 | // If a, b, cond are all boolean, so is r. 70 | // 71 | // self | other | cond | result 72 | // -----|-------|---------------- 73 | // 0 | 0 | 1 | 0 74 | // 0 | 1 | 1 | 0 75 | // 1 | 0 | 1 | 1 76 | // 1 | 1 | 1 | 1 77 | // 0 | 0 | 0 | 0 78 | // 0 | 1 | 0 | 1 79 | // 1 | 0 | 0 | 0 80 | // 1 | 1 | 0 | 1 81 | cs.enforce_r1cs_constraint( 82 | cond.lc(), 83 | lc!() + a.lc() - b.lc(), 84 | lc!() + result.lc() - b.lc(), 85 | )?; 86 | 87 | Ok(result) 88 | }, 89 | }, 90 | } 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use super::*; 97 | use crate::{ 98 | alloc::{AllocVar, AllocationMode}, 99 | boolean::test_utils::run_binary_exhaustive, 100 | prelude::EqGadget, 101 | GR1CSVar, 102 | }; 103 | use ark_test_curves::bls12_381::Fr; 104 | 105 | #[test] 106 | fn or() { 107 | run_binary_exhaustive::(|a, b| { 108 | let cs = a.cs().or(b.cs()); 109 | let both_constant = a.is_constant() && b.is_constant(); 110 | let expected_mode = if both_constant { 111 | AllocationMode::Constant 112 | } else { 113 | AllocationMode::Witness 114 | }; 115 | for cond in [true, false] { 116 | let expected = Boolean::new_variable( 117 | cs.clone(), 118 | || Ok(if cond { a.value()? } else { b.value()? }), 119 | expected_mode, 120 | )?; 121 | let cond = Boolean::new_variable(cs.clone(), || Ok(cond), expected_mode)?; 122 | let computed = cond.select(&a, &b)?; 123 | 124 | assert_eq!(expected.value(), computed.value()); 125 | expected.enforce_equal(&computed)?; 126 | if !both_constant { 127 | assert!(cs.is_satisfied().unwrap()); 128 | } 129 | } 130 | Ok(()) 131 | }) 132 | .unwrap() 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/boolean/test_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::test_utils; 2 | 3 | use super::*; 4 | use ark_relations::gr1cs::{ConstraintSystem, SynthesisError}; 5 | 6 | pub(crate) fn test_unary_op( 7 | a: bool, 8 | mode: AllocationMode, 9 | test: impl FnOnce(Boolean) -> Result<(), SynthesisError>, 10 | ) -> Result<(), SynthesisError> { 11 | let cs = ConstraintSystem::::new_ref(); 12 | let a = Boolean::::new_variable(cs.clone(), || Ok(a), mode)?; 13 | test(a) 14 | } 15 | 16 | pub(crate) fn test_binary_op( 17 | a: bool, 18 | b: bool, 19 | mode_a: AllocationMode, 20 | mode_b: AllocationMode, 21 | test: impl FnOnce(Boolean, Boolean) -> Result<(), SynthesisError>, 22 | ) -> Result<(), SynthesisError> { 23 | let cs = ConstraintSystem::::new_ref(); 24 | let a = Boolean::::new_variable(cs.clone(), || Ok(a), mode_a)?; 25 | let b = Boolean::::new_variable(cs.clone(), || Ok(b), mode_b)?; 26 | test(a, b) 27 | } 28 | 29 | pub(crate) fn run_binary_exhaustive( 30 | test: impl Fn(Boolean, Boolean) -> Result<(), SynthesisError> + Copy, 31 | ) -> Result<(), SynthesisError> { 32 | for (mode_a, a) in test_utils::combination([false, true].into_iter()) { 33 | for (mode_b, b) in test_utils::combination([false, true].into_iter()) { 34 | test_binary_op(a, b, mode_a, mode_b, test)?; 35 | } 36 | } 37 | Ok(()) 38 | } 39 | 40 | pub(crate) fn run_unary_exhaustive( 41 | test: impl Fn(Boolean) -> Result<(), SynthesisError> + Copy, 42 | ) -> Result<(), SynthesisError> { 43 | for (mode, a) in test_utils::combination([false, true].into_iter()) { 44 | test_unary_op(a, mode, test)?; 45 | } 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /src/boolean/xor.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::Field; 2 | use ark_relations::gr1cs::SynthesisError; 3 | use ark_std::ops::{BitXor, BitXorAssign}; 4 | 5 | use super::Boolean; 6 | 7 | impl Boolean { 8 | fn _xor(&self, other: &Self) -> Result { 9 | use Boolean::*; 10 | match (self, other) { 11 | (&Constant(false), x) | (x, &Constant(false)) => Ok(x.clone()), 12 | (&Constant(true), x) | (x, &Constant(true)) => Ok(!x), 13 | (Var(ref x), Var(ref y)) => Ok(Var(x.xor(y)?)), 14 | } 15 | } 16 | } 17 | 18 | impl<'a, F: Field> BitXor for &'a Boolean { 19 | type Output = Boolean; 20 | 21 | /// Outputs `self ^ other`. 22 | /// 23 | /// If at least one of `self` and `other` are constants, then this method 24 | /// *does not* create any constraints or variables. 25 | /// 26 | /// ``` 27 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 28 | /// // We'll use the BLS12-381 scalar field for our constraints. 29 | /// use ark_test_curves::bls12_381::Fr; 30 | /// use ark_relations::gr1cs::*; 31 | /// use ark_r1cs_std::prelude::*; 32 | /// 33 | /// let cs = ConstraintSystem::::new_ref(); 34 | /// 35 | /// let a = Boolean::new_witness(cs.clone(), || Ok(true))?; 36 | /// let b = Boolean::new_witness(cs.clone(), || Ok(false))?; 37 | /// 38 | /// (&a ^ &b).enforce_equal(&Boolean::TRUE)?; 39 | /// (&b ^ &a).enforce_equal(&Boolean::TRUE)?; 40 | /// 41 | /// (&a ^ &a).enforce_equal(&Boolean::FALSE)?; 42 | /// (&b ^ &b).enforce_equal(&Boolean::FALSE)?; 43 | /// 44 | /// assert!(cs.is_satisfied().unwrap()); 45 | /// # Ok(()) 46 | /// # } 47 | /// ``` 48 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 49 | fn bitxor(self, other: Self) -> Self::Output { 50 | self._xor(other).unwrap() 51 | } 52 | } 53 | 54 | impl<'a, F: Field> BitXor<&'a Self> for Boolean { 55 | type Output = Boolean; 56 | 57 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 58 | fn bitxor(self, other: &Self) -> Self::Output { 59 | self._xor(&other).unwrap() 60 | } 61 | } 62 | 63 | impl<'a, F: Field> BitXor> for &'a Boolean { 64 | type Output = Boolean; 65 | 66 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 67 | fn bitxor(self, other: Boolean) -> Self::Output { 68 | self._xor(&other).unwrap() 69 | } 70 | } 71 | 72 | impl BitXor for Boolean { 73 | type Output = Self; 74 | 75 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 76 | fn bitxor(self, other: Self) -> Self::Output { 77 | self._xor(&other).unwrap() 78 | } 79 | } 80 | 81 | impl BitXorAssign for Boolean { 82 | /// Sets `self = self ^ other`. 83 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 84 | fn bitxor_assign(&mut self, other: Self) { 85 | let result = self._xor(&other).unwrap(); 86 | *self = result; 87 | } 88 | } 89 | 90 | impl<'a, F: Field> BitXorAssign<&'a Self> for Boolean { 91 | /// Sets `self = self ^ other`. 92 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 93 | fn bitxor_assign(&mut self, other: &'a Self) { 94 | let result = self._xor(other).unwrap(); 95 | *self = result; 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use super::*; 102 | use crate::{ 103 | alloc::{AllocVar, AllocationMode}, 104 | boolean::test_utils::run_binary_exhaustive, 105 | prelude::EqGadget, 106 | GR1CSVar, 107 | }; 108 | use ark_test_curves::bls12_381::Fr; 109 | 110 | #[test] 111 | fn xor() { 112 | run_binary_exhaustive::(|a, b| { 113 | let cs = a.cs().or(b.cs()); 114 | let both_constant = a.is_constant() && b.is_constant(); 115 | let computed = &a ^ &b; 116 | let expected_mode = if both_constant { 117 | AllocationMode::Constant 118 | } else { 119 | AllocationMode::Witness 120 | }; 121 | let expected = 122 | Boolean::new_variable(cs.clone(), || Ok(a.value()? ^ b.value()?), expected_mode)?; 123 | assert_eq!(expected.value(), computed.value()); 124 | expected.enforce_equal(&computed)?; 125 | if !both_constant { 126 | assert!(cs.is_satisfied().unwrap()); 127 | } 128 | Ok(()) 129 | }) 130 | .unwrap() 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/cmp.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::{Field, PrimeField}; 2 | use ark_relations::gr1cs::SynthesisError; 3 | 4 | use crate::{boolean::Boolean, eq::EqGadget, GR1CSVar}; 5 | 6 | /// Specifies how to generate constraints for comparing two variables. 7 | pub trait CmpGadget: GR1CSVar + EqGadget { 8 | /// Checks if `self` is greater than `other`. 9 | fn is_gt(&self, other: &Self) -> Result, SynthesisError> { 10 | other.is_lt(self) 11 | } 12 | 13 | /// Checks if `self` is greater than or equal to `other`. 14 | fn is_ge(&self, other: &Self) -> Result, SynthesisError>; 15 | 16 | /// Checks if `self` is less than `other`. 17 | fn is_lt(&self, other: &Self) -> Result, SynthesisError> { 18 | Ok(!self.is_ge(other)?) 19 | } 20 | 21 | /// Checks if `self` is less than or equal to `other`. 22 | fn is_le(&self, other: &Self) -> Result, SynthesisError> { 23 | other.is_ge(self) 24 | } 25 | } 26 | 27 | /// Mimics the behavior of `std::cmp::PartialOrd` for `()`. 28 | impl CmpGadget for () { 29 | fn is_gt(&self, _other: &Self) -> Result, SynthesisError> { 30 | Ok(Boolean::FALSE) 31 | } 32 | 33 | fn is_ge(&self, _other: &Self) -> Result, SynthesisError> { 34 | Ok(Boolean::TRUE) 35 | } 36 | 37 | fn is_lt(&self, _other: &Self) -> Result, SynthesisError> { 38 | Ok(Boolean::FALSE) 39 | } 40 | 41 | fn is_le(&self, _other: &Self) -> Result, SynthesisError> { 42 | Ok(Boolean::TRUE) 43 | } 44 | } 45 | 46 | /// Mimics the lexicographic comparison behavior of `std::cmp::PartialOrd` for 47 | /// `[T]`. 48 | impl, F: PrimeField> CmpGadget for [T] { 49 | fn is_ge(&self, other: &Self) -> Result, SynthesisError> { 50 | let mut result = Boolean::TRUE; 51 | let mut all_equal_so_far = Boolean::TRUE; 52 | for (a, b) in self.iter().zip(other) { 53 | all_equal_so_far &= a.is_eq(b)?; 54 | result &= a.is_gt(b)? | &all_equal_so_far; 55 | } 56 | Ok(result) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/convert.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::Field; 2 | use ark_relations::gr1cs::SynthesisError; 3 | use ark_std::vec::Vec; 4 | 5 | use crate::{boolean::Boolean, uint8::UInt8}; 6 | 7 | /// Specifies constraints for conversion to a little-endian bit representation 8 | /// of `self`. 9 | pub trait ToBitsGadget { 10 | /// Outputs the canonical little-endian bit-wise representation of `self`. 11 | /// 12 | /// This is the correct default for 99% of use cases. 13 | fn to_bits_le(&self) -> Result>, SynthesisError>; 14 | 15 | /// Outputs a possibly non-unique little-endian bit-wise representation of 16 | /// `self`. 17 | /// 18 | /// If you're not absolutely certain that your usecase can get away with a 19 | /// non-canonical representation, please use `self.to_bits()` instead. 20 | fn to_non_unique_bits_le(&self) -> Result>, SynthesisError> { 21 | self.to_bits_le() 22 | } 23 | 24 | /// Outputs the canonical big-endian bit-wise representation of `self`. 25 | fn to_bits_be(&self) -> Result>, SynthesisError> { 26 | let mut res = self.to_bits_le()?; 27 | res.reverse(); 28 | Ok(res) 29 | } 30 | 31 | /// Outputs a possibly non-unique big-endian bit-wise representation of 32 | /// `self`. 33 | fn to_non_unique_bits_be(&self) -> Result>, SynthesisError> { 34 | let mut res = self.to_non_unique_bits_le()?; 35 | res.reverse(); 36 | Ok(res) 37 | } 38 | } 39 | 40 | impl ToBitsGadget for Boolean { 41 | fn to_bits_le(&self) -> Result>, SynthesisError> { 42 | Ok(vec![self.clone()]) 43 | } 44 | } 45 | 46 | impl ToBitsGadget for [Boolean] { 47 | /// Outputs `self`. 48 | fn to_bits_le(&self) -> Result>, SynthesisError> { 49 | Ok(self.to_vec()) 50 | } 51 | } 52 | 53 | impl ToBitsGadget for Vec 54 | where 55 | [T]: ToBitsGadget, 56 | { 57 | fn to_bits_le(&self) -> Result>, SynthesisError> { 58 | self.as_slice().to_bits_le().map(|v| v.to_vec()) 59 | } 60 | 61 | fn to_non_unique_bits_le(&self) -> Result>, SynthesisError> { 62 | self.as_slice().to_non_unique_bits_le().map(|v| v.to_vec()) 63 | } 64 | } 65 | 66 | /// Specifies constraints for conversion to a little-endian byte representation 67 | /// of `self`. 68 | pub trait ToBytesGadget { 69 | /// Outputs a canonical, little-endian, byte decomposition of `self`. 70 | /// 71 | /// This is the correct default for 99% of use cases. 72 | fn to_bytes_le(&self) -> Result>, SynthesisError>; 73 | 74 | /// Outputs a possibly non-unique byte decomposition of `self`. 75 | /// 76 | /// If you're not absolutely certain that your usecase can get away with a 77 | /// non-canonical representation, please use `self.to_bytes_le(cs)` instead. 78 | fn to_non_unique_bytes_le(&self) -> Result>, SynthesisError> { 79 | self.to_bytes_le() 80 | } 81 | } 82 | 83 | impl<'a, F: Field, T: 'a + ToBytesGadget> ToBytesGadget for &'a T { 84 | fn to_bytes_le(&self) -> Result>, SynthesisError> { 85 | (*self).to_bytes_le() 86 | } 87 | 88 | fn to_non_unique_bytes_le(&self) -> Result>, SynthesisError> { 89 | (*self).to_non_unique_bytes_le() 90 | } 91 | } 92 | 93 | impl, F: Field> ToBytesGadget for [T] { 94 | fn to_bytes_le(&self) -> Result>, SynthesisError> { 95 | let mut bytes = Vec::new(); 96 | for elem in self { 97 | let elem = elem.to_bytes_le()?; 98 | bytes.extend_from_slice(&elem); 99 | // Make sure that there's enough capacity to avoid reallocations. 100 | bytes.reserve(elem.len() * (self.len() - 1)); 101 | } 102 | Ok(bytes) 103 | } 104 | 105 | fn to_non_unique_bytes_le(&self) -> Result>, SynthesisError> { 106 | let mut bytes = Vec::new(); 107 | for elem in self { 108 | let elem = elem.to_non_unique_bytes_le()?; 109 | bytes.extend_from_slice(&elem); 110 | // Make sure that there's enough capacity to avoid reallocations. 111 | bytes.reserve(elem.len() * (self.len() - 1)); 112 | } 113 | Ok(bytes) 114 | } 115 | } 116 | 117 | impl, F: Field> ToBytesGadget for Vec { 118 | fn to_bytes_le(&self) -> Result>, SynthesisError> { 119 | self.as_slice().to_bytes_le() 120 | } 121 | 122 | fn to_non_unique_bytes_le(&self) -> Result>, SynthesisError> { 123 | self.as_slice().to_non_unique_bytes_le() 124 | } 125 | } 126 | 127 | impl, F: Field, const N: usize> ToBytesGadget for [T; N] { 128 | fn to_bytes_le(&self) -> Result>, SynthesisError> { 129 | self.as_slice().to_bytes_le() 130 | } 131 | 132 | fn to_non_unique_bytes_le(&self) -> Result>, SynthesisError> { 133 | self.as_slice().to_non_unique_bytes_le() 134 | } 135 | } 136 | 137 | impl ToBytesGadget for () { 138 | fn to_bytes_le(&self) -> Result>, SynthesisError> { 139 | Ok(Vec::new()) 140 | } 141 | 142 | fn to_non_unique_bytes_le(&self) -> Result>, SynthesisError> { 143 | Ok(Vec::new()) 144 | } 145 | } 146 | 147 | /// Specifies how to convert a variable of type `Self` to variables of 148 | /// type `FpVar` 149 | pub trait ToConstraintFieldGadget { 150 | /// Converts `self` to `FpVar` variables. 151 | fn to_constraint_field( 152 | &self, 153 | ) -> Result>, ark_relations::gr1cs::SynthesisError>; 154 | } 155 | -------------------------------------------------------------------------------- /src/fields/emulated_fp/mod.rs: -------------------------------------------------------------------------------- 1 | //! ## Overview 2 | //! 3 | //! This module implements a field gadget for a prime field `Fp` over another 4 | //! prime field `Fq` where `p != q`. 5 | //! 6 | //! When writing constraint systems for many cryptographic proofs, we are 7 | //! restricted to a native field (e.g., the scalar field of the pairing-friendly 8 | //! curve). This can be inconvenient; for example, the recursive composition of 9 | //! proofs via cycles of curves requires the verifier to compute over a 10 | //! non-native field. 11 | //! 12 | //! The library makes it possible to write computations over a non-native field 13 | //! in the same way one would write computations over the native field. This 14 | //! naturally introduces additional overhead, which we minimize using a variety 15 | //! of optimizations. (Nevertheless, the overhead is still substantial, and 16 | //! native fields should be used where possible.) 17 | //! 18 | //! ## Usage 19 | //! 20 | //! Because [`EmulatedFpVar`] implements the [`FieldVar`] trait in arkworks, 21 | //! we can treat it like a native prime field variable ([`FpVar`]). 22 | //! 23 | //! We can do the standard field operations, such as `+`, `-`, and `*`. See the 24 | //! following example: 25 | //! 26 | //! ```rust 27 | //! # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 28 | //! # use ark_std::UniformRand; 29 | //! # use ark_relations::{ns, gr1cs::ConstraintSystem}; 30 | //! # use ark_r1cs_std::prelude::*; 31 | //! use ark_r1cs_std::fields::emulated_fp::EmulatedFpVar; 32 | //! use ark_bls12_377::{Fr, Fq}; 33 | //! 34 | //! # let mut rng = ark_std::test_rng(); 35 | //! # let a_value = Fr::rand(&mut rng); 36 | //! # let b_value = Fr::rand(&mut rng); 37 | //! # let cs = ConstraintSystem::::new_ref(); 38 | //! 39 | //! let a = EmulatedFpVar::::new_witness(ns!(cs, "a"), || Ok(a_value))?; 40 | //! let b = EmulatedFpVar::::new_witness(ns!(cs, "b"), || Ok(b_value))?; 41 | //! 42 | //! // add 43 | //! let a_plus_b = &a + &b; 44 | //! 45 | //! // sub 46 | //! let a_minus_b = &a - &b; 47 | //! 48 | //! // multiply 49 | //! let a_times_b = &a * &b; 50 | //! 51 | //! // enforce equality 52 | //! a.enforce_equal(&b)?; 53 | //! # Ok(()) 54 | //! # } 55 | //! ``` 56 | //! 57 | //! ## Advanced optimization 58 | //! 59 | //! After each multiplication, our library internally performs a *reduce* 60 | //! operation, which reduces an intermediate type [`MulResultVar`] 61 | //! to the normalized type [`EmulatedFpVar`]. This enables a user to 62 | //! seamlessly perform a sequence of operations without worrying about the 63 | //! underlying details. 64 | //! 65 | //! However, this operation is expensive and is sometimes avoidable. We can 66 | //! reduce the number of constraints by using this intermediate type, which only 67 | //! supports additions. To multiply, it must be reduced back to 68 | //! [`EmulatedFpVar`]. See below for a skeleton example. 69 | //! 70 | //! --- 71 | //! 72 | //! To compute `a * b + c * d`, the straightforward (but more expensive) 73 | //! implementation is as follows: 74 | //! 75 | //! ```ignore 76 | //! let a_times_b = &a * &b; 77 | //! let c_times_d = &c * &d; 78 | //! let res = &a_times_b + &c_times_d; 79 | //! ``` 80 | //! 81 | //! This performs two *reduce* operations in total, one for each multiplication. 82 | //! 83 | //! --- 84 | //! 85 | //! We can save one reduction by using [`MulResultVar`], as 86 | //! follows: 87 | //! 88 | //! ```ignore 89 | //! let a_times_b = a.mul_without_reduce(&b)?; 90 | //! let c_times_d = c.mul_without_reduce(&d)?; 91 | //! let res = (&a_times_b + &c_times_d)?.reduce()?; 92 | //! ``` 93 | //! 94 | //! It performs only one *reduce* operation and is roughly 2x faster than the 95 | //! first implementation. 96 | //! 97 | //! ## Inspiration and basic design 98 | //! 99 | //! This implementation employs the standard idea of using multiple **limbs** to 100 | //! represent an element of the target field. For example, an element in the 101 | //! TargetF may be represented by three BaseF elements (i.e., the 102 | //! limbs). 103 | //! 104 | //! ```text 105 | //! TargetF -> limb 1, limb 2, and limb 3 (each is a BaseF element) 106 | //! ``` 107 | //! 108 | //! After some computation, the limbs become saturated and need to be 109 | //! **reduced**, in order to engage in more computation. 110 | //! 111 | //! We heavily use the optimization techniques in [\[KPS18\]](https://akosba.github.io/papers/xjsnark.pdf) and [\[OWWB20\]](https://eprint.iacr.org/2019/1494). 112 | //! Both works have their own open-source libraries: 113 | //! [xJsnark](https://github.com/akosba/xjsnark) and 114 | //! [bellman-bignat](https://github.com/alex-ozdemir/bellman-bignat). 115 | //! Compared with these, this module works with the `arkworks` ecosystem. 116 | //! It also provides the option (based on an `optimization_goal` for the 117 | //! constraint system) to optimize for constraint density instead of number of 118 | //! constraints, which improves efficiency in proof systems like [Marlin](https://github.com/arkworks-rs/marlin). 119 | //! 120 | //! ## References 121 | //! \[KPS18\]: A. E. Kosba, C. Papamanthou, and E. Shi. "xJsnark: a framework for efficient verifiable computation," in *Proceedings of the 39th Symposium on Security and Privacy*, ser. S&P ’18, 2018, pp. 944–961. 122 | //! 123 | //! \[OWWB20\]: A. Ozdemir, R. S. Wahby, B. Whitehat, and D. Boneh. "Scaling verifiable computation using efficient set accumulators," in *Proceedings of the 29th USENIX Security Symposium*, ser. Security ’20, 2020. 124 | //! 125 | //! [`EmulatedFpVar`]: crate::fields::emulated_fp::EmulatedFpVar 126 | //! [`MulResultVar`]: crate::fields::emulated_fp::MulResultVar 127 | //! [`FpVar`]: crate::fields::fp::FpVar 128 | 129 | #![allow( 130 | clippy::redundant_closure_call, 131 | clippy::enum_glob_use, 132 | clippy::missing_errors_doc, 133 | clippy::cast_possible_truncation, 134 | clippy::unseparated_literal_suffix 135 | )] 136 | 137 | use ark_std::fmt::Debug; 138 | 139 | /// Utilities for sampling parameters for non-native field gadgets 140 | /// 141 | /// - `BaseF`: the constraint field 142 | /// - `TargetF`: the field being simulated 143 | /// - `num_limbs`: how many limbs are used 144 | /// - `bits_per_limb`: the size of the limbs 145 | pub mod params; 146 | /// How are non-native elements reduced? 147 | pub(crate) mod reduce; 148 | 149 | /// a macro for computing ceil(log2(x)) for a field element x 150 | macro_rules! overhead { 151 | ($x:expr) => {{ 152 | use ark_ff::BigInteger; 153 | let num = $x; 154 | let num_bits = num.into_bigint().to_bits_be(); 155 | let mut skipped_bits = 0; 156 | for b in num_bits.iter() { 157 | if *b == false { 158 | skipped_bits += 1; 159 | } else { 160 | break; 161 | } 162 | } 163 | 164 | let mut is_power_of_2 = true; 165 | for b in num_bits.iter().skip(skipped_bits + 1) { 166 | if *b == true { 167 | is_power_of_2 = false; 168 | } 169 | } 170 | 171 | if is_power_of_2 { 172 | num_bits.len() - skipped_bits 173 | } else { 174 | num_bits.len() - skipped_bits + 1 175 | } 176 | }}; 177 | } 178 | 179 | pub(crate) use overhead; 180 | 181 | /// Parameters for a specific `EmulatedFpVar` instantiation 182 | #[derive(Clone, Debug)] 183 | pub struct NonNativeFieldConfig { 184 | /// The number of limbs (`BaseF` elements) used to represent a 185 | /// `TargetF` element. Highest limb first. 186 | pub num_limbs: usize, 187 | 188 | /// The number of bits of the limb 189 | pub bits_per_limb: usize, 190 | } 191 | 192 | mod allocated_field_var; 193 | pub use allocated_field_var::*; 194 | 195 | mod allocated_mul_result; 196 | pub use allocated_mul_result::*; 197 | 198 | mod field_var; 199 | pub use field_var::*; 200 | 201 | mod mul_result; 202 | pub use mul_result::*; 203 | -------------------------------------------------------------------------------- /src/fields/emulated_fp/mul_result.rs: -------------------------------------------------------------------------------- 1 | use super::{AllocatedMulResultVar, EmulatedFpVar}; 2 | use ark_ff::PrimeField; 3 | use ark_relations::gr1cs::Result as R1CSResult; 4 | 5 | /// An intermediate representation especially for the result of a 6 | /// multiplication, containing more limbs. It is intended for advanced usage to 7 | /// improve the efficiency. 8 | /// 9 | /// That is, instead of calling `mul`, one can call `mul_without_reduce` to 10 | /// obtain this intermediate representation, which can still be added. 11 | /// Then, one can call `reduce` to reduce it back to `EmulatedFpVar`. 12 | /// This may help cut the number of reduce operations. 13 | #[derive(Debug)] 14 | #[must_use] 15 | pub enum MulResultVar { 16 | /// as a constant 17 | Constant(TargetF), 18 | /// as an allocated gadget 19 | Var(AllocatedMulResultVar), 20 | } 21 | 22 | impl MulResultVar { 23 | /// Create a zero `MulResultVar` (used for additions) 24 | pub fn zero() -> Self { 25 | Self::Constant(TargetF::zero()) 26 | } 27 | 28 | /// Create an `MulResultVar` from a constant 29 | pub fn constant(v: TargetF) -> Self { 30 | Self::Constant(v) 31 | } 32 | 33 | /// Reduce the `MulResultVar` back to EmulatedFpVar 34 | #[tracing::instrument(target = "gr1cs")] 35 | pub fn reduce(&self) -> R1CSResult> { 36 | match self { 37 | Self::Constant(c) => Ok(EmulatedFpVar::Constant(*c)), 38 | Self::Var(v) => Ok(EmulatedFpVar::Var(v.reduce()?)), 39 | } 40 | } 41 | } 42 | 43 | impl From<&EmulatedFpVar> 44 | for MulResultVar 45 | { 46 | fn from(src: &EmulatedFpVar) -> Self { 47 | match src { 48 | EmulatedFpVar::Constant(c) => MulResultVar::Constant(*c), 49 | EmulatedFpVar::Var(v) => { 50 | MulResultVar::Var(AllocatedMulResultVar::::from(v)) 51 | }, 52 | } 53 | } 54 | } 55 | 56 | impl_bounded_ops!( 57 | MulResultVar, 58 | TargetF, 59 | Add, 60 | add, 61 | AddAssign, 62 | add_assign, 63 | |this: &'a MulResultVar, other: &'a MulResultVar| { 64 | use MulResultVar::*; 65 | match (this, other) { 66 | (Constant(c1), Constant(c2)) => Constant(*c1 + c2), 67 | (Constant(c), Var(v)) | (Var(v), Constant(c)) => Var(v.add_constant(c).unwrap()), 68 | (Var(v1), Var(v2)) => Var(v1.add(v2).unwrap()), 69 | } 70 | }, 71 | |this: &'a MulResultVar, other: TargetF| { this + &MulResultVar::Constant(other) }, 72 | (TargetF: PrimeField, BaseF: PrimeField), 73 | ); 74 | -------------------------------------------------------------------------------- /src/fields/emulated_fp/params.rs: -------------------------------------------------------------------------------- 1 | use super::NonNativeFieldConfig; 2 | 3 | /// Obtain the parameters from a `ConstraintSystem`'s cache or generate a new 4 | /// one 5 | #[must_use] 6 | pub const fn get_params( 7 | target_field_size: usize, 8 | base_field_size: usize, 9 | optimization_type: OptimizationType, 10 | ) -> NonNativeFieldConfig { 11 | let (num_of_limbs, limb_size) = 12 | find_parameters(base_field_size, target_field_size, optimization_type); 13 | NonNativeFieldConfig { 14 | num_limbs: num_of_limbs, 15 | bits_per_limb: limb_size, 16 | } 17 | } 18 | 19 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 20 | /// The type of optimization target for the parameters searching 21 | pub enum OptimizationType { 22 | /// Optimized for constraints 23 | Constraints, 24 | /// Optimized for weight 25 | Weight, 26 | } 27 | 28 | /// A function to search for parameters for emulated field gadgets 29 | pub const fn find_parameters( 30 | base_field_prime_length: usize, 31 | target_field_prime_bit_length: usize, 32 | optimization_type: OptimizationType, 33 | ) -> (usize, usize) { 34 | let mut found = false; 35 | let mut min_cost = 0usize; 36 | let mut min_cost_limb_size = 0usize; 37 | let mut min_cost_num_of_limbs = 0usize; 38 | 39 | let surfeit = 10; 40 | let mut max_limb_size = (base_field_prime_length - 1 - surfeit - 1) / 2 - 1; 41 | if max_limb_size > target_field_prime_bit_length { 42 | max_limb_size = target_field_prime_bit_length; 43 | } 44 | let mut limb_size = 1; 45 | 46 | while limb_size <= max_limb_size { 47 | let num_of_limbs = (target_field_prime_bit_length + limb_size - 1) / limb_size; 48 | 49 | let group_size = 50 | (base_field_prime_length - 1 - surfeit - 1 - 1 - limb_size + limb_size - 1) / limb_size; 51 | let num_of_groups = (2 * num_of_limbs - 1 + group_size - 1) / group_size; 52 | 53 | let mut this_cost = 0; 54 | 55 | match optimization_type { 56 | OptimizationType::Constraints => { 57 | this_cost += 2 * num_of_limbs - 1; 58 | }, 59 | OptimizationType::Weight => { 60 | this_cost += 6 * num_of_limbs * num_of_limbs; 61 | }, 62 | }; 63 | 64 | match optimization_type { 65 | OptimizationType::Constraints => { 66 | this_cost += target_field_prime_bit_length; // allocation of k 67 | this_cost += target_field_prime_bit_length + num_of_limbs; // allocation of r 68 | // this_cost += 2 * num_of_limbs - 1; // compute kp 69 | this_cost += num_of_groups + (num_of_groups - 1) * (limb_size * 2 + surfeit) + 1; 70 | // equality check 71 | }, 72 | OptimizationType::Weight => { 73 | this_cost += target_field_prime_bit_length * 3 + target_field_prime_bit_length; // allocation of k 74 | this_cost += target_field_prime_bit_length * 3 75 | + target_field_prime_bit_length 76 | + num_of_limbs; // allocation of r 77 | this_cost += num_of_limbs * num_of_limbs + 2 * (2 * num_of_limbs - 1); // compute kp 78 | this_cost += num_of_limbs 79 | + num_of_groups 80 | + 6 * num_of_groups 81 | + (num_of_groups - 1) * (2 * limb_size + surfeit) * 4 82 | + 2; // equality check 83 | }, 84 | }; 85 | 86 | if !found || this_cost < min_cost { 87 | found = true; 88 | min_cost = this_cost; 89 | min_cost_limb_size = limb_size; 90 | min_cost_num_of_limbs = num_of_limbs; 91 | } 92 | 93 | limb_size += 1; 94 | } 95 | 96 | (min_cost_num_of_limbs, min_cost_limb_size) 97 | } 98 | -------------------------------------------------------------------------------- /src/fields/fp12.rs: -------------------------------------------------------------------------------- 1 | use crate::fields::{fp2::Fp2Var, fp6_3over2::Fp6Var, quadratic_extension::*, FieldVar}; 2 | use ark_ff::{ 3 | fields::{fp12_2over3over2::*, Field}, 4 | fp6_3over2::Fp6Config, 5 | QuadExtConfig, 6 | }; 7 | use ark_relations::gr1cs::SynthesisError; 8 | 9 | /// A degree-12 extension field constructed as the tower of a 10 | /// quadratic extension over a cubic extension over a quadratic extension field. 11 | /// This is the R1CS equivalent of `ark_ff::fp12_2over3over2::Fp12

`. 12 | pub type Fp12Var

= QuadExtVar::Fp6Config>, Fp12ConfigWrapper

>; 13 | 14 | type Fp2Config

= <

::Fp6Config as Fp6Config>::Fp2Config; 15 | 16 | impl QuadExtVarConfig> for Fp12ConfigWrapper

{ 17 | fn mul_base_field_var_by_frob_coeff(fe: &mut Fp6Var, power: usize) { 18 | fe.c0 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD]; 19 | fe.c1 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD]; 20 | fe.c2 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD]; 21 | } 22 | } 23 | 24 | impl Fp12Var

{ 25 | /// Multiplies by a sparse element of the form `(c0 = (c0, c1, 0), c1 = (0, 26 | /// d1, 0))`. 27 | #[inline] 28 | pub fn mul_by_014( 29 | &self, 30 | c0: &Fp2Var>, 31 | c1: &Fp2Var>, 32 | d1: &Fp2Var>, 33 | ) -> Result { 34 | let v0 = self.c0.mul_by_c0_c1_0(&c0, &c1)?; 35 | let v1 = self.c1.mul_by_0_c1_0(&d1)?; 36 | let new_c0 = Self::mul_base_field_by_nonresidue(&v1)? + &v0; 37 | 38 | let new_c1 = (&self.c0 + &self.c1).mul_by_c0_c1_0(&c0, &(c1 + d1))? - &v0 - &v1; 39 | Ok(Self::new(new_c0, new_c1)) 40 | } 41 | 42 | /// Multiplies by a sparse element of the form `(c0 = (c0, 0, 0), c1 = (d0, 43 | /// d1, 0))`. 44 | #[inline] 45 | pub fn mul_by_034( 46 | &self, 47 | c0: &Fp2Var>, 48 | d0: &Fp2Var>, 49 | d1: &Fp2Var>, 50 | ) -> Result { 51 | let a0 = &self.c0.c0 * c0; 52 | let a1 = &self.c0.c1 * c0; 53 | let a2 = &self.c0.c2 * c0; 54 | let a = Fp6Var::new(a0, a1, a2); 55 | let b = self.c1.mul_by_c0_c1_0(&d0, &d1)?; 56 | 57 | let c0 = c0 + d0; 58 | let c1 = d1; 59 | let e = (&self.c0 + &self.c1).mul_by_c0_c1_0(&c0, &c1)?; 60 | let new_c1 = e - (&a + &b); 61 | let new_c0 = Self::mul_base_field_by_nonresidue(&b)? + &a; 62 | 63 | Ok(Self::new(new_c0, new_c1)) 64 | } 65 | 66 | /// Squares `self` when `self` is in the cyclotomic subgroup. 67 | pub fn cyclotomic_square(&self) -> Result { 68 | if characteristic_square_mod_6_is_one(Fp12::

::characteristic()) { 69 | let fp2_nr = ::NONRESIDUE; 70 | 71 | let z0 = &self.c0.c0; 72 | let z4 = &self.c0.c1; 73 | let z3 = &self.c0.c2; 74 | let z2 = &self.c1.c0; 75 | let z1 = &self.c1.c1; 76 | let z5 = &self.c1.c2; 77 | 78 | // t0 + t1*y = (z0 + z1*y)^2 = a^2 79 | let tmp = z0 * z1; 80 | let t0 = { 81 | let tmp1 = z0 + z1; 82 | let tmp2 = z1 * fp2_nr + z0; 83 | let tmp4 = &tmp * fp2_nr + &tmp; 84 | tmp1 * tmp2 - tmp4 85 | }; 86 | let t1 = tmp.double()?; 87 | 88 | // t2 + t3*y = (z2 + z3*y)^2 = b^2 89 | let tmp = z2 * z3; 90 | let t2 = { 91 | // (z2 + &z3) * &(z2 + &(fp2_nr * &z3)) - &tmp - &(tmp * &fp2_nr); 92 | let tmp1 = z2 + z3; 93 | let tmp2 = z3 * fp2_nr + z2; 94 | let tmp4 = &tmp * fp2_nr + &tmp; 95 | tmp1 * tmp2 - tmp4 96 | }; 97 | let t3 = tmp.double()?; 98 | 99 | // t4 + t5*y = (z4 + z5*y)^2 = c^2 100 | let tmp = z4 * z5; 101 | let t4 = { 102 | // (z4 + &z5) * &(z4 + &(fp2_nr * &z5)) - &tmp - &(tmp * &fp2_nr); 103 | let tmp1 = z4 + z5; 104 | let tmp2 = (z5 * fp2_nr) + z4; 105 | let tmp4 = (&tmp * fp2_nr) + &tmp; 106 | (tmp1 * tmp2) - tmp4 107 | }; 108 | let t5 = tmp.double()?; 109 | 110 | // for A 111 | 112 | // z0 = 3 * t0 - 2 * z0 113 | let c0_c0 = (&t0 - z0).double()? + &t0; 114 | 115 | // z1 = 3 * t1 + 2 * z1 116 | let c1_c1 = (&t1 + z1).double()? + &t1; 117 | 118 | // for B 119 | 120 | // z2 = 3 * (xi * t5) + 2 * z2 121 | let c1_c0 = { 122 | let tmp = &t5 * fp2_nr; 123 | (z2 + &tmp).double()? + &tmp 124 | }; 125 | 126 | // z3 = 3 * t4 - 2 * z3 127 | let c0_c2 = (&t4 - z3).double()? + &t4; 128 | 129 | // for C 130 | 131 | // z4 = 3 * t2 - 2 * z4 132 | let c0_c1 = (&t2 - z4).double()? + &t2; 133 | 134 | // z5 = 3 * t3 + 2 * z5 135 | let c1_c2 = (&t3 + z5).double()? + &t3; 136 | let c0 = Fp6Var::new(c0_c0, c0_c1, c0_c2); 137 | let c1 = Fp6Var::new(c1_c0, c1_c1, c1_c2); 138 | 139 | Ok(Self::new(c0, c1)) 140 | } else { 141 | self.square() 142 | } 143 | } 144 | 145 | /// Like `Self::cyclotomic_exp`, but additionally uses cyclotomic squaring. 146 | pub fn optimized_cyclotomic_exp( 147 | &self, 148 | exponent: impl AsRef<[u64]>, 149 | ) -> Result { 150 | use ark_ff::biginteger::arithmetic::find_naf; 151 | let mut res = Self::one(); 152 | let self_inverse = self.unitary_inverse()?; 153 | 154 | let mut found_nonzero = false; 155 | let naf = find_naf(exponent.as_ref()); 156 | 157 | for &value in naf.iter().rev() { 158 | if found_nonzero { 159 | res = res.cyclotomic_square()?; 160 | } 161 | 162 | if value != 0 { 163 | found_nonzero = true; 164 | 165 | if value > 0 { 166 | res *= self; 167 | } else { 168 | res *= &self_inverse; 169 | } 170 | } 171 | } 172 | 173 | Ok(res) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/fields/fp2.rs: -------------------------------------------------------------------------------- 1 | use crate::fields::{fp::FpVar, quadratic_extension::*}; 2 | use ark_ff::fields::{Fp2Config, Fp2ConfigWrapper, QuadExtConfig}; 3 | 4 | /// A quadratic extension field constructed over a prime field. 5 | /// This is the R1CS equivalent of `ark_ff::Fp2

`. 6 | pub type Fp2Var

= QuadExtVar::Fp>, Fp2ConfigWrapper

>; 7 | 8 | impl QuadExtVarConfig> for Fp2ConfigWrapper

{ 9 | fn mul_base_field_var_by_frob_coeff(fe: &mut FpVar, power: usize) { 10 | *fe *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD]; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/fields/fp3.rs: -------------------------------------------------------------------------------- 1 | use crate::fields::{cubic_extension::*, fp::FpVar}; 2 | use ark_ff::{ 3 | fields::{CubicExtConfig, Fp3ConfigWrapper}, 4 | Fp3Config, 5 | }; 6 | 7 | /// A cubic extension field constructed over a prime field. 8 | /// This is the R1CS equivalent of `ark_ff::Fp3

`. 9 | pub type Fp3Var

= CubicExtVar::Fp>, Fp3ConfigWrapper

>; 10 | 11 | impl CubicExtVarConfig> for Fp3ConfigWrapper

{ 12 | fn mul_base_field_vars_by_frob_coeff( 13 | c1: &mut FpVar, 14 | c2: &mut FpVar, 15 | power: usize, 16 | ) { 17 | *c1 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD]; 18 | *c2 *= Self::FROBENIUS_COEFF_C2[power % Self::DEGREE_OVER_BASE_PRIME_FIELD]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/fields/fp4.rs: -------------------------------------------------------------------------------- 1 | use crate::fields::{fp2::Fp2Var, quadratic_extension::*}; 2 | use ark_ff::{ 3 | fields::{Fp4ConfigWrapper, QuadExtConfig}, 4 | Fp4Config, 5 | }; 6 | 7 | /// A quartic extension field constructed as the tower of a 8 | /// quadratic extension over a quadratic extension field. 9 | /// This is the R1CS equivalent of `ark_ff::Fp4

`. 10 | pub type Fp4Var

= QuadExtVar::Fp2Config>, Fp4ConfigWrapper

>; 11 | 12 | impl QuadExtVarConfig> for Fp4ConfigWrapper

{ 13 | fn mul_base_field_var_by_frob_coeff(fe: &mut Fp2Var, power: usize) { 14 | fe.c0 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD]; 15 | fe.c1 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/fields/fp6_2over3.rs: -------------------------------------------------------------------------------- 1 | use crate::fields::{fp3::Fp3Var, quadratic_extension::*}; 2 | use ark_ff::{fields::fp6_2over3::*, QuadExtConfig}; 3 | 4 | /// A sextic extension field constructed as the tower of a 5 | /// quadratic extension over a cubic extension field. 6 | /// This is the R1CS equivalent of `ark_ff::fp6_2over3::Fp6

`. 7 | pub type Fp6Var

= QuadExtVar::Fp3Config>, Fp6ConfigWrapper

>; 8 | 9 | impl QuadExtVarConfig> for Fp6ConfigWrapper

{ 10 | fn mul_base_field_var_by_frob_coeff(fe: &mut Fp3Var, power: usize) { 11 | fe.c0 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD]; 12 | fe.c1 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD]; 13 | fe.c2 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD]; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/fields/fp6_3over2.rs: -------------------------------------------------------------------------------- 1 | use crate::fields::{cubic_extension::*, fp2::*}; 2 | use ark_ff::{ 3 | fields::{fp6_3over2::*, Fp2}, 4 | CubicExtConfig, 5 | }; 6 | use ark_relations::gr1cs::SynthesisError; 7 | use ark_std::ops::MulAssign; 8 | 9 | /// A sextic extension field constructed as the tower of a 10 | /// cubic extension over a quadratic extension field. 11 | /// This is the R1CS equivalent of `ark_ff::fp6_3over3::Fp6

`. 12 | pub type Fp6Var

= CubicExtVar::Fp2Config>, Fp6ConfigWrapper

>; 13 | 14 | impl CubicExtVarConfig> for Fp6ConfigWrapper

{ 15 | fn mul_base_field_vars_by_frob_coeff( 16 | c1: &mut Fp2Var, 17 | c2: &mut Fp2Var, 18 | power: usize, 19 | ) { 20 | *c1 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD]; 21 | *c2 *= Self::FROBENIUS_COEFF_C2[power % Self::DEGREE_OVER_BASE_PRIME_FIELD]; 22 | } 23 | } 24 | 25 | impl Fp6Var

{ 26 | /// Multiplies `self` by a sparse element which has `c0 == c2 == zero`. 27 | pub fn mul_by_0_c1_0(&self, c1: &Fp2Var) -> Result { 28 | // Karatsuba multiplication 29 | // v0 = a0 * b0 = 0 30 | 31 | // v1 = a1 * b1 32 | let v1 = &self.c1 * c1; 33 | 34 | // v2 = a2 * b2 = 0 35 | 36 | let a1_plus_a2 = &self.c1 + &self.c2; 37 | let b1_plus_b2 = c1.clone(); 38 | 39 | let a0_plus_a1 = &self.c0 + &self.c1; 40 | 41 | // c0 = (NONRESIDUE * ((a1 + a2)*(b1 + b2) - v1 - v2)) + v0 42 | // = NONRESIDUE * ((a1 + a2) * b1 - v1) 43 | let c0 = &(a1_plus_a2 * &b1_plus_b2 - &v1) * P::NONRESIDUE; 44 | 45 | // c1 = (a0 + a1) * (b0 + b1) - v0 - v1 + NONRESIDUE * v2 46 | // = (a0 + a1) * b1 - v1 47 | let c1 = a0_plus_a1 * c1 - &v1; 48 | // c2 = (a0 + a2) * (b0 + b2) - v0 - v2 + v1 49 | // = v1 50 | let c2 = v1; 51 | Ok(Self::new(c0, c1, c2)) 52 | } 53 | 54 | /// Multiplies `self` by a sparse element which has `c2 == zero`. 55 | pub fn mul_by_c0_c1_0( 56 | &self, 57 | c0: &Fp2Var, 58 | c1: &Fp2Var, 59 | ) -> Result { 60 | let v0 = &self.c0 * c0; 61 | let v1 = &self.c1 * c1; 62 | // v2 = 0. 63 | 64 | let a1_plus_a2 = &self.c1 + &self.c2; 65 | let a0_plus_a1 = &self.c0 + &self.c1; 66 | let a0_plus_a2 = &self.c0 + &self.c2; 67 | 68 | let b1_plus_b2 = c1.clone(); 69 | let b0_plus_b1 = c0 + c1; 70 | let b0_plus_b2 = c0.clone(); 71 | 72 | let c0 = (&a1_plus_a2 * &b1_plus_b2 - &v1) * P::NONRESIDUE + &v0; 73 | 74 | let c1 = a0_plus_a1 * &b0_plus_b1 - &v0 - &v1; 75 | 76 | let c2 = a0_plus_a2 * &b0_plus_b2 - &v0 + &v1; 77 | 78 | Ok(Self::new(c0, c1, c2)) 79 | } 80 | } 81 | 82 | impl MulAssign> for Fp6Var

{ 83 | fn mul_assign(&mut self, other: Fp2) { 84 | self.c0 *= other; 85 | self.c1 *= other; 86 | self.c2 *= other; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/gr1cs_var.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::Field; 2 | use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; 3 | use ark_std::vec::Vec; 4 | 5 | /// This trait describes some core functionality that is common to high-level 6 | /// variables, such as `Boolean`s, `FieldVar`s, `GroupVar`s, etc. 7 | pub trait GR1CSVar { 8 | /// The type of the "native" value that `Self` represents in the constraint 9 | /// system. 10 | type Value: core::fmt::Debug + Eq + Clone; 11 | 12 | /// Returns the underlying `ConstraintSystemRef`. 13 | /// 14 | /// If `self` is a constant value, then this *must* return 15 | /// `ark_relations::gr1cs::ConstraintSystemRef::None`. 16 | fn cs(&self) -> ConstraintSystemRef; 17 | 18 | /// Returns `true` if `self` is a circuit-generation-time constant. 19 | fn is_constant(&self) -> bool { 20 | self.cs().is_none() 21 | } 22 | 23 | /// Returns the value that is assigned to `self` in the underlying 24 | /// `ConstraintSystem`. 25 | fn value(&self) -> Result; 26 | } 27 | 28 | impl> GR1CSVar for [T] { 29 | type Value = Vec; 30 | 31 | fn cs(&self) -> ConstraintSystemRef { 32 | let mut result = ConstraintSystemRef::None; 33 | for var in self { 34 | result = var.cs().or(result); 35 | } 36 | result 37 | } 38 | 39 | fn value(&self) -> Result { 40 | let mut result = Vec::new(); 41 | for var in self { 42 | result.push(var.value()?); 43 | } 44 | Ok(result) 45 | } 46 | } 47 | 48 | impl<'a, F: Field, T: 'a + GR1CSVar> GR1CSVar for &'a T { 49 | type Value = T::Value; 50 | 51 | fn cs(&self) -> ConstraintSystemRef { 52 | (*self).cs() 53 | } 54 | 55 | fn value(&self) -> Result { 56 | (*self).value() 57 | } 58 | } 59 | 60 | impl, const N: usize> GR1CSVar for [T; N] { 61 | type Value = [T::Value; N]; 62 | 63 | fn cs(&self) -> ConstraintSystemRef { 64 | let mut result = ConstraintSystemRef::None; 65 | for var in self { 66 | result = var.cs().or(result); 67 | } 68 | result 69 | } 70 | 71 | fn value(&self) -> Result { 72 | Ok(core::array::from_fn(|i| self[i].value().unwrap())) 73 | } 74 | } 75 | 76 | impl GR1CSVar for () { 77 | type Value = (); 78 | 79 | fn cs(&self) -> ConstraintSystemRef { 80 | ConstraintSystemRef::None 81 | } 82 | 83 | fn value(&self) -> Result { 84 | Ok(()) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/groups/curves/mod.rs: -------------------------------------------------------------------------------- 1 | /// This module generically implements arithmetic for Short 2 | /// Weierstrass elliptic curves by following the complete formulae of 3 | /// [[Renes, Costello, Batina 2015]](https://eprint.iacr.org/2015/1060). 4 | pub mod short_weierstrass; 5 | 6 | /// This module generically implements arithmetic for Twisted 7 | /// Edwards elliptic curves by following the complete formulae described in the 8 | /// [EFD](https://www.hyperelliptic.org/EFD/g1p/auto-twisted.html). 9 | pub mod twisted_edwards; 10 | -------------------------------------------------------------------------------- /src/groups/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | convert::{ToBitsGadget, ToBytesGadget, ToConstraintFieldGadget}, 3 | fields::emulated_fp::EmulatedFpVar, 4 | prelude::*, 5 | }; 6 | use ark_ff::PrimeField; 7 | use ark_relations::gr1cs::{Namespace, SynthesisError}; 8 | use core::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}; 9 | 10 | use ark_ec::CurveGroup; 11 | use core::{borrow::Borrow, fmt::Debug}; 12 | 13 | /// This module contains implementations of arithmetic for various curve models. 14 | pub mod curves; 15 | 16 | pub use self::curves::short_weierstrass::{bls12, mnt4, mnt6}; 17 | 18 | /// A hack used to work around the lack of implied bounds. 19 | pub trait GroupOpsBounds<'a, G, T: 'a>: 20 | Sized 21 | + Add<&'a T, Output = T> 22 | + Sub<&'a T, Output = T> 23 | + Add 24 | + Sub 25 | + Add 26 | + Sub 27 | { 28 | } 29 | 30 | /// A variable that represents a curve point for 31 | /// the curve `C`. 32 | pub trait CurveVar: 33 | 'static 34 | + Sized 35 | + Clone 36 | + Debug 37 | + GR1CSVar 38 | + ToBitsGadget 39 | + ToBytesGadget 40 | + EqGadget 41 | + CondSelectGadget 42 | + AllocVar 43 | + AllocVar 44 | + ToConstraintFieldGadget 45 | + for<'a> GroupOpsBounds<'a, C, Self> 46 | + for<'a> AddAssign<&'a Self> 47 | + for<'a> SubAssign<&'a Self> 48 | + AddAssign 49 | + SubAssign 50 | + AddAssign 51 | + SubAssign 52 | + Mul, Output = Self> 53 | + for<'a> Mul<&'a EmulatedFpVar, Output = Self> 54 | + MulAssign> 55 | { 56 | /// Returns the constant `F::zero()`. This is the identity 57 | /// of the group. 58 | fn zero() -> Self; 59 | 60 | /// Returns a `Boolean` representing whether `self == Self::zero()`. 61 | #[tracing::instrument(target = "gr1cs")] 62 | fn is_zero(&self) -> Result, SynthesisError> { 63 | self.is_eq(&Self::zero()) 64 | } 65 | 66 | /// Returns a constant with value `v`. 67 | /// 68 | /// This *should not* allocate any variables. 69 | fn constant(other: C) -> Self; 70 | 71 | /// Allocates a variable in the subgroup without checking if it's in the 72 | /// prime-order subgroup. 73 | fn new_variable_omit_prime_order_check( 74 | cs: impl Into>, 75 | f: impl FnOnce() -> Result, 76 | mode: AllocationMode, 77 | ) -> Result; 78 | 79 | /// Enforce that `self` is in the prime-order subgroup. 80 | fn enforce_prime_order(&self) -> Result<(), SynthesisError>; 81 | 82 | /// Computes `self + self`. 83 | #[tracing::instrument(target = "gr1cs")] 84 | fn double(&self) -> Result { 85 | let mut result = self.clone(); 86 | result.double_in_place()?; 87 | Ok(result) 88 | } 89 | 90 | /// Sets `self = self + self`. 91 | fn double_in_place(&mut self) -> Result<(), SynthesisError>; 92 | 93 | /// Coputes `-self`. 94 | fn negate(&self) -> Result; 95 | 96 | /// Computes `bits * self`, where `bits` is a little-endian 97 | /// `Boolean` representation of a scalar. 98 | #[tracing::instrument(target = "gr1cs", skip(bits))] 99 | fn scalar_mul_le<'a>( 100 | &self, 101 | bits: impl Iterator>, 102 | ) -> Result { 103 | // TODO: in the constant case we should call precomputed_scalar_mul_le, 104 | // but rn there's a bug when doing this with TE curves. 105 | 106 | // Computes the standard little-endian double-and-add algorithm 107 | // (Algorithm 3.26, Guide to Elliptic Curve Cryptography) 108 | let mut res = Self::zero(); 109 | let mut multiple = self.clone(); 110 | for bit in bits { 111 | let tmp = res.clone() + &multiple; 112 | res = bit.select(&tmp, &res)?; 113 | multiple.double_in_place()?; 114 | } 115 | Ok(res) 116 | } 117 | 118 | /// Computes a `I * self` in place, where `I` is a `Boolean` *little-endian* 119 | /// representation of the scalar. 120 | /// 121 | /// The bases are precomputed power-of-two multiples of a single 122 | /// base. 123 | #[tracing::instrument(target = "gr1cs", skip(scalar_bits_with_bases))] 124 | fn precomputed_base_scalar_mul_le<'a, I, B>( 125 | &mut self, 126 | scalar_bits_with_bases: I, 127 | ) -> Result<(), SynthesisError> 128 | where 129 | I: Iterator, 130 | B: Borrow>, 131 | C: 'a, 132 | { 133 | // Computes the standard little-endian double-and-add algorithm 134 | // (Algorithm 3.26, Guide to Elliptic Curve Cryptography) 135 | 136 | // Let `original` be the initial value of `self`. 137 | let mut result = Self::zero(); 138 | for (bit, base) in scalar_bits_with_bases { 139 | // Compute `self + 2^i * original` 140 | let self_plus_base = result.clone() + *base; 141 | // If `bit == 1`, set self = self + 2^i * original; 142 | // else, set self = self; 143 | result = bit.borrow().select(&self_plus_base, &result)?; 144 | } 145 | *self += result; 146 | Ok(()) 147 | } 148 | 149 | /// Computes `Σⱼ(scalarⱼ * baseⱼ)` for all j, 150 | /// where `scalarⱼ` is a `Boolean` *little-endian* 151 | /// representation of the j-th scalar. 152 | #[tracing::instrument(target = "gr1cs", skip(bases, scalars))] 153 | fn precomputed_base_multiscalar_mul_le<'a, T, I, B>( 154 | bases: &[B], 155 | scalars: I, 156 | ) -> Result 157 | where 158 | T: 'a + ToBitsGadget + ?Sized, 159 | I: Iterator, 160 | B: Borrow<[C]>, 161 | { 162 | let mut result = Self::zero(); 163 | // Compute Σᵢ(bitᵢ * baseᵢ) for all i. 164 | for (bits, bases) in scalars.zip(bases) { 165 | let bases = bases.borrow(); 166 | let bits = bits.to_bits_le()?; 167 | result.precomputed_base_scalar_mul_le(bits.iter().zip(bases))?; 168 | } 169 | Ok(result) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | //! This crate implements common "gadgets" that make 3 | //! programming rank-1 constraint systems easier. 4 | #![warn( 5 | unused, 6 | future_incompatible, 7 | nonstandard_style, 8 | rust_2018_idioms, 9 | missing_docs 10 | )] 11 | #![allow(deprecated)] 12 | #![allow(clippy::op_ref)] 13 | 14 | #[macro_use] 15 | extern crate ark_std; 16 | 17 | #[macro_use] 18 | extern crate ark_ff; 19 | 20 | #[macro_use] 21 | extern crate ark_relations; 22 | 23 | /// Some utility macros for making downstream impls easier. 24 | #[macro_use] 25 | pub mod macros; 26 | 27 | pub(crate) use ark_std::vec::Vec; 28 | 29 | #[doc(hidden)] 30 | pub mod gr1cs_var; 31 | pub use gr1cs_var::*; 32 | 33 | /// This module contains `Boolean`, an R1CS equivalent of the `bool` type. 34 | pub mod boolean; 35 | 36 | /// Finite field arithmetic. 37 | pub mod fields; 38 | 39 | /// Implementations of elliptic curve group arithmetic for popular curve models. 40 | pub mod groups; 41 | 42 | /// Gadgets for computing pairings in bilinear groups. 43 | pub mod pairing; 44 | 45 | /// Utilities for allocating new variables in a constraint system. 46 | pub mod alloc; 47 | 48 | /// Utilities for comparing variables. 49 | pub mod cmp; 50 | 51 | /// Utilities for converting variables to other kinds of variables. 52 | pub mod convert; 53 | 54 | /// Utilities for checking equality of variables. 55 | pub mod eq; 56 | 57 | /// Definitions of polynomial variables over finite fields. 58 | pub mod poly; 59 | 60 | /// Contains traits for conditionally selecting a variable from a 61 | /// list of variables. 62 | pub mod select; 63 | 64 | #[cfg(test)] 65 | pub(crate) mod test_utils; 66 | 67 | /// This module contains `UInt8`, a R1CS equivalent of the `u8` type. 68 | pub mod uint8; 69 | /// This module contains a macro for generating `UIntN` types, which are R1CS 70 | /// equivalents of `N`-bit unsigned integers. 71 | #[macro_use] 72 | pub mod uint; 73 | 74 | /// This module contains `UInt16`, a R1CS equivalent of the `u16` type. 75 | pub mod uint16 { 76 | /// A `UInt16` is a variable that represents a 16-bit unsigned integer in 77 | /// R1CS. 78 | pub type UInt16 = super::uint::UInt<16, u16, F>; 79 | } 80 | /// This module contains `UInt32`, a R1CS equivalent of the `u32` type. 81 | pub mod uint32 { 82 | /// A `UInt32` is a variable that represents a 32-bit unsigned integer in 83 | /// R1CS. 84 | pub type UInt32 = super::uint::UInt<32, u32, F>; 85 | } 86 | /// This module contains `UInt64`, a R1CS equivalent of the `u64` type. 87 | pub mod uint64 { 88 | /// A `UInt64` is a variable that represents a 64-bit unsigned integer in 89 | /// R1CS. 90 | pub type UInt64 = super::uint::UInt<64, u64, F>; 91 | } 92 | /// This module contains `UInt128`, a R1CS equivalent of the `u128` type. 93 | pub mod uint128 { 94 | /// A `UInt128` is a variable that represents a 128-bit unsigned integer in 95 | /// R1CS. 96 | pub type UInt128 = super::uint::UInt<128, u128, F>; 97 | } 98 | 99 | #[allow(missing_docs)] 100 | pub mod prelude { 101 | pub use crate::{ 102 | alloc::*, 103 | boolean::Boolean, 104 | convert::{ToBitsGadget, ToBytesGadget}, 105 | eq::*, 106 | fields::{FieldOpsBounds, FieldVar}, 107 | groups::{CurveVar, GroupOpsBounds}, 108 | pairing::PairingVar, 109 | select::*, 110 | uint128::UInt128, 111 | uint16::UInt16, 112 | uint32::UInt32, 113 | uint64::UInt64, 114 | uint8::UInt8, 115 | GR1CSVar, 116 | }; 117 | } 118 | 119 | /// A utility trait to convert `Self` to `Result 120 | pub trait Assignment { 121 | /// Converts `self` to `Result`. 122 | fn get(self) -> Result; 123 | } 124 | 125 | impl Assignment for Option { 126 | fn get(self) -> Result { 127 | self.ok_or(ark_relations::gr1cs::SynthesisError::AssignmentMissing) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/pairing/bls12/mod.rs: -------------------------------------------------------------------------------- 1 | use ark_relations::gr1cs::SynthesisError; 2 | 3 | use super::PairingVar as PG; 4 | 5 | use crate::{ 6 | fields::{fp::FpVar, fp12::Fp12Var, fp2::Fp2Var, FieldVar}, 7 | groups::bls12::{G1AffineVar, G1PreparedVar, G1Var, G2PreparedVar, G2Var}, 8 | }; 9 | use ark_ec::bls12::{Bls12, Bls12Config, TwistType}; 10 | use ark_ff::BitIteratorBE; 11 | use ark_std::marker::PhantomData; 12 | 13 | /// Specifies the constraints for computing a pairing in a BLS12 bilinear group. 14 | pub struct PairingVar(PhantomData

); 15 | 16 | type Fp2V

= Fp2Var<

::Fp2Config>; 17 | 18 | impl PairingVar

{ 19 | // Evaluate the line function at point p. 20 | #[tracing::instrument(target = "gr1cs")] 21 | fn ell( 22 | f: &mut Fp12Var, 23 | coeffs: &(Fp2V

, Fp2V

), 24 | p: &G1AffineVar

, 25 | ) -> Result<(), SynthesisError> { 26 | let zero = FpVar::::zero(); 27 | 28 | match P::TWIST_TYPE { 29 | TwistType::M => { 30 | let c0 = coeffs.0.clone(); 31 | let mut c1 = coeffs.1.clone(); 32 | let c2 = Fp2V::

::new(p.y.clone(), zero); 33 | 34 | c1.c0 *= &p.x; 35 | c1.c1 *= &p.x; 36 | *f = f.mul_by_014(&c0, &c1, &c2)?; 37 | Ok(()) 38 | }, 39 | TwistType::D => { 40 | let c0 = Fp2V::

::new(p.y.clone(), zero); 41 | let mut c1 = coeffs.0.clone(); 42 | let c2 = coeffs.1.clone(); 43 | 44 | c1.c0 *= &p.x; 45 | c1.c1 *= &p.x; 46 | *f = f.mul_by_034(&c0, &c1, &c2)?; 47 | Ok(()) 48 | }, 49 | } 50 | } 51 | 52 | #[tracing::instrument(target = "gr1cs")] 53 | fn exp_by_x(f: &Fp12Var) -> Result, SynthesisError> { 54 | let mut result = f.optimized_cyclotomic_exp(P::X)?; 55 | if P::X_IS_NEGATIVE { 56 | result = result.unitary_inverse()?; 57 | } 58 | Ok(result) 59 | } 60 | } 61 | 62 | impl PG> for PairingVar

{ 63 | type G1Var = G1Var

; 64 | type G2Var = G2Var

; 65 | type G1PreparedVar = G1PreparedVar

; 66 | type G2PreparedVar = G2PreparedVar

; 67 | type GTVar = Fp12Var; 68 | 69 | #[tracing::instrument(target = "gr1cs")] 70 | fn miller_loop( 71 | ps: &[Self::G1PreparedVar], 72 | qs: &[Self::G2PreparedVar], 73 | ) -> Result { 74 | let mut pairs = vec![]; 75 | for (p, q) in ps.iter().zip(qs.iter()) { 76 | pairs.push((p, q.ell_coeffs.iter())); 77 | } 78 | let mut f = Self::GTVar::one(); 79 | 80 | for i in BitIteratorBE::new(P::X).skip(1) { 81 | f.square_in_place()?; 82 | 83 | for &mut (p, ref mut coeffs) in pairs.iter_mut() { 84 | Self::ell(&mut f, coeffs.next().unwrap(), &p.0)?; 85 | } 86 | 87 | if i { 88 | for &mut (p, ref mut coeffs) in pairs.iter_mut() { 89 | Self::ell(&mut f, &coeffs.next().unwrap(), &p.0)?; 90 | } 91 | } 92 | } 93 | 94 | if P::X_IS_NEGATIVE { 95 | f = f.unitary_inverse()?; 96 | } 97 | 98 | Ok(f) 99 | } 100 | 101 | #[tracing::instrument(target = "gr1cs")] 102 | fn final_exponentiation(f: &Self::GTVar) -> Result { 103 | // Computing the final exponentation following 104 | // https://eprint.iacr.org/2016/130.pdf. 105 | // We don't use their "faster" formula because it is difficult to make 106 | // it work for curves with odd `P::X`. 107 | // Hence we implement the slower algorithm from Table 1 below. 108 | 109 | let f1 = f.unitary_inverse()?; 110 | 111 | f.inverse().and_then(|mut f2| { 112 | // f2 = f^(-1); 113 | // r = f^(p^6 - 1) 114 | let mut r = f1; 115 | r *= &f2; 116 | 117 | // f2 = f^(p^6 - 1) 118 | f2 = r.clone(); 119 | // r = f^((p^6 - 1)(p^2)) 120 | r.frobenius_map_in_place(2)?; 121 | 122 | // r = f^((p^6 - 1)(p^2) + (p^6 - 1)) 123 | // r = f^((p^6 - 1)(p^2 + 1)) 124 | r *= &f2; 125 | 126 | // Hard part of the final exponentation is below: 127 | // From https://eprint.iacr.org/2016/130.pdf, Table 1 128 | let mut y0 = r.cyclotomic_square()?; 129 | y0 = y0.unitary_inverse()?; 130 | 131 | let mut y5 = Self::exp_by_x(&r)?; 132 | 133 | let mut y1 = y5.cyclotomic_square()?; 134 | let mut y3 = y0 * &y5; 135 | y0 = Self::exp_by_x(&y3)?; 136 | let y2 = Self::exp_by_x(&y0)?; 137 | let mut y4 = Self::exp_by_x(&y2)?; 138 | y4 *= &y1; 139 | y1 = Self::exp_by_x(&y4)?; 140 | y3 = y3.unitary_inverse()?; 141 | y1 *= &y3; 142 | y1 *= &r; 143 | y3 = r.clone(); 144 | y3 = y3.unitary_inverse()?; 145 | y0 *= &r; 146 | y0.frobenius_map_in_place(3)?; 147 | y4 *= &y3; 148 | y4.frobenius_map_in_place(1)?; 149 | y5 *= &y2; 150 | y5.frobenius_map_in_place(2)?; 151 | y5 *= &y0; 152 | y5 *= &y4; 153 | y5 *= &y1; 154 | Ok(y5) 155 | }) 156 | } 157 | 158 | #[tracing::instrument(target = "gr1cs")] 159 | fn prepare_g1(p: &Self::G1Var) -> Result { 160 | Self::G1PreparedVar::from_group_var(p) 161 | } 162 | 163 | #[tracing::instrument(target = "gr1cs")] 164 | fn prepare_g2(q: &Self::G2Var) -> Result { 165 | Self::G2PreparedVar::from_group_var(q) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/pairing/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use ark_ec::pairing::Pairing; 3 | use ark_relations::gr1cs::SynthesisError; 4 | use core::fmt::Debug; 5 | 6 | /// This module implements pairings for BLS12 bilinear groups. 7 | pub mod bls12; 8 | /// This module implements pairings for MNT4 bilinear groups. 9 | pub mod mnt4; 10 | /// This module implements pairings for MNT6 bilinear groups. 11 | pub mod mnt6; 12 | 13 | type BasePrimeField = <::BaseField as ark_ff::Field>::BasePrimeField; 14 | 15 | /// Specifies the constraints for computing a pairing in the yybilinear group 16 | /// `E`. 17 | pub trait PairingVar { 18 | /// An variable representing an element of `G1`. 19 | /// This is the R1CS equivalent of `E::G1Projective`. 20 | type G1Var: CurveVar>; 21 | 22 | /// An variable representing an element of `G2`. 23 | /// This is the R1CS equivalent of `E::G2Projective`. 24 | type G2Var: CurveVar>; 25 | 26 | /// An variable representing an element of `GT`. 27 | /// This is the R1CS equivalent of `E::GT`. 28 | type GTVar: FieldVar>; 29 | 30 | /// An variable representing cached precomputation that can speed up 31 | /// pairings computations. This is the R1CS equivalent of 32 | /// `E::G1Prepared`. 33 | type G1PreparedVar: ToBytesGadget> 34 | + AllocVar> 35 | + Clone 36 | + Debug; 37 | /// An variable representing cached precomputation that can speed up 38 | /// pairings computations. This is the R1CS equivalent of 39 | /// `E::G2Prepared`. 40 | type G2PreparedVar: ToBytesGadget> 41 | + AllocVar> 42 | + Clone 43 | + Debug; 44 | 45 | /// Computes a multi-miller loop between elements 46 | /// of `p` and `q`. 47 | fn miller_loop( 48 | p: &[Self::G1PreparedVar], 49 | q: &[Self::G2PreparedVar], 50 | ) -> Result; 51 | 52 | /// Computes a final exponentiation over `p`. 53 | fn final_exponentiation(p: &Self::GTVar) -> Result; 54 | 55 | /// Computes a pairing over `p` and `q`. 56 | #[tracing::instrument(target = "gr1cs")] 57 | fn pairing( 58 | p: Self::G1PreparedVar, 59 | q: Self::G2PreparedVar, 60 | ) -> Result { 61 | let tmp = Self::miller_loop(&[p], &[q])?; 62 | Self::final_exponentiation(&tmp) 63 | } 64 | 65 | /// Computes a product of pairings over the elements in `p` and `q`. 66 | #[must_use] 67 | #[tracing::instrument(target = "gr1cs")] 68 | fn product_of_pairings( 69 | p: &[Self::G1PreparedVar], 70 | q: &[Self::G2PreparedVar], 71 | ) -> Result { 72 | let miller_result = Self::miller_loop(p, q)?; 73 | Self::final_exponentiation(&miller_result) 74 | } 75 | 76 | /// Performs the precomputation to generate `Self::G1PreparedVar`. 77 | fn prepare_g1(q: &Self::G1Var) -> Result; 78 | 79 | /// Performs the precomputation to generate `Self::G2PreparedVar`. 80 | fn prepare_g2(q: &Self::G2Var) -> Result; 81 | } 82 | -------------------------------------------------------------------------------- /src/poly/domain/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | boolean::Boolean, 3 | eq::EqGadget, 4 | fields::{fp::FpVar, FieldVar}, 5 | }; 6 | use ark_ff::PrimeField; 7 | use ark_relations::gr1cs::SynthesisError; 8 | use ark_std::vec::Vec; 9 | 10 | /// The vanishing polynomial for a given domain. 11 | pub mod vanishing_poly; 12 | 13 | #[derive(Clone, Debug)] 14 | /// Defines an evaluation domain over a prime field. The domain is a coset of 15 | /// size `1< { 21 | /// generator of subgroup g 22 | pub gen: F, 23 | /// index of the quotient group (i.e. the `offset`) 24 | offset: FpVar, 25 | /// dimension of evaluation domain, which is log2(size of coset) 26 | pub dim: u64, 27 | } 28 | impl Radix2DomainVar { 29 | /// Construct an evaluation domain with the given offset. 30 | pub fn new(gen: F, dimension: u64, offset: FpVar) -> Result { 31 | offset.enforce_not_equal(&FpVar::zero())?; 32 | Ok(Self { 33 | gen, 34 | offset, 35 | dim: dimension, 36 | }) 37 | } 38 | 39 | /// What is the offset of `self`? 40 | pub fn offset(&self) -> &FpVar { 41 | &self.offset 42 | } 43 | } 44 | 45 | impl EqGadget for Radix2DomainVar { 46 | fn is_eq(&self, other: &Self) -> Result, SynthesisError> { 47 | if self.gen != other.gen || self.dim != other.dim { 48 | Ok(Boolean::FALSE) 49 | } else { 50 | self.offset.is_eq(&other.offset) 51 | } 52 | } 53 | } 54 | 55 | impl Radix2DomainVar { 56 | /// order of the domain 57 | pub fn order(&self) -> usize { 58 | 1 << self.dim 59 | } 60 | 61 | /// Returns offset, offset*g, offset*g^2, ..., offset*g^{coset_size} 62 | pub fn elements(&self) -> Vec> { 63 | let mut result = Vec::new(); 64 | result.push(self.offset.clone()); 65 | for _ in 1..(1 << self.dim) { 66 | let new_element = result.last().unwrap() * self.gen; 67 | result.push(new_element); 68 | } 69 | result 70 | } 71 | 72 | /// Size of the domain 73 | pub fn size(&self) -> u64 { 74 | 1 << self.dim 75 | } 76 | 77 | /// For domain `h` with dimension `n`, `position` represented by 78 | /// `query_pos` in big endian form, returns all points of 79 | /// `h*g^{position}`. The result is the query coset at 80 | /// index `query_pos` for the FRI protocol. 81 | /// 82 | /// # Panics 83 | /// This function panics when `query_pos.len() != coset_dim` or 84 | /// `query_pos.len() != self.dim`. 85 | pub fn query_position_to_coset_elements( 86 | &self, 87 | query_pos: &[Boolean], 88 | coset_dim: u64, 89 | ) -> Result>, SynthesisError> { 90 | Ok(self 91 | .query_position_to_coset(query_pos, coset_dim)? 92 | .elements()) 93 | } 94 | 95 | /// For domain `h` with dimension `n`, `position` represented by 96 | /// `query_pos` in big endian form, returns all points of 97 | /// `h*g^{position}` 98 | /// 99 | /// # Panics 100 | /// This function panics when `query_pos.len() < log2_num_cosets`. 101 | pub fn query_position_to_coset( 102 | &self, 103 | query_pos: &[Boolean], 104 | coset_dim: u64, 105 | ) -> Result { 106 | let coset_index = truncate_to_coset_index(query_pos, self.dim, coset_dim); 107 | let offset_var = &self.offset * FpVar::Constant(self.gen).pow_le(&coset_index)?; 108 | Ok(Self { 109 | gen: self.gen.pow(&[1 << (self.dim - coset_dim)]), // distance between coset 110 | offset: offset_var, 111 | dim: coset_dim, 112 | }) 113 | } 114 | } 115 | 116 | fn truncate_to_coset_index( 117 | query_pos: &[Boolean], 118 | codeword_dim: u64, 119 | coset_dim: u64, 120 | ) -> Vec> { 121 | let log2_num_cosets = (codeword_dim - coset_dim) as usize; 122 | assert!(query_pos.len() >= log2_num_cosets); 123 | query_pos[0..log2_num_cosets].to_vec() 124 | } 125 | 126 | #[cfg(test)] 127 | mod tests { 128 | use crate::prelude::*; 129 | use ark_ff::PrimeField; 130 | use ark_relations::gr1cs::ConstraintSystem; 131 | use ark_std::{rand::Rng, test_rng}; 132 | 133 | use crate::{ 134 | alloc::AllocVar, convert::ToBitsGadget, fields::fp::FpVar, poly::domain::Radix2DomainVar, 135 | GR1CSVar, 136 | }; 137 | 138 | fn test_query_coset_template() { 139 | const COSET_DIM: u64 = 7; 140 | const COSET_SIZE: u64 = 1 << COSET_DIM; 141 | const LOCALIZATION: u64 = 3; 142 | let cs = ConstraintSystem::new_ref(); 143 | let mut rng = test_rng(); 144 | let gen = F::get_root_of_unity(COSET_SIZE).unwrap(); 145 | let offset = F::rand(&mut rng); 146 | let offset_var = FpVar::new_witness(cs.clone(), || Ok(offset)).unwrap(); 147 | let domain = Radix2DomainVar::new(gen, COSET_DIM, offset_var).unwrap(); 148 | 149 | let num_cosets = 1 << (COSET_DIM - LOCALIZATION); 150 | 151 | let coset_index = rng.gen_range(0..num_cosets); 152 | println!("{:0b}", coset_index); 153 | let coset_index_var = UInt32::new_witness(cs.clone(), || Ok(coset_index)) 154 | .unwrap() 155 | .to_bits_le() 156 | .unwrap() 157 | .into_iter() 158 | .take(COSET_DIM as usize) 159 | .collect::>(); 160 | 161 | let elements_actual = domain 162 | .query_position_to_coset(&coset_index_var, LOCALIZATION) 163 | .unwrap() 164 | .elements(); 165 | 166 | let elements_expected = domain 167 | .elements() 168 | .into_iter() 169 | .skip(coset_index as usize) 170 | .step_by(1 << (COSET_DIM - LOCALIZATION)) 171 | .collect::>(); 172 | 173 | assert_eq!(elements_expected.len(), elements_actual.len()); 174 | 175 | elements_expected 176 | .into_iter() 177 | .zip(elements_actual.into_iter()) 178 | .for_each(|(left, right)| { 179 | assert_eq!(left.value().unwrap(), right.value().unwrap()); 180 | }); 181 | } 182 | 183 | #[test] 184 | fn test_on_bls12_381() { 185 | test_query_coset_template::(); 186 | } 187 | 188 | #[test] 189 | fn test_on_bls12_377() { 190 | test_query_coset_template::(); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/poly/domain/vanishing_poly.rs: -------------------------------------------------------------------------------- 1 | use crate::fields::{fp::FpVar, FieldVar}; 2 | use ark_ff::{Field, PrimeField}; 3 | use ark_relations::gr1cs::SynthesisError; 4 | use ark_std::ops::Sub; 5 | 6 | /// Struct describing vanishing polynomial for a multiplicative coset H where 7 | /// |H| is a power of 2. As H is a coset, every element can be described as 8 | /// h*g^i and therefore has vanishing polynomial Z_H(x) = x^|H| - h^|H| 9 | #[derive(Clone)] 10 | pub struct VanishingPolynomial { 11 | /// h^|H| 12 | pub constant_term: F, 13 | /// log_2(|H|) 14 | pub dim_h: u64, 15 | /// |H| 16 | pub order_h: u64, 17 | } 18 | 19 | impl VanishingPolynomial { 20 | /// returns a VanishingPolynomial of coset `H = h`. 21 | pub fn new(offset: F, dim_h: u64) -> Self { 22 | let order_h = 1 << dim_h; 23 | let vp = VanishingPolynomial { 24 | constant_term: offset.pow([order_h]), 25 | dim_h, 26 | order_h, 27 | }; 28 | vp 29 | } 30 | 31 | /// Evaluates the vanishing polynomial without generating the constraints. 32 | pub fn evaluate(&self, x: &F) -> F { 33 | let mut result = x.pow([self.order_h]); 34 | result -= &self.constant_term; 35 | result 36 | } 37 | 38 | /// Evaluates the constraints and just gives you the gadget for the result. 39 | /// Caution for use in holographic lincheck: The output has 2 entries in one 40 | /// matrix 41 | pub fn evaluate_constraints(&self, x: &FpVar) -> Result, SynthesisError> { 42 | if self.dim_h == 1 { 43 | let result = x.sub(x); 44 | return Ok(result); 45 | } 46 | 47 | let mut cur = x.square()?; 48 | for _ in 1..self.dim_h { 49 | cur.square_in_place()?; 50 | } 51 | cur -= &FpVar::Constant(self.constant_term); 52 | Ok(cur) 53 | } 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | use crate::{ 59 | alloc::AllocVar, fields::fp::FpVar, poly::domain::vanishing_poly::VanishingPolynomial, 60 | GR1CSVar, 61 | }; 62 | use ark_relations::gr1cs::ConstraintSystem; 63 | use ark_std::{test_rng, UniformRand}; 64 | use ark_test_curves::bls12_381::Fr; 65 | 66 | #[test] 67 | fn constraints_test() { 68 | let mut rng = test_rng(); 69 | let offset = Fr::rand(&mut rng); 70 | let cs = ConstraintSystem::new_ref(); 71 | let x = Fr::rand(&mut rng); 72 | let x_var = FpVar::new_witness(ns!(cs, "x_var"), || Ok(x)).unwrap(); 73 | let vp = VanishingPolynomial::new(offset, 12); 74 | let native = vp.evaluate(&x); 75 | let result_var = vp.evaluate_constraints(&x_var).unwrap(); 76 | assert!(cs.is_satisfied().unwrap()); 77 | assert_eq!(result_var.value().unwrap(), native); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/poly/evaluations/mod.rs: -------------------------------------------------------------------------------- 1 | /// Univariate polynomials in R1CS 2 | pub mod univariate; 3 | -------------------------------------------------------------------------------- /src/poly/evaluations/univariate/lagrange_interpolator.rs: -------------------------------------------------------------------------------- 1 | use crate::poly::domain::vanishing_poly::VanishingPolynomial; 2 | use ark_ff::{batch_inversion_and_mul, PrimeField}; 3 | use ark_std::vec::Vec; 4 | /// Struct describing Lagrange interpolation for a multiplicative coset I, 5 | /// with |I| a power of 2. 6 | /// TODO: Pull in lagrange poly explanation from libiop 7 | #[derive(Clone)] 8 | pub struct LagrangeInterpolator { 9 | pub(crate) domain_order: usize, 10 | pub(crate) all_domain_elems: Vec, 11 | pub(crate) v_inv_elems: Vec, 12 | pub(crate) domain_vp: VanishingPolynomial, 13 | poly_evaluations: Vec, 14 | } 15 | 16 | impl LagrangeInterpolator { 17 | /// Returns a lagrange interpolator, given the domain specification. 18 | pub fn new( 19 | domain_offset: F, 20 | domain_generator: F, 21 | domain_dim: u64, 22 | poly_evaluations: Vec, 23 | ) -> Self { 24 | let domain_order = 1 << domain_dim; 25 | assert_eq!(poly_evaluations.len(), domain_order); 26 | let mut cur_elem = domain_offset; 27 | let mut all_domain_elems = vec![domain_offset]; 28 | let mut v_inv_elems: Vec = Vec::new(); 29 | // Cache all elements in the domain 30 | for _ in 1..domain_order { 31 | cur_elem *= domain_generator; 32 | all_domain_elems.push(cur_elem); 33 | } 34 | // By computing the following elements as constants, 35 | // we can further reduce the interpolation costs. 36 | // 37 | // m = order of the interpolation domain 38 | // v_inv[i] = prod_{j != i} h(g^i - g^j) 39 | // We use the following facts to compute this: 40 | // v_inv[0] = m*h^{m-1} 41 | // v_inv[i] = g^{-1} * v_inv[i-1] 42 | // TODO: Include proof of the above two points 43 | let g_inv = domain_generator.inverse().unwrap(); 44 | let m = F::from((1 << domain_dim) as u128); 45 | let mut v_inv_i = m * domain_offset.pow([(domain_order - 1) as u64]); 46 | for _ in 0..domain_order { 47 | v_inv_elems.push(v_inv_i); 48 | v_inv_i *= g_inv; 49 | } 50 | 51 | // TODO: Cache the intermediate terms with Z_H(x) evaluations. 52 | let vp = VanishingPolynomial::new(domain_offset, domain_dim); 53 | 54 | let lagrange_interpolation: LagrangeInterpolator = LagrangeInterpolator { 55 | domain_order, 56 | all_domain_elems, 57 | v_inv_elems, 58 | domain_vp: vp, 59 | poly_evaluations, 60 | }; 61 | lagrange_interpolation 62 | } 63 | 64 | pub(crate) fn compute_lagrange_coefficients(&self, interpolation_point: F) -> Vec { 65 | // Let t be the interpolation point, H be the multiplicative coset, with 66 | // elements of the form h*g^i. Compute each L_{i,H}(t) as Z_{H}(t) * v_i 67 | // / (t- h g^i) where: 68 | // - Z_{H}(t) = \prod_{j} (t-h*g^j) = (t^m-h^m), and 69 | // - v_{i} = 1 / \prod_{j \neq i} h(g^i-g^j). 70 | // Below we use the fact that v_{0} = 1/(m * h^(m-1)) and v_{i+1} = g * v_{i}. 71 | // We first compute the inverse of each coefficient, except for the Z_H(t) term. 72 | // We then batch invert the entire result, and multiply by Z_H(t). 73 | let mut inverted_lagrange_coeffs: Vec = Vec::with_capacity(self.all_domain_elems.len()); 74 | for i in 0..self.domain_order { 75 | let l = self.v_inv_elems[i]; 76 | let r = self.all_domain_elems[i]; 77 | inverted_lagrange_coeffs.push(l * (interpolation_point - r)); 78 | } 79 | let vp_t = self.domain_vp.evaluate(&interpolation_point); 80 | let lagrange_coeffs = inverted_lagrange_coeffs.as_mut_slice(); 81 | batch_inversion_and_mul::(lagrange_coeffs, &vp_t); 82 | lagrange_coeffs.iter().cloned().collect() 83 | } 84 | 85 | /// Interpolate the polynomial at the given point. 86 | pub fn interpolate(&self, interpolation_point: F) -> F { 87 | let lagrange_coeffs = self.compute_lagrange_coefficients(interpolation_point); 88 | let mut interpolation = F::zero(); 89 | for i in 0..self.domain_order { 90 | interpolation += lagrange_coeffs[i] * self.poly_evaluations[i]; 91 | } 92 | interpolation 93 | } 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use crate::{ 99 | fields::{fp::FpVar, FieldVar}, 100 | poly::{ 101 | domain::Radix2DomainVar, 102 | evaluations::univariate::lagrange_interpolator::LagrangeInterpolator, 103 | }, 104 | GR1CSVar, 105 | }; 106 | use ark_ff::{FftField, Field, One}; 107 | use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; 108 | use ark_std::{test_rng, UniformRand}; 109 | use ark_test_curves::bls12_381::Fr; 110 | 111 | #[test] 112 | pub fn test_native_interpolate() { 113 | let mut rng = test_rng(); 114 | let poly = DensePolynomial::rand(15, &mut rng); 115 | let gen = Fr::get_root_of_unity(1 << 4).unwrap(); 116 | assert_eq!(gen.pow(&[1 << 4]), Fr::one()); 117 | let domain = Radix2DomainVar::new( 118 | gen, 119 | 4, // 2^4 = 16 120 | FpVar::constant(Fr::GENERATOR), 121 | ) 122 | .unwrap(); 123 | // generate evaluations of `poly` on this domain 124 | let mut coset_point = domain.offset().value().unwrap(); 125 | let mut oracle_evals = Vec::new(); 126 | for _ in 0..(1 << 4) { 127 | oracle_evals.push(poly.evaluate(&coset_point)); 128 | coset_point *= gen; 129 | } 130 | 131 | let interpolator = LagrangeInterpolator::new( 132 | domain.offset().value().unwrap(), 133 | domain.gen, 134 | domain.dim, 135 | oracle_evals, 136 | ); 137 | 138 | // the point to evaluate at 139 | let interpolate_point = Fr::rand(&mut rng); 140 | 141 | let expected = poly.evaluate(&interpolate_point); 142 | let actual = interpolator.interpolate(interpolate_point); 143 | 144 | assert_eq!(actual, expected) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/poly/mod.rs: -------------------------------------------------------------------------------- 1 | /// Evaluation domains for polynomials. 2 | pub mod domain; 3 | /// Evaluations of polynomials over domains. 4 | pub mod evaluations; 5 | /// Modules for working with polynomials in coefficient forms. 6 | pub mod polynomial; 7 | -------------------------------------------------------------------------------- /src/poly/polynomial/mod.rs: -------------------------------------------------------------------------------- 1 | /// Module defining data structures for univariate polynomials. 2 | pub mod univariate; 3 | -------------------------------------------------------------------------------- /src/poly/polynomial/univariate/dense.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::PrimeField; 2 | use ark_relations::gr1cs::SynthesisError; 3 | use ark_std::vec::Vec; 4 | 5 | use crate::fields::{fp::FpVar, FieldVar}; 6 | 7 | /// Stores a polynomial in coefficient form, where coeffcient is represented by 8 | /// a list of `Fpvar`. 9 | pub struct DensePolynomialVar { 10 | /// The coefficient of `x^i` is stored at location `i` in `self.coeffs`. 11 | pub coeffs: Vec>, 12 | } 13 | 14 | impl DensePolynomialVar { 15 | /// Constructs a new polynomial from a list of coefficients. 16 | pub fn from_coefficients_slice(coeffs: &[FpVar]) -> Self { 17 | Self::from_coefficients_vec(coeffs.to_vec()) 18 | } 19 | 20 | /// Constructs a new polynomial from a list of coefficients. 21 | pub fn from_coefficients_vec(coeffs: Vec>) -> Self { 22 | Self { coeffs } 23 | } 24 | 25 | /// Evaluates `self` at the given `point` and just gives you the gadget for 26 | /// the result. Caution for use in holographic lincheck: The output has 27 | /// 2 entries in one matrix 28 | pub fn evaluate(&self, point: &FpVar) -> Result, SynthesisError> { 29 | // Horner's Method 30 | Ok(self 31 | .coeffs 32 | .iter() 33 | .rfold(FpVar::zero(), move |acc, coeff| acc * point + coeff)) 34 | } 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use crate::{ 40 | alloc::AllocVar, fields::fp::FpVar, 41 | poly::polynomial::univariate::dense::DensePolynomialVar, GR1CSVar, 42 | }; 43 | use ark_poly::{polynomial::univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; 44 | use ark_relations::gr1cs::ConstraintSystem; 45 | use ark_std::{test_rng, vec::Vec, UniformRand}; 46 | use ark_test_curves::bls12_381::Fr; 47 | 48 | #[test] 49 | fn test_evaluate() { 50 | let mut rng = test_rng(); 51 | for _ in 0..100 { 52 | let cs = ConstraintSystem::new_ref(); 53 | let poly: DensePolynomial = DensePolynomial::rand(10, &mut rng); 54 | let poly_var = { 55 | let coeff: Vec<_> = poly 56 | .coeffs 57 | .iter() 58 | .map(|&x| FpVar::new_witness(ns!(cs, "coeff"), || Ok(x)).unwrap()) 59 | .collect(); 60 | DensePolynomialVar::from_coefficients_vec(coeff) 61 | }; 62 | let point = Fr::rand(&mut rng); 63 | let point_var = FpVar::new_witness(ns!(cs, "point"), || Ok(point)).unwrap(); 64 | 65 | let expected = poly.evaluate(&point); 66 | let actual = poly_var.evaluate(&point_var).unwrap(); 67 | 68 | assert_eq!(actual.value().unwrap(), expected); 69 | assert!(cs.is_satisfied().unwrap()); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/poly/polynomial/univariate/mod.rs: -------------------------------------------------------------------------------- 1 | /// A dense univariate polynomial represented in coefficient form. 2 | pub mod dense; 3 | -------------------------------------------------------------------------------- /src/select.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use ark_ff::Field; 3 | use ark_relations::gr1cs::SynthesisError; 4 | use ark_std::vec::Vec; 5 | /// Generates constraints for selecting between one of two values. 6 | pub trait CondSelectGadget: Sized + Clone { 7 | /// If `cond == &Boolean::TRUE`, then this returns `true_value`; else, 8 | /// returns `false_value`. 9 | /// 10 | /// # Note 11 | /// `Self::conditionally_select(cond, true_value, false_value)?` can be more 12 | /// succinctly written as `cond.select(&true_value, &false_value)?`. 13 | fn conditionally_select( 14 | cond: &Boolean, 15 | true_value: &Self, 16 | false_value: &Self, 17 | ) -> Result; 18 | 19 | /// Returns an element of `values` whose index in represented by `position`. 20 | /// `position` is an array of boolean that represents an unsigned integer in 21 | /// big endian order. 22 | /// 23 | /// # Example 24 | /// To get the 6th element of `values`, convert unsigned integer 6 (`0b110`) 25 | /// to `position = [True, True, False]`, 26 | /// and call `conditionally_select_power_of_two_vector(position, values)`. 27 | fn conditionally_select_power_of_two_vector( 28 | position: &[Boolean], 29 | values: &[Self], 30 | ) -> Result { 31 | let m = values.len(); 32 | let n = position.len(); 33 | 34 | // Assert m is a power of 2, and n = log(m) 35 | assert!(m.is_power_of_two()); 36 | assert_eq!(1 << n, m); 37 | 38 | let mut cur_mux_values = values.to_vec(); 39 | 40 | // Traverse the evaluation tree from bottom to top in level order traversal. 41 | // This is method 5.1 from https://github.com/mir-protocol/r1cs-workshop/blob/master/workshop.pdf 42 | // TODO: Add method 5.2/5.3 43 | for i in 0..n { 44 | // Size of current layer. 45 | let cur_size = 1 << (n - i); 46 | assert_eq!(cur_mux_values.len(), cur_size); 47 | 48 | let mut next_mux_values = Vec::new(); 49 | for j in (0..cur_size).step_by(2) { 50 | let cur = Self::conditionally_select( 51 | &position[n - 1 - i], 52 | // true case 53 | &cur_mux_values[j + 1], 54 | // false case 55 | &cur_mux_values[j], 56 | )?; 57 | next_mux_values.push(cur); 58 | } 59 | cur_mux_values = next_mux_values; 60 | } 61 | 62 | Ok(cur_mux_values[0].clone()) 63 | } 64 | } 65 | 66 | /// Performs a lookup in a 4-element table using two bits. 67 | pub trait TwoBitLookupGadget: Sized { 68 | /// The type of values being looked up. 69 | type TableConstant; 70 | 71 | /// Interprets the slice `bits` as a two-bit integer `b = bits[0] + (bits[1] 72 | /// << 1)`, and then outputs `constants[b]`. 73 | /// 74 | /// For example, if `bits == [0, 1]`, and `constants == [0, 1, 2, 3]`, this 75 | /// method should output a variable corresponding to `2`. 76 | /// 77 | /// # Panics 78 | /// 79 | /// This method panics if `bits.len() != 2` or `constants.len() != 4`. 80 | fn two_bit_lookup( 81 | bits: &[Boolean], 82 | constants: &[Self::TableConstant], 83 | ) -> Result; 84 | } 85 | 86 | /// Uses three bits to perform a lookup into a table, where the last bit 87 | /// conditionally negates the looked-up value. 88 | pub trait ThreeBitCondNegLookupGadget: Sized { 89 | /// The type of values being looked up. 90 | type TableConstant; 91 | 92 | /// Interprets the slice `bits` as a two-bit integer `b = bits[0] + (bits[1] 93 | /// << 1)`, and then outputs `constants[b] * c`, where `c = if bits[2] { 94 | /// -1 } else { 1 };`. 95 | /// 96 | /// That is, `bits[2]` conditionally negates the looked-up value. 97 | /// 98 | /// For example, if `bits == [1, 0, 1]`, and `constants == [0, 1, 2, 3]`, 99 | /// this method should output a variable corresponding to `-1`. 100 | /// 101 | /// # Panics 102 | /// 103 | /// This method panics if `bits.len() != 3` or `constants.len() != 4`. 104 | fn three_bit_cond_neg_lookup( 105 | bits: &[Boolean], 106 | b0b1: &Boolean, 107 | constants: &[Self::TableConstant], 108 | ) -> Result; 109 | } 110 | -------------------------------------------------------------------------------- /src/test_utils.rs: -------------------------------------------------------------------------------- 1 | use core::iter; 2 | 3 | use crate::alloc::AllocationMode; 4 | 5 | pub(crate) fn modes() -> impl Iterator { 6 | use AllocationMode::*; 7 | [Constant, Input, Witness].into_iter() 8 | } 9 | 10 | pub(crate) fn combination( 11 | mut i: impl Iterator, 12 | ) -> impl Iterator { 13 | iter::from_fn(move || i.next().map(|t| modes().map(move |mode| (mode, t.clone())))) 14 | .flat_map(|x| x) 15 | } 16 | -------------------------------------------------------------------------------- /src/uint/add/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::fields::fp::FpVar; 2 | 3 | use super::*; 4 | 5 | mod saturating; 6 | mod wrapping; 7 | 8 | impl UInt { 9 | /// Adds up `operands`, returning the bit decomposition of the result, along 10 | /// with the value of the result. If all the operands are constant, then 11 | /// the bit decomposition is empty, and the value is the constant value 12 | /// of the result. 13 | /// 14 | /// # Panics 15 | /// 16 | /// This method panics if the result of addition could possibly exceed the 17 | /// field size. 18 | #[tracing::instrument(target = "gr1cs", skip(operands, adder))] 19 | fn add_many_helper( 20 | operands: &[Self], 21 | adder: impl Fn(T, T) -> T, 22 | ) -> Result<(Vec>, Option), SynthesisError> { 23 | // Bounds on `N` to avoid overflows 24 | 25 | assert!(operands.len() >= 1); 26 | let max_value_size = N as u32 + ark_std::log2(operands.len()); 27 | assert!(max_value_size <= F::MODULUS_BIT_SIZE); 28 | 29 | if operands.len() == 1 { 30 | return Ok((operands[0].bits.to_vec(), operands[0].value)); 31 | } 32 | 33 | // Compute the value of the result. 34 | let mut value = Some(T::zero()); 35 | for op in operands { 36 | value = value.and_then(|v| Some(adder(v, op.value?))); 37 | } 38 | if operands.is_constant() { 39 | // If all operands are constant, then the result is also constant. 40 | // In this case, we can return early. 41 | return Ok((Vec::new(), value)); 42 | } 43 | 44 | // Compute the full (non-wrapped) sum of the operands. 45 | let result = operands 46 | .iter() 47 | .map(|op| Boolean::le_bits_to_fp(&op.bits).unwrap()) 48 | .sum::>(); 49 | let (result, _) = result.to_bits_le_with_top_bits_zero(max_value_size as usize)?; 50 | Ok((result, value)) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/uint/add/saturating.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::PrimeField; 2 | use ark_relations::gr1cs::SynthesisError; 3 | 4 | use crate::{boolean::Boolean, uint::*, GR1CSVar}; 5 | 6 | impl UInt { 7 | /// Compute `*self = self.wrapping_add(other)`. 8 | pub fn saturating_add_in_place(&mut self, other: &Self) { 9 | let result = Self::saturating_add_many(&[self.clone(), other.clone()]).unwrap(); 10 | *self = result; 11 | } 12 | 13 | /// Compute `self.wrapping_add(other)`. 14 | pub fn saturating_add(&self, other: &Self) -> Self { 15 | let mut result = self.clone(); 16 | result.saturating_add_in_place(other); 17 | result 18 | } 19 | 20 | /// Perform wrapping addition of `operands`. 21 | /// Computes `operands[0].wrapping_add(operands[1]). 22 | /// wrapping_add(operands[2])...`. 23 | /// 24 | /// The user must ensure that overflow does not occur. 25 | #[tracing::instrument(target = "gr1cs", skip(operands))] 26 | pub fn saturating_add_many(operands: &[Self]) -> Result 27 | where 28 | F: PrimeField, 29 | { 30 | let (sum_bits, value) = Self::add_many_helper(operands, |a, b| a.saturating_add(b))?; 31 | if operands.is_constant() { 32 | // If all operands are constant, then the result is also constant. 33 | // In this case, we can return early. 34 | Ok(UInt::constant(value.unwrap())) 35 | } else if sum_bits.len() == N { 36 | // No overflow occurred. 37 | Ok(UInt::from_bits_le(&sum_bits)) 38 | } else { 39 | // Split the sum into the bottom `N` bits and the top bits. 40 | let (bottom_bits, top_bits) = sum_bits.split_at(N); 41 | 42 | // Construct a candidate result assuming that no overflow occurred. 43 | let bits = TryFrom::try_from(bottom_bits.to_vec()).unwrap(); 44 | let candidate_result = UInt { bits, value }; 45 | 46 | // Check if any of the top bits is set. 47 | // If any of them is set, then overflow occurred. 48 | let overflow_occurred = Boolean::kary_or(&top_bits)?; 49 | 50 | // If overflow occurred, return the maximum value. 51 | overflow_occurred.select(&Self::MAX, &candidate_result) 52 | } 53 | } 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | use super::*; 59 | use crate::{ 60 | alloc::{AllocVar, AllocationMode}, 61 | prelude::EqGadget, 62 | uint::test_utils::{run_binary_exhaustive, run_binary_random}, 63 | GR1CSVar, 64 | }; 65 | use ark_ff::PrimeField; 66 | use ark_test_curves::bls12_381::Fr; 67 | 68 | fn uint_saturating_add( 69 | a: UInt, 70 | b: UInt, 71 | ) -> Result<(), SynthesisError> { 72 | let cs = a.cs().or(b.cs()); 73 | let both_constant = a.is_constant() && b.is_constant(); 74 | let computed = a.saturating_add(&b); 75 | let expected_mode = if both_constant { 76 | AllocationMode::Constant 77 | } else { 78 | AllocationMode::Witness 79 | }; 80 | let expected = UInt::new_variable( 81 | cs.clone(), 82 | || Ok(a.value()?.saturating_add(b.value()?)), 83 | expected_mode, 84 | )?; 85 | assert_eq!(expected.value(), computed.value()); 86 | expected.enforce_equal(&computed)?; 87 | if !both_constant { 88 | assert!(cs.is_satisfied().unwrap()); 89 | } 90 | Ok(()) 91 | } 92 | 93 | #[test] 94 | fn u8_saturating_add() { 95 | run_binary_exhaustive(uint_saturating_add::).unwrap() 96 | } 97 | 98 | #[test] 99 | fn u16_saturating_add() { 100 | run_binary_random::<1000, 16, _, _>(uint_saturating_add::).unwrap() 101 | } 102 | 103 | #[test] 104 | fn u32_saturating_add() { 105 | run_binary_random::<1000, 32, _, _>(uint_saturating_add::).unwrap() 106 | } 107 | 108 | #[test] 109 | fn u64_saturating_add() { 110 | run_binary_random::<1000, 64, _, _>(uint_saturating_add::).unwrap() 111 | } 112 | 113 | #[test] 114 | fn u128_saturating_add() { 115 | run_binary_random::<1000, 128, _, _>(uint_saturating_add::).unwrap() 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/uint/add/wrapping.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::PrimeField; 2 | use ark_relations::gr1cs::SynthesisError; 3 | 4 | use crate::{uint::*, GR1CSVar}; 5 | 6 | impl UInt { 7 | /// Compute `*self = self.wrapping_add(other)`. 8 | pub fn wrapping_add_in_place(&mut self, other: &Self) { 9 | let result = Self::wrapping_add_many(&[self.clone(), other.clone()]).unwrap(); 10 | *self = result; 11 | } 12 | 13 | /// Compute `self.wrapping_add(other)`. 14 | pub fn wrapping_add(&self, other: &Self) -> Self { 15 | let mut result = self.clone(); 16 | result.wrapping_add_in_place(other); 17 | result 18 | } 19 | 20 | /// Perform wrapping addition of `operands`. 21 | /// Computes `operands[0].wrapping_add(operands[1]). 22 | /// wrapping_add(operands[2])...`. 23 | /// 24 | /// The user must ensure that overflow does not occur. 25 | #[tracing::instrument(target = "gr1cs", skip(operands))] 26 | pub fn wrapping_add_many(operands: &[Self]) -> Result 27 | where 28 | F: PrimeField, 29 | { 30 | let (mut sum_bits, value) = Self::add_many_helper(operands, |a, b| a.wrapping_add(&b))?; 31 | if operands.is_constant() { 32 | // If all operands are constant, then the result is also constant. 33 | // In this case, we can return early. 34 | Ok(UInt::constant(value.unwrap())) 35 | } else { 36 | sum_bits.truncate(N); 37 | Ok(UInt { 38 | bits: sum_bits.try_into().unwrap(), 39 | value, 40 | }) 41 | } 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | use crate::{ 49 | alloc::{AllocVar, AllocationMode}, 50 | prelude::EqGadget, 51 | uint::test_utils::{run_binary_exhaustive, run_binary_random}, 52 | GR1CSVar, 53 | }; 54 | use ark_ff::PrimeField; 55 | use ark_test_curves::bls12_381::Fr; 56 | 57 | fn uint_wrapping_add( 58 | a: UInt, 59 | b: UInt, 60 | ) -> Result<(), SynthesisError> { 61 | let cs = a.cs().or(b.cs()); 62 | let both_constant = a.is_constant() && b.is_constant(); 63 | let computed = a.wrapping_add(&b); 64 | let expected_mode = if both_constant { 65 | AllocationMode::Constant 66 | } else { 67 | AllocationMode::Witness 68 | }; 69 | let expected = UInt::new_variable( 70 | cs.clone(), 71 | || Ok(a.value()?.wrapping_add(&b.value()?)), 72 | expected_mode, 73 | )?; 74 | assert_eq!(expected.value(), computed.value()); 75 | expected.enforce_equal(&computed)?; 76 | if !both_constant { 77 | assert!(cs.is_satisfied().unwrap()); 78 | } 79 | Ok(()) 80 | } 81 | 82 | #[test] 83 | fn u8_wrapping_add() { 84 | run_binary_exhaustive(uint_wrapping_add::).unwrap() 85 | } 86 | 87 | #[test] 88 | fn u16_wrapping_add() { 89 | run_binary_random::<1000, 16, _, _>(uint_wrapping_add::).unwrap() 90 | } 91 | 92 | #[test] 93 | fn u32_wrapping_add() { 94 | run_binary_random::<1000, 32, _, _>(uint_wrapping_add::).unwrap() 95 | } 96 | 97 | #[test] 98 | fn u64_wrapping_add() { 99 | run_binary_random::<1000, 64, _, _>(uint_wrapping_add::).unwrap() 100 | } 101 | 102 | #[test] 103 | fn u128_wrapping_add() { 104 | run_binary_random::<1000, 128, _, _>(uint_wrapping_add::).unwrap() 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/uint/cmp.rs: -------------------------------------------------------------------------------- 1 | use crate::cmp::CmpGadget; 2 | 3 | use super::*; 4 | 5 | impl> CmpGadget for UInt { 6 | fn is_ge(&self, other: &Self) -> Result, SynthesisError> { 7 | if N + 1 < ((F::MODULUS_BIT_SIZE - 1) as usize) { 8 | let a = self.to_fp()?; 9 | let b = other.to_fp()?; 10 | let (bits, _) = (a - b + F::from(T::max_value()) + F::one()) 11 | .to_bits_le_with_top_bits_zero(N + 1)?; 12 | Ok(bits.last().unwrap().clone()) 13 | } else { 14 | unimplemented!("bit sizes larger than modulus size not yet supported") 15 | } 16 | } 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use super::*; 22 | use crate::{ 23 | alloc::{AllocVar, AllocationMode}, 24 | prelude::EqGadget, 25 | uint::test_utils::{run_binary_exhaustive, run_binary_random}, 26 | GR1CSVar, 27 | }; 28 | use ark_ff::PrimeField; 29 | use ark_test_curves::bls12_381::Fr; 30 | 31 | fn uint_gt>( 32 | a: UInt, 33 | b: UInt, 34 | ) -> Result<(), SynthesisError> { 35 | let cs = a.cs().or(b.cs()); 36 | let both_constant = a.is_constant() && b.is_constant(); 37 | let expected_mode = if both_constant { 38 | AllocationMode::Constant 39 | } else { 40 | AllocationMode::Witness 41 | }; 42 | let computed = a.is_gt(&b)?; 43 | let expected = 44 | Boolean::new_variable(cs.clone(), || Ok(a.value()? > b.value()?), expected_mode)?; 45 | assert_eq!(expected.value(), computed.value()); 46 | expected.enforce_equal(&computed)?; 47 | if !both_constant { 48 | assert!(cs.is_satisfied().unwrap()); 49 | } 50 | Ok(()) 51 | } 52 | 53 | fn uint_lt>( 54 | a: UInt, 55 | b: UInt, 56 | ) -> Result<(), SynthesisError> { 57 | let cs = a.cs().or(b.cs()); 58 | let both_constant = a.is_constant() && b.is_constant(); 59 | let expected_mode = if both_constant { 60 | AllocationMode::Constant 61 | } else { 62 | AllocationMode::Witness 63 | }; 64 | let computed = a.is_lt(&b)?; 65 | let expected = 66 | Boolean::new_variable(cs.clone(), || Ok(a.value()? < b.value()?), expected_mode)?; 67 | assert_eq!(expected.value(), computed.value()); 68 | expected.enforce_equal(&computed)?; 69 | if !both_constant { 70 | assert!(cs.is_satisfied().unwrap()); 71 | } 72 | Ok(()) 73 | } 74 | 75 | fn uint_ge>( 76 | a: UInt, 77 | b: UInt, 78 | ) -> Result<(), SynthesisError> { 79 | let cs = a.cs().or(b.cs()); 80 | let both_constant = a.is_constant() && b.is_constant(); 81 | let expected_mode = if both_constant { 82 | AllocationMode::Constant 83 | } else { 84 | AllocationMode::Witness 85 | }; 86 | let computed = a.is_ge(&b)?; 87 | let expected = 88 | Boolean::new_variable(cs.clone(), || Ok(a.value()? >= b.value()?), expected_mode)?; 89 | assert_eq!(expected.value(), computed.value()); 90 | expected.enforce_equal(&computed)?; 91 | if !both_constant { 92 | assert!(cs.is_satisfied().unwrap()); 93 | } 94 | Ok(()) 95 | } 96 | 97 | fn uint_le>( 98 | a: UInt, 99 | b: UInt, 100 | ) -> Result<(), SynthesisError> { 101 | let cs = a.cs().or(b.cs()); 102 | let both_constant = a.is_constant() && b.is_constant(); 103 | let expected_mode = if both_constant { 104 | AllocationMode::Constant 105 | } else { 106 | AllocationMode::Witness 107 | }; 108 | let computed = a.is_le(&b)?; 109 | let expected = 110 | Boolean::new_variable(cs.clone(), || Ok(a.value()? <= b.value()?), expected_mode)?; 111 | assert_eq!(expected.value(), computed.value()); 112 | expected.enforce_equal(&computed)?; 113 | if !both_constant { 114 | assert!(cs.is_satisfied().unwrap()); 115 | } 116 | Ok(()) 117 | } 118 | 119 | #[test] 120 | fn u8_gt() { 121 | run_binary_exhaustive(uint_gt::).unwrap() 122 | } 123 | 124 | #[test] 125 | fn u16_gt() { 126 | run_binary_random::<1000, 16, _, _>(uint_gt::).unwrap() 127 | } 128 | 129 | #[test] 130 | fn u32_gt() { 131 | run_binary_random::<1000, 32, _, _>(uint_gt::).unwrap() 132 | } 133 | 134 | #[test] 135 | fn u64_gt() { 136 | run_binary_random::<1000, 64, _, _>(uint_gt::).unwrap() 137 | } 138 | 139 | #[test] 140 | fn u128_gt() { 141 | run_binary_random::<1000, 128, _, _>(uint_gt::).unwrap() 142 | } 143 | 144 | #[test] 145 | fn u8_lt() { 146 | run_binary_exhaustive(uint_lt::).unwrap() 147 | } 148 | 149 | #[test] 150 | fn u16_lt() { 151 | run_binary_random::<1000, 16, _, _>(uint_lt::).unwrap() 152 | } 153 | 154 | #[test] 155 | fn u32_lt() { 156 | run_binary_random::<1000, 32, _, _>(uint_lt::).unwrap() 157 | } 158 | 159 | #[test] 160 | fn u64_lt() { 161 | run_binary_random::<1000, 64, _, _>(uint_lt::).unwrap() 162 | } 163 | 164 | #[test] 165 | fn u128_lt() { 166 | run_binary_random::<1000, 128, _, _>(uint_lt::).unwrap() 167 | } 168 | 169 | #[test] 170 | fn u8_le() { 171 | run_binary_exhaustive(uint_le::).unwrap() 172 | } 173 | 174 | #[test] 175 | fn u16_le() { 176 | run_binary_random::<1000, 16, _, _>(uint_le::).unwrap() 177 | } 178 | 179 | #[test] 180 | fn u32_le() { 181 | run_binary_random::<1000, 32, _, _>(uint_le::).unwrap() 182 | } 183 | 184 | #[test] 185 | fn u64_le() { 186 | run_binary_random::<1000, 64, _, _>(uint_le::).unwrap() 187 | } 188 | 189 | #[test] 190 | fn u128_le() { 191 | run_binary_random::<1000, 128, _, _>(uint_le::).unwrap() 192 | } 193 | 194 | #[test] 195 | fn u8_ge() { 196 | run_binary_exhaustive(uint_ge::).unwrap() 197 | } 198 | 199 | #[test] 200 | fn u16_ge() { 201 | run_binary_random::<1000, 16, _, _>(uint_ge::).unwrap() 202 | } 203 | 204 | #[test] 205 | fn u32_ge() { 206 | run_binary_random::<1000, 32, _, _>(uint_ge::).unwrap() 207 | } 208 | 209 | #[test] 210 | fn u64_ge() { 211 | run_binary_random::<1000, 64, _, _>(uint_ge::).unwrap() 212 | } 213 | 214 | #[test] 215 | fn u128_ge() { 216 | run_binary_random::<1000, 128, _, _>(uint_ge::).unwrap() 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/uint/eq.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::PrimeField; 2 | use ark_relations::gr1cs::SynthesisError; 3 | use ark_std::vec::Vec; 4 | 5 | use crate::{boolean::Boolean, eq::EqGadget}; 6 | 7 | use super::*; 8 | 9 | impl EqGadget 10 | for UInt 11 | { 12 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 13 | fn is_eq(&self, other: &Self) -> Result, SynthesisError> { 14 | let chunk_size = usize::try_from(ConstraintF::MODULUS_BIT_SIZE - 1).unwrap(); 15 | let chunks_are_eq = self 16 | .bits 17 | .chunks(chunk_size) 18 | .zip(other.bits.chunks(chunk_size)) 19 | .map(|(a, b)| { 20 | let a = Boolean::le_bits_to_fp(a)?; 21 | let b = Boolean::le_bits_to_fp(b)?; 22 | a.is_eq(&b) 23 | }) 24 | .collect::, _>>()?; 25 | Boolean::kary_and(&chunks_are_eq) 26 | } 27 | 28 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 29 | fn conditional_enforce_equal( 30 | &self, 31 | other: &Self, 32 | condition: &Boolean, 33 | ) -> Result<(), SynthesisError> { 34 | let chunk_size = usize::try_from(ConstraintF::MODULUS_BIT_SIZE - 1).unwrap(); 35 | for (a, b) in self 36 | .bits 37 | .chunks(chunk_size) 38 | .zip(other.bits.chunks(chunk_size)) 39 | { 40 | let a = Boolean::le_bits_to_fp(a)?; 41 | let b = Boolean::le_bits_to_fp(b)?; 42 | a.conditional_enforce_equal(&b, condition)?; 43 | } 44 | Ok(()) 45 | } 46 | 47 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 48 | fn conditional_enforce_not_equal( 49 | &self, 50 | other: &Self, 51 | condition: &Boolean, 52 | ) -> Result<(), SynthesisError> { 53 | let chunk_size = usize::try_from(ConstraintF::MODULUS_BIT_SIZE - 1).unwrap(); 54 | for (a, b) in self 55 | .bits 56 | .chunks(chunk_size) 57 | .zip(other.bits.chunks(chunk_size)) 58 | { 59 | let a = Boolean::le_bits_to_fp(a)?; 60 | let b = Boolean::le_bits_to_fp(b)?; 61 | a.conditional_enforce_not_equal(&b, condition)?; 62 | } 63 | Ok(()) 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | use crate::{ 71 | alloc::{AllocVar, AllocationMode}, 72 | prelude::EqGadget, 73 | uint::test_utils::{run_binary_exhaustive, run_binary_random}, 74 | GR1CSVar, 75 | }; 76 | use ark_ff::PrimeField; 77 | use ark_test_curves::bls12_381::Fr; 78 | 79 | fn uint_eq( 80 | a: UInt, 81 | b: UInt, 82 | ) -> Result<(), SynthesisError> { 83 | let cs = a.cs().or(b.cs()); 84 | let both_constant = a.is_constant() && b.is_constant(); 85 | let computed = a.is_eq(&b)?; 86 | let expected_mode = if both_constant { 87 | AllocationMode::Constant 88 | } else { 89 | AllocationMode::Witness 90 | }; 91 | let expected = 92 | Boolean::new_variable(cs.clone(), || Ok(a.value()? == b.value()?), expected_mode)?; 93 | assert_eq!(expected.value(), computed.value()); 94 | expected.enforce_equal(&computed)?; 95 | if !both_constant { 96 | assert!(cs.is_satisfied().unwrap()); 97 | } 98 | Ok(()) 99 | } 100 | 101 | fn uint_neq( 102 | a: UInt, 103 | b: UInt, 104 | ) -> Result<(), SynthesisError> { 105 | let cs = a.cs().or(b.cs()); 106 | let both_constant = a.is_constant() && b.is_constant(); 107 | let computed = a.is_neq(&b)?; 108 | let expected_mode = if both_constant { 109 | AllocationMode::Constant 110 | } else { 111 | AllocationMode::Witness 112 | }; 113 | let expected = 114 | Boolean::new_variable(cs.clone(), || Ok(a.value()? != b.value()?), expected_mode)?; 115 | assert_eq!(expected.value(), computed.value()); 116 | expected.enforce_equal(&computed)?; 117 | if !both_constant { 118 | assert!(cs.is_satisfied().unwrap()); 119 | } 120 | Ok(()) 121 | } 122 | 123 | #[test] 124 | fn u8_eq() { 125 | run_binary_exhaustive(uint_eq::).unwrap() 126 | } 127 | 128 | #[test] 129 | fn u16_eq() { 130 | run_binary_random::<1000, 16, _, _>(uint_eq::).unwrap() 131 | } 132 | 133 | #[test] 134 | fn u32_eq() { 135 | run_binary_random::<1000, 32, _, _>(uint_eq::).unwrap() 136 | } 137 | 138 | #[test] 139 | fn u64_eq() { 140 | run_binary_random::<1000, 64, _, _>(uint_eq::).unwrap() 141 | } 142 | 143 | #[test] 144 | fn u128_eq() { 145 | run_binary_random::<1000, 128, _, _>(uint_eq::).unwrap() 146 | } 147 | 148 | #[test] 149 | fn u8_neq() { 150 | run_binary_exhaustive(uint_neq::).unwrap() 151 | } 152 | 153 | #[test] 154 | fn u16_neq() { 155 | run_binary_random::<1000, 16, _, _>(uint_neq::).unwrap() 156 | } 157 | 158 | #[test] 159 | fn u32_neq() { 160 | run_binary_random::<1000, 32, _, _>(uint_neq::).unwrap() 161 | } 162 | 163 | #[test] 164 | fn u64_neq() { 165 | run_binary_random::<1000, 64, _, _>(uint_neq::).unwrap() 166 | } 167 | 168 | #[test] 169 | fn u128_neq() { 170 | run_binary_random::<1000, 128, _, _>(uint_neq::).unwrap() 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/uint/mod.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::{Field, PrimeField}; 2 | use core::{borrow::Borrow, convert::TryFrom, fmt::Debug}; 3 | 4 | use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; 5 | 6 | use crate::{boolean::Boolean, prelude::*, Assignment, Vec}; 7 | 8 | mod add; 9 | mod and; 10 | mod cmp; 11 | mod convert; 12 | mod eq; 13 | mod not; 14 | mod or; 15 | mod rotate; 16 | mod select; 17 | mod shl; 18 | mod shr; 19 | mod xor; 20 | 21 | #[doc(hidden)] 22 | pub mod prim_uint; 23 | pub use prim_uint::*; 24 | 25 | #[cfg(test)] 26 | pub(crate) mod test_utils; 27 | 28 | /// This struct represent an unsigned `N` bit integer as a sequence of `N` 29 | /// [`Boolean`]s. 30 | #[derive(Clone, Debug)] 31 | pub struct UInt { 32 | #[doc(hidden)] 33 | pub bits: [Boolean; N], 34 | #[doc(hidden)] 35 | pub value: Option, 36 | } 37 | 38 | impl GR1CSVar for UInt { 39 | type Value = T; 40 | 41 | fn cs(&self) -> ConstraintSystemRef { 42 | self.bits.as_ref().cs() 43 | } 44 | 45 | fn value(&self) -> Result { 46 | let mut value = T::zero(); 47 | for (i, bit) in self.bits.iter().enumerate() { 48 | value = value + (T::from(bit.value()? as u8).unwrap() << i); 49 | } 50 | debug_assert_eq!(self.value, Some(value)); 51 | Ok(value) 52 | } 53 | } 54 | 55 | impl UInt { 56 | /// The maximum value of the unsigned integer type. 57 | pub const MAX: Self = Self { 58 | bits: [Boolean::TRUE; N], 59 | value: Some(T::MAX), 60 | }; 61 | 62 | /// Construct a constant [`UInt`] from the native unsigned integer type. 63 | /// 64 | /// This *does not* create new variables or constraints. 65 | /// 66 | /// ``` 67 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 68 | /// // We'll use the BLS12-381 scalar field for our constraints. 69 | /// use ark_test_curves::bls12_381::Fr; 70 | /// use ark_relations::gr1cs::*; 71 | /// use ark_r1cs_std::prelude::*; 72 | /// 73 | /// let cs = ConstraintSystem::::new_ref(); 74 | /// let var = UInt8::new_witness(cs.clone(), || Ok(2))?; 75 | /// 76 | /// let constant = UInt8::constant(2); 77 | /// var.enforce_equal(&constant)?; 78 | /// assert!(cs.is_satisfied().unwrap()); 79 | /// # Ok(()) 80 | /// # } 81 | /// ``` 82 | pub fn constant(value: T) -> Self { 83 | let mut bits = [Boolean::FALSE; N]; 84 | let mut bit_values = value; 85 | 86 | for bit in &mut bits { 87 | *bit = Boolean::constant((bit_values & T::one()) == T::one()); 88 | bit_values >>= 1u8; 89 | } 90 | 91 | Self { 92 | bits, 93 | value: Some(value), 94 | } 95 | } 96 | 97 | /// Construct a constant vector of [`UInt`] from a vector of the native type 98 | /// 99 | /// This *does not* create any new variables or constraints. 100 | /// ``` 101 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 102 | /// // We'll use the BLS12-381 scalar field for our constraints. 103 | /// use ark_test_curves::bls12_381::Fr; 104 | /// use ark_relations::gr1cs::*; 105 | /// use ark_r1cs_std::prelude::*; 106 | /// 107 | /// let cs = ConstraintSystem::::new_ref(); 108 | /// let var = vec![UInt8::new_witness(cs.clone(), || Ok(2))?]; 109 | /// 110 | /// let constant = UInt8::constant_vec(&[2]); 111 | /// var.enforce_equal(&constant)?; 112 | /// assert!(cs.is_satisfied().unwrap()); 113 | /// # Ok(()) 114 | /// # } 115 | /// ``` 116 | pub fn constant_vec(values: &[T]) -> Vec { 117 | values.iter().map(|v| Self::constant(*v)).collect() 118 | } 119 | 120 | /// Allocates a slice of `uN`'s as private witnesses. 121 | pub fn new_witness_vec( 122 | cs: impl Into>, 123 | values: &[impl Into> + Copy], 124 | ) -> Result, SynthesisError> { 125 | let ns = cs.into(); 126 | let cs = ns.cs(); 127 | let mut output_vec = Vec::with_capacity(values.len()); 128 | for value in values { 129 | let byte: Option = Into::into(*value); 130 | output_vec.push(Self::new_witness(cs.clone(), || byte.get())?); 131 | } 132 | Ok(output_vec) 133 | } 134 | } 135 | 136 | impl AllocVar 137 | for UInt 138 | { 139 | fn new_variable>( 140 | cs: impl Into>, 141 | f: impl FnOnce() -> Result, 142 | mode: AllocationMode, 143 | ) -> Result { 144 | let ns = cs.into(); 145 | let cs = ns.cs(); 146 | let value = f().map(|f| *f.borrow()).ok(); 147 | 148 | let mut values = [None; N]; 149 | if let Some(val) = value { 150 | values 151 | .iter_mut() 152 | .enumerate() 153 | .for_each(|(i, v)| *v = Some(((val >> i) & T::one()) == T::one())); 154 | } 155 | 156 | let mut bits = [Boolean::FALSE; N]; 157 | for (b, v) in bits.iter_mut().zip(&values) { 158 | *b = Boolean::new_variable(cs.clone(), || v.get(), mode)?; 159 | } 160 | Ok(Self { bits, value }) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/uint/not.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::Field; 2 | use ark_relations::gr1cs::SynthesisError; 3 | use ark_std::ops::Not; 4 | 5 | use super::*; 6 | 7 | impl UInt { 8 | fn _not(&self) -> Result { 9 | let mut result = self.clone(); 10 | result._not_in_place()?; 11 | Ok(result) 12 | } 13 | 14 | fn _not_in_place(&mut self) -> Result<(), SynthesisError> { 15 | for a in &mut self.bits { 16 | a.not_in_place()?; 17 | } 18 | self.value = self.value.map(Not::not); 19 | Ok(()) 20 | } 21 | } 22 | 23 | impl<'a, const N: usize, T: PrimUInt, F: Field> Not for &'a UInt { 24 | type Output = UInt; 25 | /// Outputs `!self`. 26 | /// 27 | /// If `self` is a constant, then this method *does not* create any 28 | /// constraints or variables. 29 | /// 30 | /// ``` 31 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 32 | /// // We'll use the BLS12-381 scalar field for our constraints. 33 | /// use ark_test_curves::bls12_381::Fr; 34 | /// use ark_relations::gr1cs::*; 35 | /// use ark_r1cs_std::prelude::*; 36 | /// 37 | /// let cs = ConstraintSystem::::new_ref(); 38 | /// let a = UInt8::new_witness(cs.clone(), || Ok(2))?; 39 | /// let b = UInt8::new_witness(cs.clone(), || Ok(!2))?; 40 | /// 41 | /// (!a).enforce_equal(&b)?; 42 | /// assert!(cs.is_satisfied().unwrap()); 43 | /// # Ok(()) 44 | /// # } 45 | /// ``` 46 | #[tracing::instrument(target = "gr1cs", skip(self))] 47 | fn not(self) -> Self::Output { 48 | self._not().unwrap() 49 | } 50 | } 51 | 52 | impl<'a, const N: usize, T: PrimUInt, F: Field> Not for UInt { 53 | type Output = UInt; 54 | 55 | /// Outputs `!self`. 56 | /// 57 | /// If `self` is a constant, then this method *does not* create any 58 | /// constraints or variables. 59 | /// 60 | /// ``` 61 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 62 | /// // We'll use the BLS12-381 scalar field for our constraints. 63 | /// use ark_test_curves::bls12_381::Fr; 64 | /// use ark_relations::gr1cs::*; 65 | /// use ark_r1cs_std::prelude::*; 66 | /// 67 | /// let cs = ConstraintSystem::::new_ref(); 68 | /// let a = UInt8::new_witness(cs.clone(), || Ok(2))?; 69 | /// let b = UInt8::new_witness(cs.clone(), || Ok(!2))?; 70 | /// 71 | /// (!a).enforce_equal(&b)?; 72 | /// assert!(cs.is_satisfied().unwrap()); 73 | /// # Ok(()) 74 | /// # } 75 | /// ``` 76 | #[tracing::instrument(target = "gr1cs", skip(self))] 77 | fn not(mut self) -> Self::Output { 78 | self._not_in_place().unwrap(); 79 | self 80 | } 81 | } 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use super::*; 86 | use crate::{ 87 | alloc::{AllocVar, AllocationMode}, 88 | prelude::EqGadget, 89 | uint::test_utils::{run_unary_exhaustive, run_unary_random}, 90 | GR1CSVar, 91 | }; 92 | use ark_ff::PrimeField; 93 | use ark_test_curves::bls12_381::Fr; 94 | 95 | fn uint_not( 96 | a: UInt, 97 | ) -> Result<(), SynthesisError> { 98 | let cs = a.cs(); 99 | let computed = !&a; 100 | let expected_mode = if a.is_constant() { 101 | AllocationMode::Constant 102 | } else { 103 | AllocationMode::Witness 104 | }; 105 | let expected = 106 | UInt::::new_variable(cs.clone(), || Ok(!a.value()?), expected_mode)?; 107 | assert_eq!(expected.value(), computed.value()); 108 | expected.enforce_equal(&computed)?; 109 | if !a.is_constant() { 110 | assert!(cs.is_satisfied().unwrap()); 111 | } 112 | Ok(()) 113 | } 114 | 115 | #[test] 116 | fn u8_not() { 117 | run_unary_exhaustive(uint_not::).unwrap() 118 | } 119 | 120 | #[test] 121 | fn u16_not() { 122 | run_unary_random::<1000, 16, _, _>(uint_not::).unwrap() 123 | } 124 | 125 | #[test] 126 | fn u32_not() { 127 | run_unary_random::<1000, 32, _, _>(uint_not::).unwrap() 128 | } 129 | 130 | #[test] 131 | fn u64_not() { 132 | run_unary_random::<1000, 64, _, _>(uint_not::).unwrap() 133 | } 134 | 135 | #[test] 136 | fn u128() { 137 | run_unary_random::<1000, 128, _, _>(uint_not::).unwrap() 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/uint/prim_uint.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Shl, ShlAssign, Shr, ShrAssign}; 2 | 3 | #[doc(hidden)] 4 | // Adapted from 5 | pub trait PrimUInt: 6 | core::fmt::Debug 7 | + num_traits::PrimInt 8 | + num_traits::WrappingAdd 9 | + num_traits::SaturatingAdd 10 | + Shl 11 | + Shl 12 | + Shl 13 | + Shl 14 | + Shl 15 | + Shl 16 | + Shr 17 | + Shr 18 | + Shr 19 | + Shr 20 | + Shr 21 | + Shr 22 | + ShlAssign 23 | + ShlAssign 24 | + ShlAssign 25 | + ShlAssign 26 | + ShlAssign 27 | + ShlAssign 28 | + ShrAssign 29 | + ShrAssign 30 | + ShrAssign 31 | + ShrAssign 32 | + ShrAssign 33 | + ShrAssign 34 | + Into 35 | + _private::Sealed 36 | + ark_std::UniformRand 37 | { 38 | type Bytes: NumBytes; 39 | const MAX: Self; 40 | #[doc(hidden)] 41 | const MAX_VALUE_BIT_DECOMP: &'static [bool]; 42 | 43 | /// Return the memory representation of this number as a byte array in 44 | /// little-endian byte order. 45 | /// 46 | /// # Examples 47 | /// 48 | /// ``` 49 | /// use ark_r1cs_std::uint::PrimUInt; 50 | /// 51 | /// let bytes = PrimUInt::to_le_bytes(&0x12345678u32); 52 | /// assert_eq!(bytes, [0x78, 0x56, 0x34, 0x12]); 53 | /// ``` 54 | fn to_le_bytes(&self) -> Self::Bytes; 55 | 56 | /// Return the memory representation of this number as a byte array in 57 | /// big-endian byte order. 58 | /// 59 | /// # Examples 60 | /// 61 | /// ``` 62 | /// use ark_r1cs_std::uint::PrimUInt; 63 | /// 64 | /// let bytes = PrimUInt::to_be_bytes(&0x12345678u32); 65 | /// assert_eq!(bytes, [0x12, 0x34, 0x56, 0x78]); 66 | /// ``` 67 | fn to_be_bytes(&self) -> Self::Bytes; 68 | } 69 | 70 | impl PrimUInt for u8 { 71 | const MAX: Self = u8::MAX; 72 | const MAX_VALUE_BIT_DECOMP: &'static [bool] = &[true; 8]; 73 | type Bytes = [u8; 1]; 74 | 75 | #[inline] 76 | fn to_le_bytes(&self) -> Self::Bytes { 77 | u8::to_le_bytes(*self) 78 | } 79 | 80 | #[inline] 81 | fn to_be_bytes(&self) -> Self::Bytes { 82 | u8::to_be_bytes(*self) 83 | } 84 | } 85 | 86 | impl PrimUInt for u16 { 87 | const MAX: Self = u16::MAX; 88 | const MAX_VALUE_BIT_DECOMP: &'static [bool] = &[true; 16]; 89 | type Bytes = [u8; 2]; 90 | 91 | #[inline] 92 | fn to_le_bytes(&self) -> Self::Bytes { 93 | u16::to_le_bytes(*self) 94 | } 95 | 96 | #[inline] 97 | fn to_be_bytes(&self) -> Self::Bytes { 98 | u16::to_be_bytes(*self) 99 | } 100 | } 101 | 102 | impl PrimUInt for u32 { 103 | const MAX: Self = u32::MAX; 104 | const MAX_VALUE_BIT_DECOMP: &'static [bool] = &[true; 32]; 105 | type Bytes = [u8; 4]; 106 | 107 | #[inline] 108 | fn to_le_bytes(&self) -> Self::Bytes { 109 | u32::to_le_bytes(*self) 110 | } 111 | 112 | #[inline] 113 | fn to_be_bytes(&self) -> Self::Bytes { 114 | u32::to_be_bytes(*self) 115 | } 116 | } 117 | 118 | impl PrimUInt for u64 { 119 | const MAX: Self = u64::MAX; 120 | const MAX_VALUE_BIT_DECOMP: &'static [bool] = &[true; 64]; 121 | type Bytes = [u8; 8]; 122 | 123 | #[inline] 124 | fn to_le_bytes(&self) -> Self::Bytes { 125 | u64::to_le_bytes(*self) 126 | } 127 | 128 | #[inline] 129 | fn to_be_bytes(&self) -> Self::Bytes { 130 | u64::to_be_bytes(*self) 131 | } 132 | } 133 | 134 | impl PrimUInt for u128 { 135 | const MAX: Self = u128::MAX; 136 | const MAX_VALUE_BIT_DECOMP: &'static [bool] = &[true; 128]; 137 | type Bytes = [u8; 16]; 138 | 139 | #[inline] 140 | fn to_le_bytes(&self) -> Self::Bytes { 141 | u128::to_le_bytes(*self) 142 | } 143 | 144 | #[inline] 145 | fn to_be_bytes(&self) -> Self::Bytes { 146 | u128::to_be_bytes(*self) 147 | } 148 | } 149 | 150 | #[doc(hidden)] 151 | pub trait NumBytes: 152 | core::fmt::Debug 153 | + AsRef<[u8]> 154 | + AsMut<[u8]> 155 | + PartialEq 156 | + Eq 157 | + PartialOrd 158 | + Ord 159 | + core::hash::Hash 160 | + core::borrow::Borrow<[u8]> 161 | + core::borrow::BorrowMut<[u8]> 162 | { 163 | } 164 | 165 | #[doc(hidden)] 166 | impl NumBytes for [u8; N] {} 167 | 168 | mod _private { 169 | pub trait Sealed {} 170 | 171 | impl Sealed for u8 {} 172 | impl Sealed for u16 {} 173 | impl Sealed for u32 {} 174 | impl Sealed for u64 {} 175 | impl Sealed for u128 {} 176 | } 177 | -------------------------------------------------------------------------------- /src/uint/rotate.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | impl UInt { 4 | /// Rotates `self` to the right by `by` steps, wrapping around. 5 | /// 6 | /// # Examples 7 | /// ``` 8 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 9 | /// // We'll use the BLS12-381 scalar field for our constraints. 10 | /// use ark_test_curves::bls12_381::Fr; 11 | /// use ark_relations::gr1cs::*; 12 | /// use ark_r1cs_std::prelude::*; 13 | /// 14 | /// let cs = ConstraintSystem::::new_ref(); 15 | /// let a = UInt32::new_witness(cs.clone(), || Ok(0xb301u32))?; 16 | /// let b = UInt32::new_witness(cs.clone(), || Ok(0x10000b3))?; 17 | /// 18 | /// a.rotate_right(8).enforce_equal(&b)?; 19 | /// assert!(cs.is_satisfied().unwrap()); 20 | /// # Ok(()) 21 | /// # } 22 | /// ``` 23 | #[tracing::instrument(target = "gr1cs", skip(self))] 24 | pub fn rotate_right(&self, by: usize) -> Self { 25 | let mut result = self.clone(); 26 | result.rotate_right_in_place(by); 27 | result 28 | } 29 | /// Rotates `self` to the right *in place* by `by` steps, wrapping around. 30 | /// 31 | /// # Examples 32 | /// ``` 33 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 34 | /// // We'll use the BLS12-381 scalar field for our constraints. 35 | /// use ark_test_curves::bls12_381::Fr; 36 | /// use ark_relations::gr1cs::*; 37 | /// use ark_r1cs_std::prelude::*; 38 | /// 39 | /// let cs = ConstraintSystem::::new_ref(); 40 | /// let mut a = UInt32::new_witness(cs.clone(), || Ok(0xb301u32))?; 41 | /// let b = UInt32::new_witness(cs.clone(), || Ok(0x10000b3))?; 42 | /// 43 | /// a.rotate_right_in_place(8); 44 | /// a.enforce_equal(&b)?; 45 | /// assert!(cs.is_satisfied().unwrap()); 46 | /// # Ok(()) 47 | /// # } 48 | /// ``` 49 | #[tracing::instrument(target = "gr1cs", skip(self))] 50 | pub fn rotate_right_in_place(&mut self, by: usize) { 51 | let by = by % N; 52 | // `[T]::rotate_left` corresponds to a `rotate_right` of the bits. 53 | self.bits.rotate_left(by); 54 | self.value = self.value.map(|v| v.rotate_right(by as u32)); 55 | } 56 | 57 | /// Rotates `self` to the left by `by` steps, wrapping around. 58 | /// 59 | /// # Examples 60 | /// ``` 61 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 62 | /// // We'll use the BLS12-381 scalar field for our constraints. 63 | /// use ark_test_curves::bls12_381::Fr; 64 | /// use ark_relations::gr1cs::*; 65 | /// use ark_r1cs_std::prelude::*; 66 | /// 67 | /// let cs = ConstraintSystem::::new_ref(); 68 | /// let a = UInt32::new_witness(cs.clone(), || Ok(0x10000b3))?; 69 | /// let b = UInt32::new_witness(cs.clone(), || Ok(0xb301u32))?; 70 | /// 71 | /// a.rotate_left(8).enforce_equal(&b)?; 72 | /// assert!(cs.is_satisfied().unwrap()); 73 | /// # Ok(()) 74 | /// # } 75 | /// ``` 76 | #[tracing::instrument(target = "gr1cs", skip(self))] 77 | pub fn rotate_left(&self, by: usize) -> Self { 78 | let mut result = self.clone(); 79 | result.rotate_left_in_place(by); 80 | result 81 | } 82 | 83 | /// Rotates `self` to the left *in place* by `by` steps, wrapping around. 84 | /// 85 | /// # Examples 86 | /// ``` 87 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 88 | /// // We'll use the BLS12-381 scalar field for our constraints. 89 | /// use ark_test_curves::bls12_381::Fr; 90 | /// use ark_relations::gr1cs::*; 91 | /// use ark_r1cs_std::prelude::*; 92 | /// 93 | /// let cs = ConstraintSystem::::new_ref(); 94 | /// let mut a = UInt32::new_witness(cs.clone(), || Ok(0x10000b3))?; 95 | /// let b = UInt32::new_witness(cs.clone(), || Ok(0xb301u32))?; 96 | /// 97 | /// a.rotate_left_in_place(8); 98 | /// a.enforce_equal(&b)?; 99 | /// assert!(cs.is_satisfied().unwrap()); 100 | /// # Ok(()) 101 | /// # } 102 | /// ``` 103 | pub fn rotate_left_in_place(&mut self, by: usize) { 104 | let by = by % N; 105 | // `[T]::rotate_right` corresponds to a `rotate_left` of the bits. 106 | self.bits.rotate_right(by); 107 | self.value = self.value.map(|v| v.rotate_left(by as u32)); 108 | } 109 | } 110 | 111 | #[cfg(test)] 112 | mod tests { 113 | use super::*; 114 | use crate::{ 115 | alloc::{AllocVar, AllocationMode}, 116 | prelude::EqGadget, 117 | uint::test_utils::{run_unary_exhaustive, run_unary_random}, 118 | GR1CSVar, 119 | }; 120 | use ark_ff::PrimeField; 121 | use ark_test_curves::bls12_381::Fr; 122 | 123 | fn uint_rotate_left( 124 | a: UInt, 125 | ) -> Result<(), SynthesisError> { 126 | let cs = a.cs(); 127 | let expected_mode = if a.is_constant() { 128 | AllocationMode::Constant 129 | } else { 130 | AllocationMode::Witness 131 | }; 132 | for shift in 0..N { 133 | let computed = a.rotate_left(shift); 134 | let expected = UInt::::new_variable( 135 | cs.clone(), 136 | || Ok(a.value()?.rotate_left(shift as u32)), 137 | expected_mode, 138 | )?; 139 | assert_eq!(expected.value(), computed.value()); 140 | expected.enforce_equal(&computed)?; 141 | if !a.is_constant() { 142 | assert!(cs.is_satisfied().unwrap()); 143 | } 144 | } 145 | Ok(()) 146 | } 147 | 148 | fn uint_rotate_right( 149 | a: UInt, 150 | ) -> Result<(), SynthesisError> { 151 | let cs = a.cs(); 152 | let expected_mode = if a.is_constant() { 153 | AllocationMode::Constant 154 | } else { 155 | AllocationMode::Witness 156 | }; 157 | for shift in 0..N { 158 | let computed = a.rotate_right(shift); 159 | let expected = UInt::::new_variable( 160 | cs.clone(), 161 | || Ok(a.value()?.rotate_right(shift as u32)), 162 | expected_mode, 163 | )?; 164 | assert_eq!(expected.value(), computed.value()); 165 | expected.enforce_equal(&computed)?; 166 | if !a.is_constant() { 167 | assert!(cs.is_satisfied().unwrap()); 168 | } 169 | } 170 | Ok(()) 171 | } 172 | 173 | #[test] 174 | fn u8_rotate_left() { 175 | run_unary_exhaustive(uint_rotate_left::).unwrap() 176 | } 177 | 178 | #[test] 179 | fn u16_rotate_left() { 180 | run_unary_random::<1000, 16, _, _>(uint_rotate_left::).unwrap() 181 | } 182 | 183 | #[test] 184 | fn u32_rotate_left() { 185 | run_unary_random::<1000, 32, _, _>(uint_rotate_left::).unwrap() 186 | } 187 | 188 | #[test] 189 | fn u64_rotate_left() { 190 | run_unary_random::<200, 64, _, _>(uint_rotate_left::).unwrap() 191 | } 192 | 193 | #[test] 194 | fn u128_rotate_left() { 195 | run_unary_random::<100, 128, _, _>(uint_rotate_left::).unwrap() 196 | } 197 | 198 | #[test] 199 | fn u8_rotate_right() { 200 | run_unary_exhaustive(uint_rotate_right::).unwrap() 201 | } 202 | 203 | #[test] 204 | fn u16_rotate_right() { 205 | run_unary_random::<1000, 16, _, _>(uint_rotate_right::).unwrap() 206 | } 207 | 208 | #[test] 209 | fn u32_rotate_right() { 210 | run_unary_random::<1000, 32, _, _>(uint_rotate_right::).unwrap() 211 | } 212 | 213 | #[test] 214 | fn u64_rotate_right() { 215 | run_unary_random::<200, 64, _, _>(uint_rotate_right::).unwrap() 216 | } 217 | 218 | #[test] 219 | fn u128_rotate_right() { 220 | run_unary_random::<100, 128, _, _>(uint_rotate_right::).unwrap() 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/uint/select.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | impl CondSelectGadget 4 | for UInt 5 | { 6 | #[tracing::instrument(target = "gr1cs", skip(cond, true_value, false_value))] 7 | fn conditionally_select( 8 | cond: &Boolean, 9 | true_value: &Self, 10 | false_value: &Self, 11 | ) -> Result { 12 | let selected_bits = true_value 13 | .bits 14 | .iter() 15 | .zip(&false_value.bits) 16 | .map(|(t, f)| cond.select(t, f)); 17 | let mut bits = [Boolean::FALSE; N]; 18 | for (result, new) in bits.iter_mut().zip(selected_bits) { 19 | *result = new?; 20 | } 21 | 22 | let value = cond.value().ok().and_then(|cond| { 23 | if cond { 24 | true_value.value().ok() 25 | } else { 26 | false_value.value().ok() 27 | } 28 | }); 29 | Ok(Self { bits, value }) 30 | } 31 | } 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | use super::*; 36 | use crate::uint::test_utils::{run_binary_exhaustive, run_binary_random}; 37 | use ark_test_curves::bls12_381::Fr; 38 | 39 | fn uint_select( 40 | a: UInt, 41 | b: UInt, 42 | ) -> Result<(), SynthesisError> { 43 | let cs = a.cs().or(b.cs()); 44 | let both_constant = a.is_constant() && b.is_constant(); 45 | let expected_mode = if both_constant { 46 | AllocationMode::Constant 47 | } else { 48 | AllocationMode::Witness 49 | }; 50 | for cond in [true, false] { 51 | let expected = UInt::new_variable( 52 | cs.clone(), 53 | || Ok(if cond { a.value()? } else { b.value()? }), 54 | expected_mode, 55 | )?; 56 | let cond = Boolean::new_variable(cs.clone(), || Ok(cond), expected_mode)?; 57 | let computed = cond.select(&a, &b)?; 58 | 59 | assert_eq!(expected.value(), computed.value()); 60 | expected.enforce_equal(&computed)?; 61 | if !both_constant { 62 | assert!(cs.is_satisfied().unwrap()); 63 | } 64 | } 65 | Ok(()) 66 | } 67 | 68 | #[test] 69 | fn u8_select() { 70 | run_binary_exhaustive(uint_select::).unwrap() 71 | } 72 | 73 | #[test] 74 | fn u16_select() { 75 | run_binary_random::<1000, 16, _, _>(uint_select::).unwrap() 76 | } 77 | 78 | #[test] 79 | fn u32_select() { 80 | run_binary_random::<1000, 32, _, _>(uint_select::).unwrap() 81 | } 82 | 83 | #[test] 84 | fn u64_select() { 85 | run_binary_random::<1000, 64, _, _>(uint_select::).unwrap() 86 | } 87 | 88 | #[test] 89 | fn u128_select() { 90 | run_binary_random::<1000, 128, _, _>(uint_select::).unwrap() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/uint/shl.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::PrimeField; 2 | use ark_relations::gr1cs::SynthesisError; 3 | use ark_std::ops::{Shl, ShlAssign}; 4 | 5 | use crate::boolean::Boolean; 6 | 7 | use super::{PrimUInt, UInt}; 8 | 9 | impl UInt { 10 | fn _shl_u128(&self, other: u128) -> Result { 11 | if other < N as u128 { 12 | let mut bits = [Boolean::FALSE; N]; 13 | for (a, b) in bits[other as usize..].iter_mut().zip(&self.bits) { 14 | *a = b.clone(); 15 | } 16 | 17 | let value = self.value.and_then(|a| Some(a << other)); 18 | Ok(Self { bits, value }) 19 | } else { 20 | panic!("attempt to shift left with overflow") 21 | } 22 | } 23 | } 24 | 25 | impl Shl for UInt { 26 | type Output = Self; 27 | 28 | /// Output `self << other`. 29 | /// 30 | /// If at least one of `self` and `other` are constants, then this method 31 | /// *does not* create any constraints or variables. 32 | /// 33 | /// ``` 34 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 35 | /// // We'll use the BLS12-381 scalar field for our constraints. 36 | /// use ark_test_curves::bls12_381::Fr; 37 | /// use ark_relations::gr1cs::*; 38 | /// use ark_r1cs_std::prelude::*; 39 | /// 40 | /// let cs = ConstraintSystem::::new_ref(); 41 | /// let a = UInt8::new_witness(cs.clone(), || Ok(16))?; 42 | /// let b = 1u8; 43 | /// let c = UInt8::new_witness(cs.clone(), || Ok(16 << 1))?; 44 | /// 45 | /// (a << b).enforce_equal(&c)?; 46 | /// assert!(cs.is_satisfied().unwrap()); 47 | /// # Ok(()) 48 | /// # } 49 | /// ``` 50 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 51 | fn shl(self, other: T2) -> Self::Output { 52 | self._shl_u128(other.into()).unwrap() 53 | } 54 | } 55 | 56 | impl<'a, const N: usize, T: PrimUInt, F: PrimeField, T2: PrimUInt> Shl for &'a UInt { 57 | type Output = UInt; 58 | 59 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 60 | fn shl(self, other: T2) -> Self::Output { 61 | self._shl_u128(other.into()).unwrap() 62 | } 63 | } 64 | 65 | impl ShlAssign for UInt { 66 | /// Sets `self = self << other`. 67 | /// 68 | /// If at least one of `self` and `other` are constants, then this method 69 | /// *does not* create any constraints or variables. 70 | /// 71 | /// ``` 72 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 73 | /// // We'll use the BLS12-381 scalar field for our constraints. 74 | /// use ark_test_curves::bls12_381::Fr; 75 | /// use ark_relations::gr1cs::*; 76 | /// use ark_r1cs_std::prelude::*; 77 | /// 78 | /// let cs = ConstraintSystem::::new_ref(); 79 | /// let mut a = UInt8::new_witness(cs.clone(), || Ok(16))?; 80 | /// let b = 1u8; 81 | /// let c = UInt8::new_witness(cs.clone(), || Ok(16 << 1))?; 82 | /// 83 | /// a <<= b; 84 | /// a.enforce_equal(&c)?; 85 | /// assert!(cs.is_satisfied().unwrap()); 86 | /// # Ok(()) 87 | /// # } 88 | /// ``` 89 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 90 | fn shl_assign(&mut self, other: T2) { 91 | let result = self._shl_u128(other.into()).unwrap(); 92 | *self = result; 93 | } 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use super::*; 99 | use crate::{ 100 | alloc::{AllocVar, AllocationMode}, 101 | prelude::EqGadget, 102 | uint::test_utils::{run_binary_exhaustive_native_only, run_binary_random_native_only}, 103 | GR1CSVar, 104 | }; 105 | use ark_test_curves::bls12_381::Fr; 106 | 107 | fn uint_shl( 108 | a: UInt, 109 | b: T, 110 | ) -> Result<(), SynthesisError> { 111 | let cs = a.cs(); 112 | let b = b.into() % (N as u128); 113 | let computed = &a << b; 114 | let expected_mode = if a.is_constant() { 115 | AllocationMode::Constant 116 | } else { 117 | AllocationMode::Witness 118 | }; 119 | let expected = 120 | UInt::::new_variable(cs.clone(), || Ok(a.value()? << b), expected_mode)?; 121 | assert_eq!(expected.value(), computed.value()); 122 | expected.enforce_equal(&computed)?; 123 | if !a.is_constant() { 124 | assert!(cs.is_satisfied().unwrap()); 125 | } 126 | Ok(()) 127 | } 128 | 129 | #[test] 130 | fn u8_shl() { 131 | run_binary_exhaustive_native_only(uint_shl::).unwrap() 132 | } 133 | 134 | #[test] 135 | fn u16_shl() { 136 | run_binary_random_native_only::<1000, 16, _, _>(uint_shl::).unwrap() 137 | } 138 | 139 | #[test] 140 | fn u32_shl() { 141 | run_binary_random_native_only::<1000, 32, _, _>(uint_shl::).unwrap() 142 | } 143 | 144 | #[test] 145 | fn u64_shl() { 146 | run_binary_random_native_only::<1000, 64, _, _>(uint_shl::).unwrap() 147 | } 148 | 149 | #[test] 150 | fn u128_shl() { 151 | run_binary_random_native_only::<1000, 128, _, _>(uint_shl::).unwrap() 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/uint/shr.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::PrimeField; 2 | use ark_relations::gr1cs::SynthesisError; 3 | use ark_std::ops::{Shr, ShrAssign}; 4 | 5 | use crate::boolean::Boolean; 6 | 7 | use super::{PrimUInt, UInt}; 8 | 9 | impl UInt { 10 | fn _shr_u128(&self, other: u128) -> Result { 11 | if other < N as u128 { 12 | let mut bits = [Boolean::FALSE; N]; 13 | for (a, b) in bits.iter_mut().zip(&self.bits[other as usize..]) { 14 | *a = b.clone(); 15 | } 16 | 17 | let value = self.value.map(|a| a >> other); 18 | Ok(Self { bits, value }) 19 | } else { 20 | panic!("attempt to shift right with overflow") 21 | } 22 | } 23 | } 24 | 25 | impl Shr for UInt { 26 | type Output = Self; 27 | 28 | /// Output `self >> other`. 29 | /// 30 | /// If at least one of `self` and `other` are constants, then this method 31 | /// *does not* create any constraints or variables. 32 | /// 33 | /// ``` 34 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 35 | /// // We'll use the BLS12-381 scalar field for our constraints. 36 | /// use ark_test_curves::bls12_381::Fr; 37 | /// use ark_relations::gr1cs::*; 38 | /// use ark_r1cs_std::prelude::*; 39 | /// 40 | /// let cs = ConstraintSystem::::new_ref(); 41 | /// let a = UInt8::new_witness(cs.clone(), || Ok(16))?; 42 | /// let b = 1u8; 43 | /// let c = UInt8::new_witness(cs.clone(), || Ok(16 >> 1))?; 44 | /// 45 | /// (a >> b).enforce_equal(&c)?; 46 | /// assert!(cs.is_satisfied().unwrap()); 47 | /// # Ok(()) 48 | /// # } 49 | /// ``` 50 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 51 | fn shr(self, other: T2) -> Self::Output { 52 | self._shr_u128(other.into()).unwrap() 53 | } 54 | } 55 | 56 | impl Shr for &UInt { 57 | type Output = UInt; 58 | 59 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 60 | fn shr(self, other: T2) -> Self::Output { 61 | self._shr_u128(other.into()).unwrap() 62 | } 63 | } 64 | 65 | impl ShrAssign for UInt { 66 | /// Sets `self = self >> other`. 67 | /// 68 | /// If at least one of `self` and `other` are constants, then this method 69 | /// *does not* create any constraints or variables. 70 | /// 71 | /// ``` 72 | /// # fn main() -> Result<(), ark_relations::gr1cs::SynthesisError> { 73 | /// // We'll use the BLS12-381 scalar field for our constraints. 74 | /// use ark_test_curves::bls12_381::Fr; 75 | /// use ark_relations::gr1cs::*; 76 | /// use ark_r1cs_std::prelude::*; 77 | /// 78 | /// let cs = ConstraintSystem::::new_ref(); 79 | /// let mut a = UInt8::new_witness(cs.clone(), || Ok(16))?; 80 | /// let b = 1u8; 81 | /// let c = UInt8::new_witness(cs.clone(), || Ok(16 >> 1))?; 82 | /// 83 | /// a >>= b; 84 | /// a.enforce_equal(&c)?; 85 | /// assert!(cs.is_satisfied().unwrap()); 86 | /// # Ok(()) 87 | /// # } 88 | /// ``` 89 | #[tracing::instrument(target = "gr1cs", skip(self, other))] 90 | fn shr_assign(&mut self, other: T2) { 91 | let result = self._shr_u128(other.into()).unwrap(); 92 | *self = result; 93 | } 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use super::*; 99 | use crate::{ 100 | alloc::{AllocVar, AllocationMode}, 101 | prelude::EqGadget, 102 | uint::test_utils::{run_binary_exhaustive_native_only, run_binary_random_native_only}, 103 | GR1CSVar, 104 | }; 105 | use ark_test_curves::bls12_381::Fr; 106 | 107 | fn uint_shr( 108 | a: UInt, 109 | b: T, 110 | ) -> Result<(), SynthesisError> { 111 | let cs = a.cs(); 112 | let b = b.into() % (N as u128); 113 | let computed = &a >> b; 114 | let expected_mode = if a.is_constant() { 115 | AllocationMode::Constant 116 | } else { 117 | AllocationMode::Witness 118 | }; 119 | let expected = 120 | UInt::::new_variable(cs.clone(), || Ok(a.value()? >> b), expected_mode)?; 121 | assert_eq!(expected.value(), computed.value()); 122 | expected.enforce_equal(&computed)?; 123 | if !a.is_constant() { 124 | assert!(cs.is_satisfied().unwrap()); 125 | } 126 | Ok(()) 127 | } 128 | 129 | #[test] 130 | fn u8_shr() { 131 | run_binary_exhaustive_native_only(uint_shr::).unwrap() 132 | } 133 | 134 | #[test] 135 | fn u16_shr() { 136 | run_binary_random_native_only::<1000, 16, _, _>(uint_shr::).unwrap() 137 | } 138 | 139 | #[test] 140 | fn u32_shr() { 141 | run_binary_random_native_only::<1000, 32, _, _>(uint_shr::).unwrap() 142 | } 143 | 144 | #[test] 145 | fn u64_shr() { 146 | run_binary_random_native_only::<1000, 64, _, _>(uint_shr::).unwrap() 147 | } 148 | 149 | #[test] 150 | fn u128_shr() { 151 | run_binary_random_native_only::<1000, 128, _, _>(uint_shr::).unwrap() 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/uint/test_utils.rs: -------------------------------------------------------------------------------- 1 | use ark_relations::gr1cs::{ConstraintSystem, SynthesisError}; 2 | use std::ops::RangeInclusive; 3 | 4 | use crate::test_utils::{self, modes}; 5 | 6 | use super::*; 7 | 8 | pub(crate) fn test_unary_op( 9 | a: T, 10 | mode: AllocationMode, 11 | test: impl FnOnce(UInt) -> Result<(), SynthesisError>, 12 | ) -> Result<(), SynthesisError> { 13 | let cs = ConstraintSystem::::new_ref(); 14 | let a = UInt::::new_variable(cs.clone(), || Ok(a), mode)?; 15 | test(a) 16 | } 17 | 18 | pub(crate) fn test_binary_op( 19 | a: T, 20 | b: T, 21 | mode_a: AllocationMode, 22 | mode_b: AllocationMode, 23 | test: impl FnOnce(UInt, UInt) -> Result<(), SynthesisError>, 24 | ) -> Result<(), SynthesisError> { 25 | let cs = ConstraintSystem::::new_ref(); 26 | let a = UInt::::new_variable(cs.clone(), || Ok(a), mode_a)?; 27 | let b = UInt::::new_variable(cs.clone(), || Ok(b), mode_b)?; 28 | test(a, b) 29 | } 30 | 31 | pub(crate) fn test_binary_op_with_native( 32 | a: T, 33 | b: T, 34 | mode_a: AllocationMode, 35 | test: impl FnOnce(UInt, T) -> Result<(), SynthesisError>, 36 | ) -> Result<(), SynthesisError> { 37 | let cs = ConstraintSystem::::new_ref(); 38 | let a = UInt::::new_variable(cs.clone(), || Ok(a), mode_a)?; 39 | test(a, b) 40 | } 41 | 42 | pub(crate) fn run_binary_random_both( 43 | test: impl Fn(UInt, UInt) -> Result<(), SynthesisError> + Copy, 44 | test_native: impl Fn(UInt, T) -> Result<(), SynthesisError> + Copy, 45 | ) -> Result<(), SynthesisError> 46 | where 47 | T: PrimUInt, 48 | F: PrimeField, 49 | { 50 | let mut rng = ark_std::test_rng(); 51 | 52 | for _ in 0..ITERATIONS { 53 | for mode_a in modes() { 54 | let a = T::rand(&mut rng); 55 | for mode_b in modes() { 56 | let b = T::rand(&mut rng); 57 | test_binary_op(a, b, mode_a, mode_b, test)?; 58 | test_binary_op_with_native(a, b, mode_a, test_native)?; 59 | } 60 | } 61 | } 62 | Ok(()) 63 | } 64 | 65 | pub(crate) fn run_binary_random( 66 | test: impl Fn(UInt, UInt) -> Result<(), SynthesisError> + Copy, 67 | ) -> Result<(), SynthesisError> 68 | where 69 | T: PrimUInt, 70 | F: PrimeField, 71 | { 72 | let mut rng = ark_std::test_rng(); 73 | 74 | for _ in 0..ITERATIONS { 75 | for mode_a in modes() { 76 | let a = T::rand(&mut rng); 77 | for mode_b in modes() { 78 | let b = T::rand(&mut rng); 79 | test_binary_op(a, b, mode_a, mode_b, test)?; 80 | } 81 | } 82 | } 83 | Ok(()) 84 | } 85 | 86 | pub(crate) fn run_binary_exhaustive( 87 | test: impl Fn(UInt, UInt) -> Result<(), SynthesisError> + Copy, 88 | ) -> Result<(), SynthesisError> 89 | where 90 | T: PrimUInt, 91 | F: PrimeField, 92 | RangeInclusive: Iterator, 93 | { 94 | for (mode_a, a) in test_utils::combination(T::min_value()..=T::max_value()) { 95 | for (mode_b, b) in test_utils::combination(T::min_value()..=T::max_value()) { 96 | test_binary_op(a, b, mode_a, mode_b, test)?; 97 | } 98 | } 99 | Ok(()) 100 | } 101 | 102 | pub(crate) fn run_binary_exhaustive_both( 103 | test: impl Fn(UInt, UInt) -> Result<(), SynthesisError> + Copy, 104 | test_native: impl Fn(UInt, T) -> Result<(), SynthesisError> + Copy, 105 | ) -> Result<(), SynthesisError> 106 | where 107 | T: PrimUInt, 108 | F: PrimeField, 109 | RangeInclusive: Iterator, 110 | { 111 | for (mode_a, a) in test_utils::combination(T::min_value()..=T::max_value()) { 112 | for (mode_b, b) in test_utils::combination(T::min_value()..=T::max_value()) { 113 | test_binary_op(a, b, mode_a, mode_b, test)?; 114 | test_binary_op_with_native(a, b, mode_a, test_native)?; 115 | } 116 | } 117 | Ok(()) 118 | } 119 | 120 | pub(crate) fn run_binary_random_native_only( 121 | test: impl Fn(UInt, T) -> Result<(), SynthesisError> + Copy, 122 | ) -> Result<(), SynthesisError> 123 | where 124 | T: PrimUInt, 125 | F: PrimeField, 126 | { 127 | let mut rng = ark_std::test_rng(); 128 | 129 | for _ in 0..ITERATIONS { 130 | for mode_a in modes() { 131 | let a = T::rand(&mut rng); 132 | let b = T::rand(&mut rng); 133 | test_binary_op_with_native(a, b, mode_a, test)?; 134 | } 135 | } 136 | Ok(()) 137 | } 138 | 139 | pub(crate) fn run_binary_exhaustive_native_only( 140 | test: impl Fn(UInt, T) -> Result<(), SynthesisError> + Copy, 141 | ) -> Result<(), SynthesisError> 142 | where 143 | T: PrimUInt, 144 | F: PrimeField, 145 | RangeInclusive: Iterator, 146 | { 147 | for (mode_a, a) in test_utils::combination(T::min_value()..=T::max_value()) { 148 | for b in T::min_value()..=T::max_value() { 149 | test_binary_op_with_native(a, b, mode_a, test)?; 150 | } 151 | } 152 | Ok(()) 153 | } 154 | 155 | pub(crate) fn run_unary_random( 156 | test: impl Fn(UInt) -> Result<(), SynthesisError> + Copy, 157 | ) -> Result<(), SynthesisError> 158 | where 159 | T: PrimUInt, 160 | F: PrimeField, 161 | { 162 | let mut rng = ark_std::test_rng(); 163 | 164 | for _ in 0..ITERATIONS { 165 | for mode_a in modes() { 166 | let a = T::rand(&mut rng); 167 | test_unary_op(a, mode_a, test)?; 168 | } 169 | } 170 | Ok(()) 171 | } 172 | 173 | pub(crate) fn run_unary_exhaustive( 174 | test: impl Fn(UInt) -> Result<(), SynthesisError> + Copy, 175 | ) -> Result<(), SynthesisError> 176 | where 177 | T: PrimUInt, 178 | F: PrimeField, 179 | RangeInclusive: Iterator, 180 | { 181 | for (mode, a) in test_utils::combination(T::min_value()..=T::max_value()) { 182 | test_unary_op(a, mode, test)?; 183 | } 184 | Ok(()) 185 | } 186 | -------------------------------------------------------------------------------- /tests/from_test.rs: -------------------------------------------------------------------------------- 1 | use ark_r1cs_std::{ 2 | alloc::AllocVar, 3 | fields::emulated_fp::{EmulatedFpVar, MulResultVar}, 4 | GR1CSVar, 5 | }; 6 | use ark_relations::gr1cs::ConstraintSystem; 7 | use ark_std::UniformRand; 8 | 9 | #[test] 10 | fn from_test() { 11 | type F = ark_bls12_377::Fr; 12 | type CF = ark_bls12_377::Fq; 13 | 14 | let mut rng = ark_std::test_rng(); 15 | let cs = ConstraintSystem::::new_ref(); 16 | let f = F::rand(&mut rng); 17 | 18 | let f_var = EmulatedFpVar::::new_input(cs.clone(), || Ok(f)).unwrap(); 19 | let f_var_converted = MulResultVar::::from(&f_var); 20 | let f_var_converted_reduced = f_var_converted.reduce().unwrap(); 21 | 22 | let f_var_value = f_var.value().unwrap(); 23 | let f_var_converted_reduced_value = f_var_converted_reduced.value().unwrap(); 24 | 25 | assert_eq!(f_var_value, f_var_converted_reduced_value); 26 | } 27 | -------------------------------------------------------------------------------- /tests/to_constraint_field_test.rs: -------------------------------------------------------------------------------- 1 | use ark_r1cs_std::{ 2 | alloc::AllocVar, convert::ToConstraintFieldGadget, fields::emulated_fp::EmulatedFpVar, GR1CSVar, 3 | }; 4 | use ark_relations::gr1cs::ConstraintSystem; 5 | 6 | #[test] 7 | fn to_constraint_field_test() { 8 | type F = ark_bls12_377::Fr; 9 | type CF = ark_bls12_377::Fq; 10 | 11 | let cs = ConstraintSystem::::new_ref(); 12 | 13 | let a = EmulatedFpVar::Constant(F::from(12u8)); 14 | let b = EmulatedFpVar::new_input(cs.clone(), || Ok(F::from(6u8))).unwrap(); 15 | 16 | let b2 = &b + &b; 17 | 18 | let a_to_constraint_field = a.to_constraint_field().unwrap(); 19 | let b2_to_constraint_field = b2.to_constraint_field().unwrap(); 20 | 21 | assert_eq!(a_to_constraint_field.len(), b2_to_constraint_field.len()); 22 | for (left, right) in a_to_constraint_field 23 | .iter() 24 | .zip(b2_to_constraint_field.iter()) 25 | { 26 | assert_eq!(left.value(), right.value()); 27 | } 28 | } 29 | --------------------------------------------------------------------------------